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 <szuend@chromium.org>
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).
R=bmeurer@chromium.org
Bug: 493631402
Change-Id: I18ec510a8288fd2a4bc44921ba538613ee8e1201
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/7690996
Reviewed-by: Benedikt Meurer <bmeurer@chromium.org>
Commit-Queue: Simon Zünd <szuend@chromium.org>
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 sa...@gmail.com
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