Chrome · V8
CVE-2026-7940
UAF in V8
Overview
Medium
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
forsrc/inspector/v8-debugger-agent-impl.cc |
modified | |
ifsrc/inspector/v8-debugger-agent-impl.cc |
modified | |
m_isLiveEditsrc/inspector/v8-debugger-script.cc |
modified |
Files Changed
src/inspector/v8-debugger-agent-impl.ccsrc/inspector/v8-debugger-agent-impl.hsrc/inspector/v8-debugger-script.ccsrc/inspector/v8-debugger-script.hsrc/inspector/v8-debugger.cctest/inspector/debugger/set-breakpoint-by-url-in-broken-script-expected.txt
Patch
From 606a11949ee0fecbb3cae68b0191814b6efb5cb1 Mon Sep 17 00:00:00 2001 From: Simon Zünd <[email protected]> Date: Mon, 23 Mar 2026 11:43:12 +0000 Subject: [PATCH] [inspector] Don't emit scriptFailedToParse on setBreakpoint Setting breakpoints triggers re-compilation if V8 hasn't compiled the script yet or the GC evicted it. If the top-level compilation fails, the inspector receives another 'scriptFailedToParse' event. The "real" fix would be to forward a "CLEAR_EXCEPTION" bit into 'CompileTopLevel' in V8. For now, we prevent setting breakpoints on broken scripts, they can't be run in any case. Drive-by: Catch exceptions when setting breakpoints in case there are other ways where we re-enter V8 (e.g. via the regex). [email protected] Bug: 493631402 Change-Id: I18ec510a8288fd2a4bc44921ba538613ee8e1201 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/7690996 Reviewed-by: Benedikt Meurer <[email protected]> Commit-Queue: Simon Zünd <[email protected]> Cr-Commit-Position: refs/heads/main@{#105967} --- diff --git a/src/inspector/v8-debugger-agent-impl.cc b/src/inspector/v8-debugger-agent-impl.cc index 2e78b58..b39dfea 100644 --- a/src/inspector/v8-debugger-agent-impl.cc +++ b/src/inspector/v8-debugger-agent-impl.cc @@ -446,7 +446,7 @@ std::vector<std::unique_ptr<V8DebuggerScript>> compiledScripts = m_debugger->getCompiledScripts(m_session->contextGroupId(), this); for (auto& script : compiledScripts) { - didParseSource(std::move(script), true); + didParseSource(std::move(script)); } m_breakpointsActive = m_state->booleanProperty( @@ -1073,6 +1073,7 @@ ScriptsMap::iterator scriptIterator = m_scripts.find(scriptId); if (scriptIterator == m_scripts.end()) return nullptr; V8DebuggerScript* script = scriptIterator->second.get(); + if (script->hadCompileError()) return nullptr; v8::debug::BreakpointId debuggerBreakpointId; v8::debug::Location location(lineNumber, columnNumber); @@ -1083,6 +1084,7 @@ { v8::Context::Scope contextScope(inspected->context()); + v8::TryCatch tryCatch(m_isolate); if (!script->setBreakpoint(condition, &location, &debuggerBreakpointId)) { return nullptr; } @@ -1956,9 +1958,9 @@ } // namespace void V8DebuggerAgentImpl::didParseSource( - std::unique_ptr<V8DebuggerScript> script, bool success) { + std::unique_ptr<V8DebuggerScript> script) { v8::HandleScope handles(m_isolate); - if (!success) { + if (script->hadCompileError()) { String16 scriptSource = script->source(0); script->setSourceURL(findSourceURL(scriptSource, false)); script->setSourceMappingURL(findSourceMapURL(scriptSource, false)); @@ -2025,7 +2027,7 @@ ? stack->buildInspectorObjectImpl(m_debugger, 0) : nullptr; - if (!success) { + if (scriptRef->hadCompileError()) { m_frontend.scriptFailedToParse( scriptId, scriptURL, scriptRef->startLine(), scriptRef->startColumn(), scriptRef->endLine(), scriptRef->endColumn(), contextId, diff --git a/src/inspector/v8-debugger-agent-impl.h b/src/inspector/v8-debugger-agent-impl.h index 7c81ced..c71e9ab 100644 --- a/src/inspector/v8-debugger-agent-impl.h +++ b/src/inspector/v8-debugger-agent-impl.h @@ -182,7 +182,7 @@ v8::debug::ExceptionType exceptionType, bool isUncaught, v8::debug::BreakReasons breakReasons); void didContinue(); - void didParseSource(std::unique_ptr<V8DebuggerScript>, bool success); + void didParseSource(std::unique_ptr<V8DebuggerScript>); bool isFunctionBlackboxed(const String16& scriptId, const v8::debug::Location& start, diff --git a/src/inspector/v8-debugger-script.cc b/src/inspector/v8-debugger-script.cc index 5d65d7a..05f8107 100644 --- a/src/inspector/v8-debugger-script.cc +++ b/src/inspector/v8-debugger-script.cc @@ -42,13 +42,15 @@ V8DebuggerScript::V8DebuggerScript(v8::Isolate* isolate, v8::Local<v8::debug::Script> script, - bool isLiveEdit, V8DebuggerAgentImpl* agent, + bool hadCompileError, bool isLiveEdit, + V8DebuggerAgentImpl* agent, V8InspectorClient* client) : m_id(String16::fromInteger(script->Id())), m_url(GetScriptURL(isolate, script, client)), m_isolate(isolate), m_embedderName(GetScriptName(isolate, script, client)), m_agent(agent), + m_hadCompileError(hadCompileError), m_isLiveEdit(isLiveEdit) { Initialize(script); } diff --git a/src/inspector/v8-debugger-script.h b/src/inspector/v8-debugger-script.h index d8f5e1d..107b870 100644 --- a/src/inspector/v8-debugger-script.h +++ b/src/inspector/v8-debugger-script.h @@ -53,8 +53,8 @@ enum class Language { JavaScript, WebAssembly }; V8DebuggerScript(v8::Isolate* isolate, v8::Local<v8::debug::Script> script, - bool isLiveEdit, V8DebuggerAgentImpl* agent, - V8InspectorClient* client); + bool hadCompileError, bool isLiveEdit, + V8DebuggerAgentImpl* agent, V8InspectorClient* client); ~V8DebuggerScript() = default; V8DebuggerScript(const V8DebuggerScript&) = delete; V8DebuggerScript& operator=(const V8DebuggerScript&) = delete; @@ -76,6 +76,7 @@ int endColumn() const { return m_endColumn; } int codeOffset() const; int executionContextId() const { return m_executionContextId; } + bool hadCompileError() const { return m_hadCompileError; } bool isLiveEdit() const { return m_isLiveEdit; } bool isModule() const { return m_isModule; } int length() const; @@ -132,6 +133,7 @@ String16 m_sourceMappingURL; mutable String16 m_buildId; Language m_language; + bool m_hadCompileError = false; bool m_isLiveEdit = false; bool m_isModule = false; mutable String16 m_hash; diff --git a/src/inspector/v8-debugger.cc b/src/inspector/v8-debugger.cc index 72e5f49..faa2463 100644 --- a/src/inspector/v8-debugger.cc +++ b/src/inspector/v8-debugger.cc @@ -169,7 +169,7 @@ if (m_inspector->contextGroupId(contextId) != contextGroupId) continue; } result.push_back(std::make_unique<V8DebuggerScript>( - m_isolate, script, false, agent, m_inspector->client())); + m_isolate, script, false, false, agent, m_inspector->client())); } return result; } @@ -604,10 +604,8 @@ client](V8InspectorSessionImpl* session) { auto agent = session->debuggerAgent(); if (!agent->enabled()) return; - agent->didParseSource( - std::make_unique<V8DebuggerScript>(isolate, script, is_live_edited, - agent, client), - !has_compile_error); + agent->didParseSource(std::make_unique<V8DebuggerScript>( + isolate, script, has_compile_error, is_live_edited, agent, client)); }); } diff --git a/test/inspector/debugger/set-breakpoint-by-url-in-broken-script-expected.txt b/test/inspector/debugger/set-breakpoint-by-url-in-broken-script-expected.txt new file mode 100644 index 0000000..e0f9392 --- /dev/null +++ b/test/inspector/debugger/set-breakpoint-by-url-in-broken-script-expected.txt @@ -0,0 +1,21 @@ +Check that setBreakpointByUrl in a broken script doesn't trigger scriptFailedToParse +{ + method : Debugger.scriptFailedToParse + params : { + buildId : + embedderName : foo.js + endColumn : 1 + endLine : 0 + executionContextId : <executionContextId> + hasSourceURL : false + hash : 021fb596db81e6d02bf3d2586ee3981fe519f275c0ac9ca76bbcf2ebb4097d96 + isModule : false + length : 1 + scriptId : <scriptId> + scriptLanguage : JavaScript + sourceMapURL : + startColumn : 0 + startLine : 0 + url : foo.js + } +} diff --git a/test/inspector/debugger/set-breakpoint-by-url-in-broken-script.js b/test/inspector/debugger/set-breakpoint-by-url-in-broken-script.js new file mode 100644
Loading diff…
Original Bug Report
reported by [email protected]
heap-use-after-free in v8_inspector::V8DebuggerAgentImpl::setBreakpointByUrl
VULNERABILITY DETAILS UAF in v8_inspector::V8DebuggerAgentImpl::setBreakpointByUrl
VERSION Chrome Version: 148.0.7728.0(Developer Build) Operating System: Ubuntu
REPRODUCTION CASE
- put manifest.json/background.js into the extension_path
- run the command: ./chrome –user-data-dir=./noexist –no-sandbox –load-extension=“extension_path”
FOR CRASHES, PLEASE INCLUDE THE FOLLOWING ADDITIONAL INFORMATION Type of crash: tab Crash State: see asan.log file
Please note that this bug is very similar to https://issues.chromium.org/u/2/issues/40063469.
CREDIT INFORMATION Externally reported security bugs may appear in Chrome release notes. If this bug is included, how would you like to be credited? Reporter credit: sakana
References
On This Page