Chrome · V8
CVE-2026-17948
Type Confusion in V8
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
Btest/inspector/debugger/set-variable-value-class-synthetic.js |
modified | |
Ctest/inspector/debugger/set-variable-value-class-synthetic.js |
modified | |
footest/inspector/debugger/set-variable-value-class-synthetic.js |
modified | |
constructortest/inspector/debugger/set-variable-value-class-synthetic.js |
modified | |
runtest/inspector/debugger/set-variable-value-class-synthetic.js |
modified | |
fortest/inspector/debugger/set-variable-value-class-synthetic.js |
modified |
Files Changed
src/debug/debug-scopes.cctest/inspector/debugger/set-variable-value-class-synthetic-expected.txttest/inspector/debugger/set-variable-value-class-synthetic.js
Patch
From bee4f84e3b1258e903e26236ca0260f7bfbc2699 Mon Sep 17 00:00:00 2001 From: Simon Zünd <[email protected]> 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. [email protected] Fixed: 516849257 Change-Id: Id1c7352b70a26550b0853806f863b35eebace900 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/7984385 Reviewed-by: Philip Pfaffe <[email protected]> Commit-Queue: Simon Zünd <[email protected]> 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.
References
On This Page