Low chrome Type Confusion 🔧 Commit mapped

Overview

Low
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactType Confusion in V8
DescriptionType Confusion in V8
ComponentV8
Bug ClassType Confusion
Tracker516849257
Fix commitbee4f84e3b12 (v8/v8) +94/-6
CISA KEVNot listed
CreditedGoogle
Disclosed2026-07-29

Changed Functions

FunctionChangeNotes
B
test/inspector/debugger/set-variable-value-class-synthetic.js
modified
C
test/inspector/debugger/set-variable-value-class-synthetic.js
modified
foo
test/inspector/debugger/set-variable-value-class-synthetic.js
modified
constructor
test/inspector/debugger/set-variable-value-class-synthetic.js
modified
run
test/inspector/debugger/set-variable-value-class-synthetic.js
modified
for
test/inspector/debugger/set-variable-value-class-synthetic.js
modified

Files Changed

  • src/debug/debug-scopes.cc
  • test/inspector/debugger/set-variable-value-class-synthetic-expected.txt
  • test/inspector/debugger/set-variable-value-class-synthetic.js
From bee4f84e3b1258e903e26236ca0260f7bfbc2699 Mon Sep 17 00:00:00 2001
From: Simon Zünd <szuend@chromium.org>
Date: Tue, 23 Jun 2026 11:12:29 +0000
Subject: [PATCH] [debug] Disallow setVariableValue on synthetic context slots

Synthetic, compiler-introduced variables (e.g. ".brand",
".generator_object", "#method") are filtered from the locals that
ScopeIterator exposes to the debugger frontend, but the write side only
filtered private method/accessor modes. The class ".brand" slot has
VariableMode::kConst, so a Debugger.setVariableValue request naming
".brand" would overwrite the slot and the next constructor invocation
would use the bogus value as a property key when stamping the private
brand on the new instance.

Reject all synthetic variable names at the
ScopeIterator::SetVariableValue entry point so the read and write sides
agree, and drop the now-redundant per-mode check in
SetContextVariableValue.

R=pfaffe@chromium.org

Fixed: 516849257
Change-Id: Id1c7352b70a26550b0853806f863b35eebace900
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/7984385
Reviewed-by: Philip Pfaffe <pfaffe@chromium.org>
Commit-Queue: Simon Zünd <szuend@chromium.org>
Cr-Commit-Position: refs/heads/main@{#108170}
---

diff --git a/src/debug/debug-scopes.cc b/src/debug/debug-scopes.cc
index 5c2fe8b5..4624daf 100644
--- a/src/debug/debug-scopes.cc
+++ b/src/debug/debug-scopes.cc
@@ -696,6 +696,9 @@
                                      DirectHandle<Object> value) {
   DCHECK(!Done());
   name = isolate_->factory()->InternalizeString(name);
+  // Synthetic variables are compiler-introduced and not exposed to the user, so
+  // they may carry values outside the JSAny type and must not be overwritten.
+  if (ScopeInfo::VariableIsSynthetic(*name)) return false;
   switch (Type()) {
     case ScopeTypeGlobal:
     case ScopeTypeWith:
@@ -1216,13 +1219,8 @@
 
 bool ScopeIterator::SetContextVariableValue(DirectHandle<String> variable_name,
                                             DirectHandle<Object> new_value) {
-  VariableLookupResult lookup_result;
-  int slot_index =
-      context_->scope_info()->ContextSlotIndex(*variable_name, &lookup_result);
+  int slot_index = context_->scope_info()->ContextSlotIndex(*variable_name);
   if (slot_index < 0) return false;
-  if (IsPrivateMethodOrAccessorVariableMode(lookup_result.mode)) {
-    return false;
-  }
   Context::Set(context_, slot_index, new_value, isolate_);
   return true;
 }
diff --git a/test/inspector/debugger/set-variable-value-class-synthetic-expected.txt b/test/inspector/debugger/set-variable-value-class-synthetic-expected.txt
new file mode 100644
index 0000000..0b7135d
--- /dev/null
+++ b/test/inspector/debugger/set-variable-value-class-synthetic-expected.txt
@@ -0,0 +1,31 @@
+Checks that Debugger.setVariableValue cannot overwrite synthetic class context slots
+Class block scope found: true
+Setting .brand to "foo"
+{
+    error : {
+        code : -32603
+        message : Internal error
+    }
+    id : <messageId>
+}
+Own property names of inst:
+{
+    id : <messageId>
+    result : {
+        result : {
+            type : string
+            value : []
+        }
+    }
+}
+inst.run():
+{
+    id : <messageId>
+    result : {
+        result : {
+            description : 42
+            type : number
+            value : 42
+        }
+    }
+}
diff --git a/test/inspector/debugger/set-variable-value-class-synthetic.js b/test/inspector/debugger/set-variable-value-class-synthetic.js
new file mode 100644
index 0000000..ddf8c9f
--- /dev/null
+++ b/test/inspector/debugger/set-variable-value-class-synthetic.js
@@ -0,0 +1,59 @@
+// 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.
+
+const {session, contextGroup, Protocol} = InspectorTest.start(
+    'Checks that Debugger.setVariableValue cannot overwrite synthetic class context slots');
+
+const source = `
+class B {}
+class C extends B {
+  #m() { return 42; }
+  foo() { return C; }
+  constructor() { debugger; super(); }
+  run() { return this.#m(); }
+}
+globalThis.inst = new C();
+globalThis.inst.run();
+`;
+
+(async () => {
+  await Protocol.Debugger.enable();
+
+  const evalPromise = Protocol.Runtime.evaluate({expression: source});
+  const {params: {callFrames}} = await Protocol.Debugger.oncePaused();
+  const {callFrameId, scopeChain} = callFrames[0];
+
+  let scopeNumber = -1;
+  for (let i = 0; i < scopeChain.length; ++i) {
+    if (scopeChain[i].type !== 'block') continue;
+    const {result: {result}} = await Protocol.Runtime.getProperties(
+        {objectId: scopeChain[i].object.objectId});
+    if (result.some(p => p.name === 'C')) {
+      scopeNumber = i;
+      break;
+    }
+  }
+  InspectorTest.log('Class block scope found: ' + (scopeNumber >= 0));
+
+  InspectorTest.log('Setting .brand to "foo"');
+  InspectorTest.logMessage(await Protocol.Debugger.setVariableValue({
+    scopeNumber,
+    variableName: '.brand',
+    newValue: {value: 'foo'},
+    callFrameId,
+  }));
+
+  await Protocol.Debugger.resume();
+  await evalPromise;
+
+  InspectorTest.log('Own property names of inst:');
+  InspectorTest.logMessage(await Protocol.Runtime.evaluate(
+      {expression: 'JSON.stringify(Object.getOwnPropertyNames(globalThis.inst))'}));
+
+  InspectorTest.log('inst.run():');
+  InspectorTest.logMessage(await Protocol.Runtime.evaluate(
+      {expression: 'globalThis.inst.run()'}));
+
+  InspectorTest.completeTest();
+})();
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.