From ccd648eb0cf0bbf1fee0ffa9a6fc91f9101ce508 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Fl=C3=BCckiger?= Date: Thu, 6 Aug 2026 07:57:24 +0000 Subject: [PATCH] deps: V8: backport b36cf9f6ccd2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Original commit message: [api] Prepare DisallowJavascriptExecution scope around api callbacks In `v8::Isolate::RequestInterrupt` the API contract says: API interrupt callbacks are forbidden from executing JavaScript on the interrupted Isolate. This CL prepares the infrastructure to enforce this contract. Bug: 540137721, 40067940 TAG=agy CONV=78573798-a951-4bd3-9c6f-3e3b6775da4b Change-Id: I43cc83c3cc3ebd34052a927dde55bbf4d49d2d29 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/8173727 Auto-Submit: Olivier Flückiger Reviewed-by: Simon Zünd Commit-Queue: Olivier Flückiger Cr-Commit-Position: refs/heads/main@{#109086} Refs: https://github.com/v8/v8/commit/b36cf9f6ccd2931d1d0bde6a9f366d30667e4b75 Signed-off-by: deepak1556 PR-URL: https://github.com/nodejs/node/pull/65397 Reviewed-By: Joyee Cheung Reviewed-By: Chengzhong Wu Reviewed-By: Juan José Arboleda Reviewed-By: James M Snell Reviewed-By: Shelley Vohr --- deps/v8/BUILD.bazel | 6 ++++ deps/v8/BUILD.gn | 11 ++++++ deps/v8/src/common/assert-scope.h | 2 +- deps/v8/src/execution/isolate.cc | 7 ++++ deps/v8/src/inspector/v8-debugger.cc | 1 + deps/v8/src/inspector/v8-inspector-impl.cc | 1 + .../inspector/v8-inspector-session-impl.cc | 2 ++ deps/v8/src/inspector/v8-regex.cc | 2 ++ deps/v8/test/cctest/test-api.cc | 20 +++++++++++ deps/v8/test/cctest/test-regexp.cc | 8 ++++- .../debugger/debug/futex-reentrant-wait.js | 35 +++++++++++++++++++ 11 files changed, 93 insertions(+), 2 deletions(-) create mode 100644 deps/v8/test/debugger/debug/futex-reentrant-wait.js diff --git a/deps/v8/BUILD.bazel b/deps/v8/BUILD.bazel index f28fea4e8aa..53c06e3cecc 100644 --- a/deps/v8/BUILD.bazel +++ b/deps/v8/BUILD.bazel @@ -148,6 +148,11 @@ v8_flag(name = "v8_enable_trace_maps") v8_flag(name = "v8_enable_v8_checks") +v8_flag( + name = "v8_disallow_js_in_api_interrupts_is_checked", + default = True, +) + v8_flag(name = "v8_enable_verify_csa") v8_flag(name = "v8_enable_verify_heap") @@ -496,6 +501,7 @@ v8_config( "v8_android_log_stdout": "V8_ANDROID_LOG_STDOUT", "v8_code_comments": "V8_CODE_COMMENTS", "v8_deprecation_warnings": "V8_DEPRECATION_WARNINGS", + "v8_disallow_js_in_api_interrupts_is_checked": "V8_DISALLOW_JS_IN_API_INTERRUPTS_IS_CHECKED", "v8_imminent_deprecation_warnings": "V8_IMMINENT_DEPRECATION_WARNINGS", "v8_enable_debug_code": "V8_ENABLE_DEBUG_CODE", "v8_enable_disassembler": "ENABLE_DISASSEMBLER", diff --git a/deps/v8/BUILD.gn b/deps/v8/BUILD.gn index e81430fbc39..ea335df9034 100644 --- a/deps/v8/BUILD.gn +++ b/deps/v8/BUILD.gn @@ -59,6 +59,10 @@ declare_args() { # Enable compiler warnings when using V8_DEPRECATED apis. v8_deprecation_warnings = true + # Check that executing JavaScript inside API interrupt callbacks is + # disallowed. + v8_disallow_js_in_api_interrupts_is_checked = true + # Enable compiler warnings when using V8_DEPRECATE_SOON apis. v8_imminent_deprecation_warnings = true @@ -999,6 +1003,7 @@ external_v8_defines = [ "V8_COMPRESS_ZONES", "V8_ENABLE_SANDBOX", "V8_DEPRECATION_WARNINGS", + "V8_DISALLOW_JS_IN_API_INTERRUPTS_IS_CHECKED", "V8_IMMINENT_DEPRECATION_WARNINGS", "V8_USE_PERFETTO", "V8_USE_PERFETTO_JSON_EXPORT", @@ -1047,6 +1052,10 @@ if (v8_enable_sandbox) { if (v8_deprecation_warnings) { enabled_external_v8_defines += [ "V8_DEPRECATION_WARNINGS" ] } +if (v8_disallow_js_in_api_interrupts_is_checked) { + enabled_external_v8_defines += + [ "V8_DISALLOW_JS_IN_API_INTERRUPTS_IS_CHECKED" ] +} if (v8_imminent_deprecation_warnings) { enabled_external_v8_defines += [ "V8_IMMINENT_DEPRECATION_WARNINGS" ] } @@ -3093,6 +3102,8 @@ generated_file("v8_generate_features_json") { output_conversion = "json" contents = { v8_deprecation_warnings = v8_deprecation_warnings + v8_disallow_js_in_api_interrupts_is_checked = + v8_disallow_js_in_api_interrupts_is_checked v8_enable_31bit_smis_on_64bit_arch = v8_enable_31bit_smis_on_64bit_arch v8_enable_direct_handle = v8_enable_direct_handle v8_enable_extensible_ro_snapshot = v8_enable_extensible_ro_snapshot diff --git a/deps/v8/src/common/assert-scope.h b/deps/v8/src/common/assert-scope.h index cf82be0bc8e..494d330b333 100644 --- a/deps/v8/src/common/assert-scope.h +++ b/deps/v8/src/common/assert-scope.h @@ -110,7 +110,7 @@ class V8_NODISCARD PerThreadAssertScope ScopeType& operator=(const ScopeType&) = delete; \ V8_EXPORT_PRIVATE ~ScopeType(); \ \ - static bool IsAllowed(Isolate* isolate); \ + V8_EXPORT_PRIVATE static bool IsAllowed(Isolate* isolate); \ \ V8_EXPORT_PRIVATE static void Open(Isolate* isolate, \ bool* was_execution_allowed); \ diff --git a/deps/v8/src/execution/isolate.cc b/deps/v8/src/execution/isolate.cc index 06fa14bf6b1..e86eabdbf07 100644 --- a/deps/v8/src/execution/isolate.cc +++ b/deps/v8/src/execution/isolate.cc @@ -2245,6 +2245,13 @@ void Isolate::InvokeApiInterruptCallbacks() { } VMState state(this); HandleScope handle_scope(this); + // API interrupt callbacks are forbidden from executing JavaScript on the + // interrupted Isolate (see v8::Isolate::RequestInterrupt contract in + // v8-isolate.h: "Registered |callback| must not reenter interrupted + // Isolate."). +#ifdef V8_DISALLOW_JS_IN_API_INTERRUPTS_IS_CHECKED + DisallowJavascriptExecution no_js(this); +#endif // V8_DISALLOW_JS_IN_API_INTERRUPTS_IS_CHECKED entry.first(reinterpret_cast(this), entry.second); } } diff --git a/deps/v8/src/inspector/v8-debugger.cc b/deps/v8/src/inspector/v8-debugger.cc index 69fb293610b..747abefcedd 100644 --- a/deps/v8/src/inspector/v8-debugger.cc +++ b/deps/v8/src/inspector/v8-debugger.cc @@ -532,6 +532,7 @@ void V8Debugger::handleProgramBreak( } }); { + v8::Isolate::AllowJavascriptExecutionScope allow_script(m_isolate); v8::Context::Scope scope(pausedContext); m_inspector->forEachSession( diff --git a/deps/v8/src/inspector/v8-inspector-impl.cc b/deps/v8/src/inspector/v8-inspector-impl.cc index a4610e0d9b3..f085f7f72ec 100644 --- a/deps/v8/src/inspector/v8-inspector-impl.cc +++ b/deps/v8/src/inspector/v8-inspector-impl.cc @@ -96,6 +96,7 @@ int V8InspectorImpl::resolveUniqueContextId( v8::MaybeLocal V8InspectorImpl::compileAndRunInternalScript( v8::Local context, v8::Local source) { + v8::Isolate::AllowJavascriptExecutionScope allow_script(m_isolate); v8::Local unboundScript; if (!v8::debug::CompileInspectorScript(m_isolate, source) .ToLocal(&unboundScript)) diff --git a/deps/v8/src/inspector/v8-inspector-session-impl.cc b/deps/v8/src/inspector/v8-inspector-session-impl.cc index 95ae57032b3..38d8dd93ea4 100644 --- a/deps/v8/src/inspector/v8-inspector-session-impl.cc +++ b/deps/v8/src/inspector/v8-inspector-session-impl.cc @@ -355,6 +355,8 @@ void V8InspectorSessionImpl::reportAllContexts(V8RuntimeAgentImpl* agent) { } void V8InspectorSessionImpl::dispatchProtocolMessage(StringView message) { + v8::Isolate::AllowJavascriptExecutionScope allow_script( + m_inspector->isolate()); KeepSessionAliveScope keepAlive(*this); using v8_crdtp::span; diff --git a/deps/v8/src/inspector/v8-regex.cc b/deps/v8/src/inspector/v8-regex.cc index 4adf347843f..09c308f84d3 100644 --- a/deps/v8/src/inspector/v8-regex.cc +++ b/deps/v8/src/inspector/v8-regex.cc @@ -38,6 +38,7 @@ V8Regex::V8Regex(V8InspectorImpl* inspector, const String16& pattern, v8::Local regex; // Protect against reentrant debugger calls via interrupts. v8::debug::PostponeInterruptsScope no_interrupts(m_inspector->isolate()); + v8::Isolate::AllowJavascriptExecutionScope allow_js(m_inspector->isolate()); if (v8::RegExp::New(context, toV8String(isolate, pattern), static_cast(flags)) .ToLocal(®ex)) @@ -69,6 +70,7 @@ int V8Regex::match(const String16& string, int startFrom, v8::MicrotasksScope::kDoNotRunMicrotasks); // Protect against reentrant debugger calls via interrupts. v8::debug::PostponeInterruptsScope no_interrupts(m_inspector->isolate()); + v8::Isolate::AllowJavascriptExecutionScope allow_js(m_inspector->isolate()); v8::TryCatch tryCatch(isolate); v8::Local regex = m_regex.Get(isolate); diff --git a/deps/v8/test/cctest/test-api.cc b/deps/v8/test/cctest/test-api.cc index ba7e079f411..9b5fdd3cfd9 100644 --- a/deps/v8/test/cctest/test-api.cc +++ b/deps/v8/test/cctest/test-api.cc @@ -22306,6 +22306,26 @@ TEST(RequestInterruptSmallScripts) { CHECK(interrupt_was_called); } +#ifdef V8_DISALLOW_JS_IN_API_INTERRUPTS_IS_CHECKED +static bool interrupt_check_no_js = false; +void DisallowJsInterruptCallback(v8::Isolate* isolate, void* data) { + CHECK(!i::AllowJavascriptExecution::IsAllowed( + reinterpret_cast(isolate))); + interrupt_check_no_js = true; +} + +TEST(RequestInterruptDisallowsJavascript) { + LocalContext env; + v8::Isolate* isolate = CcTest::isolate(); + v8::HandleScope scope(isolate); + + interrupt_check_no_js = false; + isolate->RequestInterrupt(&DisallowJsInterruptCallback, nullptr); + CompileRun("(function(x){return x;})(1);"); + CHECK(interrupt_check_no_js); +} +#endif // V8_DISALLOW_JS_IN_API_INTERRUPTS_IS_CHECKED + static v8::Global function_new_expected_env_global; static void FunctionNewCallback(const v8::FunctionCallbackInfo& info) { v8::Isolate* isolate = info.GetIsolate(); diff --git a/deps/v8/test/cctest/test-regexp.cc b/deps/v8/test/cctest/test-regexp.cc index 946695833c6..08be47b221a 100644 --- a/deps/v8/test/cctest/test-regexp.cc +++ b/deps/v8/test/cctest/test-regexp.cc @@ -124,7 +124,13 @@ class InterruptTest { CHECK(string->ContainsOnlyOneByte()); // Internalize the subject by using it as a computed property name in an // object. - CompileRun("o = { [subject_string]: 'foo' }"); + { + // This test is technically wrong for running JS in a C++ interrupt. + // However we know that the interuptee here is the regexp engine, which + // does not care. + Isolate::AllowJavascriptExecutionScope allow_script(isolate); + CompileRun("o = { [subject_string]: 'foo' }"); + } CHECK(string->IsOneByte()); } diff --git a/deps/v8/test/debugger/debug/futex-reentrant-wait.js b/deps/v8/test/debugger/debug/futex-reentrant-wait.js new file mode 100644 index 00000000000..fc82beac534 --- /dev/null +++ b/deps/v8/test/debugger/debug/futex-reentrant-wait.js @@ -0,0 +1,35 @@ +// 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: --allow-natives-syntax + +var Debug = debug.Debug; +var exception = null; + +Debug.setListener(function (event, exec_state, event_data, data) { + if (event == Debug.DebugEvent.Break) { + try { + var sab2 = new SharedArrayBuffer(4); + var i32a2 = new Int32Array(sab2); + Atomics.wait(i32a2, 0, 0, 10); + } catch (e) { + exception = e; + } + } +}); + +let sab = new SharedArrayBuffer(4); +let i32a = new Int32Array(sab); + +let timeout = { + valueOf: function() { + %ScheduleBreak(); + return 10; + } +}; + +Atomics.wait(i32a, 0, 0, timeout); + +assertNotNull(exception); +assertTrue(exception.message.includes("cannot be called in this context"));