High chrome Input Validation 🔧 Commit mapped

Overview

High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactImproper state validation in DevTools
DescriptionImproper state validation in DevTools
ComponentDevTools
Sub-componentDebug
Bug ClassInput Validation
Tracker566404364
Fix commit7075c436f401 (v8/v8) +61/-15
CISA KEVNot listed
CreditedGoogle
Disclosed2026-10-06

Changed Functions

FunctionChangeNotes
if
src/debug/debug.cc
modified
while
test/inspector/debugger/pause-on-oom-in-eval.js
modified
eval
test/inspector/debugger/pause-on-oom-in-eval.js
modified

Files Changed

  • test/inspector/debugger/pause-on-oom-in-eval.js
  • test/inspector/debugger/pause-on-oom-in-eval-expected.txt
  • src/debug/debug-scopes.cc
  • src/debug/debug.h
  • src/debug/debug.cc
From 7075c436f40149aabd8b9ab8ad187f260450a1f4 Mon Sep 17 00:00:00 2001
From: Simon Zünd <[email protected]>
Date: Wed, 30 Sep 2026 11:03:20 +0000
Subject: [PATCH] [debug] Step into function when OOM break fires at function entry

When the inspector's near-heap-limit callback requests an OOM break and
the interrupt fires at the function-entry stack check of a function or
eval, the function's context has not been created and pushed yet.
Pausing there makes the ScopeIterator observe an inconsistent context
chain and trips a DCHECK.

Handle kOOM breaks at function entry the same way as scheduled breaks:
step into the function and pause at the first breakable position after
the prologue. The original break reasons are preserved across the step
so the pause is still reported as OOM.

Also extend the ScopeIterator::NeedsContext invariant check to eval
scopes.

Fixed: 566404364
Change-Id: I8b58cd0cb8bdeab4385b4d68182642ab53e31df5
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/8480873
Reviewed-by: Yang Guo <[email protected]>
Auto-Submit: Simon Zünd <[email protected]>
Commit-Queue: Simon Zünd <[email protected]>
Cr-Commit-Position: refs/heads/main@{#110193}
---
diff --git a/src/debug/debug-scopes.cc b/src/debug/debug-scopes.cc
index e2d3ab8..efb7922 100644
--- a/src/debug/debug-scopes.cc
+++ b/src/debug/debug-scopes.cc
@@ -274,7 +274,9 @@
   // context should the function need one. In that case the function has already
   // pushed the context and we are good.
   CHECK_IMPLIES(needs_context && current_scope_index_ == closure_scope_index_ &&
-                    current_scope().is_function_scope() && !function_.is_null(),
+                    (current_scope().is_function_scope() ||
+                     current_scope().is_eval_scope()) &&
+                    !function_.is_null(),
                 function_->context() != *context_);
 
   return needs_context;
diff --git a/src/debug/debug.cc b/src/debug/debug.cc
index adac5a7..def10a5 100644
--- a/src/debug/debug.cc
+++ b/src/debug/debug.cc
@@ -514,7 +514,7 @@
   base::Relaxed_Store(&thread_local_.current_debug_scope_,
                       static_cast<base::AtomicWord>(0));
   thread_local_.break_on_next_function_call_ = false;
-  thread_local_.scheduled_break_on_next_function_call_ = false;
+  thread_local_.scheduled_break_on_next_function_call_ = {};
   UpdateHookOnFunctionCall();
   thread_local_.muted_function_ = Smi::zero();
   thread_local_.muted_position_ = -1;
@@ -722,8 +722,9 @@
   if (!break_points_hit.is_null() || break_on_next_function_call() ||
       scheduled_break) {
     StepAction lastStepAction = last_step_action();
-    debug::BreakReasons break_reasons;
-    if (scheduled_break) {
+    debug::BreakReasons break_reasons =
+        thread_local_.scheduled_break_on_next_function_call_;
+    if (shouldPauseAfterInstrumentation) {
       break_reasons.Add(debug::BreakReason::kScheduled);
     }
     // If it's a debugger statement, add the reason and then mute the location
@@ -1710,7 +1711,7 @@
   thread_local_.last_frame_count_ = -1;
   thread_local_.target_frame_count_ = -1;
   thread_local_.break_on_next_function_call_ = false;
-  thread_local_.scheduled_break_on_next_function_call_ = false;
+  thread_local_.scheduled_break_on_next_function_call_ = {};
   clear_restart_frame();
   UpdateHookOnFunctionCall();
 }
@@ -2936,14 +2937,15 @@
             *function, LazyDeoptimizeReason::kDebugger, frame->LookupCode());
       }
 
