Logo xia0o0o0o

First Step Toward a Full Chain: Exploiting Chrome on Android

May 27, 2025
4 min read
Table of Contents

First Step Toward a Full Chain: Exploiting Chrome on Android

This is the first part of a series of writeups on exploiting Chrome on Android. In this part, we will focus on exploiting the renderer process of Chrome on Android, and enable MojoJS binding for further exploitation.

Introduction

In this writeup, we will exploit CVE-2020-16040 to gain arbitrary read/write in the renderer process. Thanks to Corellium, we can setup the debugging environment easily. corellium. We chose to use Google Pixel 3 with firmware version 10.0.0.

➜ git:(main) adb shell
shell:/$ id
uid=2000(shell) gid=2000(shell) groups=1004(input),1007(log),1011(adb),1015(sdcard_rw),1028(sdcard_r),3001(net_bt_admin),3002(net_bt),3003(inet),3006(net_bw_stats),3009(readproc),3011(uhid)
shell:/$

Exploit the renderer

Starting from the diff of the patch,

diff --git a/src/compiler/simplified-lowering.cc b/src/compiler/simplified-lowering.cc
index a1f10f98fe5..ef56d56e447 100644
--- a/src/compiler/simplified-lowering.cc
+++ b/src/compiler/simplified-lowering.cc
@@ -1409,7 +1409,6 @@ class RepresentationSelector {
                 IsSomePositiveOrderedNumber(input1_type)
             ? CheckForMinusZeroMode::kDontCheckForMinusZero
             : CheckForMinusZeroMode::kCheckForMinusZero;
-
     NodeProperties::ChangeOp(node, simplified()->CheckedInt32Mul(mz_mode));
   }
 
@@ -1453,6 +1452,13 @@ class RepresentationSelector {
 
     Type left_feedback_type = TypeOf(node->InputAt(0));
     Type right_feedback_type = TypeOf(node->InputAt(1));
+
+    // Using Signed32 as restriction type amounts to promising there won't be
+    // signed overflow. This is incompatible with relying on a Word32
+    // truncation in order to skip the overflow check.
+    Type const restriction =
+        truncation.IsUsedAsWord32() ? Type::Any() : Type::Signed32();
+
     // Handle the case when no int32 checks on inputs are necessary (but
     // an overflow check is needed on the output). Note that we do not
     // have to do any check if at most one side can be minus zero. For
@@ -1466,7 +1472,7 @@ class RepresentationSelector {
         right_upper.Is(Type::Signed32OrMinusZero()) &&
         (left_upper.Is(Type::Signed32()) || right_upper.Is(Type::Signed32()))) {
       VisitBinop<T>(node, UseInfo::TruncatingWord32(),
-                    MachineRepresentation::kWord32, Type::Signed32());
+                    MachineRepresentation::kWord32, restriction);
     } else {
       // If the output's truncation is identify-zeros, we can pass it
       // along. Moreover, if the operation is addition and we know the
@@ -1486,8 +1492,9 @@ class RepresentationSelector {
       UseInfo right_use = CheckedUseInfoAsWord32FromHint(hint, FeedbackSource(),
                                                          kIdentifyZeros);
       VisitBinop<T>(node, left_use, right_use, MachineRepresentation::kWord32,
-                    Type::Signed32());
+                    restriction);
     }
+
     if (lower<T>()) {
       if (truncation.IsUsedAsWord32() ||
           !CanOverflowSigned32(node->op(), left_feedback_type,
diff --git a/test/mjsunit/compiler/regress-1150649.js b/test/mjsunit/compiler/regress-1150649.js
new file mode 100644
index 00000000000..a193481a3a2
--- /dev/null
+++ b/test/mjsunit/compiler/regress-1150649.js
@@ -0,0 +1,24 @@
+// Copyright 2020 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Flags: --allow-natives-syntax
+
+function foo(a) {
+  var y = 0x7fffffff;  // 2^31 - 1
+
+  // Widen the static type of y (this condition never holds).
+  if (a == NaN) y = NaN;
+
+  // The next condition holds only in the warmup run. It leads to Smi
+  // (SignedSmall) feedback being collected for the addition below.
+  if (a) y = -1;
+
+  const z = (y + 1)|0;
+  return z < 0;
+}
+
+%PrepareFunctionForOptimization(foo);
+assertFalse(foo(true));
+%OptimizeFunctionOnNextCall(foo);
+assertTrue(foo(false));

there is a very comprehensive description of it:

+
+    // Using Signed32 as restriction type amounts to promising there won't be
+    // signed overflow. This is incompatible with relying on a Word32
+    // truncation in order to skip the overflow check.
+    Type const restriction =
+        truncation.IsUsedAsWord32() ? Type::Any() : Type::Signed32();
+

this bug has already been documented a lot so I will only briefly go through it.