-      // kScheduled breaks are triggered by the stack check. While we could
-      // pause here, the JSFunction didn't have time yet to create and push
-      // it's context. Instead, we step into the function and pause at the
+      // kScheduled and kOOM breaks are triggered by the stack check. While we
+      // could pause here, the JSFunction didn't have time yet to create and
+      // push its context. Instead, we step into the function and pause at the
       // first official breakable position.
       // This behavior mirrors "BreakOnNextFunctionCall".
-      if (break_reasons.contains(v8::debug::BreakReason::kScheduled) &&
+      if ((break_reasons.contains(v8::debug::BreakReason::kScheduled) ||
+           break_reasons.contains(v8::debug::BreakReason::kOOM)) &&
           BreakLocation::IsPausedInJsFunctionEntry(frame)) {
-        thread_local_.scheduled_break_on_next_function_call_ = true;
+        thread_local_.scheduled_break_on_next_function_call_.Add(break_reasons);
         PrepareStepIn(function);
         return;
       }
diff --git a/src/debug/debug.h b/src/debug/debug.h
index 7907de4..4a09380 100644
--- a/src/debug/debug.h
+++ b/src/debug/debug.h
@@ -458,7 +458,7 @@
   }
 
   bool scheduled_break_on_function_call() const {
-    return thread_local_.scheduled_break_on_next_function_call_;
+    return !thread_local_.scheduled_break_on_next_function_call_.empty();
   }
 
   bool IsRestartFrameScheduled() const {
@@ -670,10 +670,10 @@
     // debugger to break on next function call.
     bool break_on_next_function_call_;
 
-    // This flag is true when we break via stack check (BreakReason::kScheduled)
-    // We don't stay paused there but instead "step in" to the function similar
-    // to what "BreakOnNextFunctionCall" does.
-    bool scheduled_break_on_next_function_call_;
+    // Non-empty when we break via stack check (BreakReason::kScheduled or
+    // BreakReason::kOOM). We don't stay paused there but instead "step in" to
+    // the function similar to what "BreakOnNextFunctionCall" does.
+    v8::debug::BreakReasons scheduled_break_on_next_function_call_;
 
     // Frame ID for the frame that needs to be restarted. StackFrameId::NO_ID
     // otherwise. The unwinder uses the id to restart execution in this frame
diff --git a/test/inspector/debugger/pause-on-oom-in-eval-expected.txt b/test/inspector/debugger/pause-on-oom-in-eval-expected.txt
new file mode 100644
index 0000000..cddc096
--- /dev/null
+++ b/test/inspector/debugger/pause-on-oom-in-eval-expected.txt
@@ -0,0 +1,2 @@
+Tests that an OOM break triggered at the entry of a top-level strict eval pauses inside the eval
+reason: OOM
diff --git a/test/inspector/debugger/pause-on-oom-in-eval.js b/test/inspector/debugger/pause-on-oom-in-eval.js
new file mode 100644
index 0000000..dbbfb20
--- /dev/null
+++ b/test/inspector/debugger/pause-on-oom-in-eval.js
@@ -0,0 +1,40 @@
+// 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: --max-old-space-size=16
+
+const {session, contextGroup, Protocol} = InspectorTest.start(
+    'Tests that an OOM break triggered at the entry of a top-level strict ' +
+    'eval pauses inside the eval');
+
+Protocol.Debugger.onPaused(async message => {
+  InspectorTest.log(`reason: ${message.params.reason}`);
+  await Protocol.Debugger.evaluateOnCallFrame({
+    callFrameId: message.params.callFrames[0].callFrameId,
+    expression: 'arr = []; stop = true;'
+  });
+  Protocol.Debugger.resume();
+});
+
+(async function test() {
+  await Protocol.Debugger.enable();
+  // The loop body only allocates before entering the eval, so the OOM
+  // interrupt requested by the near-heap-limit callback fires at the
+  // function-entry stack check of the eval, i.e. before the eval context is
+  // pushed. The eval is called from the top level of the script so that the
+  // current context is the script context, whose ScopeInfo shares its unique
+  // ID with the eval scope.
+  await Protocol.Runtime.evaluate({
+    expression: `
+      'use strict';
+      let arr = [];
+      let stop = false;
+      while (!stop) {
+        arr.push(new Array(10000).fill(0));
+        eval('let x = 1; function inner() { return x; }');
+      }
+      //# sourceURL=test.js`
+  });
+  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.