Chrome · V8 › Parser
CVE-2026-113192
Memory Corruption in V8 (Parser)
Overview
High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
TEST_Ftest/unittests/regress/regress-568831099-unittest.cc |
modified |
Files Changed
test/unittests/BUILD.gnsrc/parsing/parse-info.ccsrc/codegen/compiler.ccsrc/parsing/parser.ccsrc/objects/script.cctest/unittests/regress/regress-568831099-unittest.ccsrc/objects/shared-function-info.h
Patch
From 17f229f3ea0662f8fce3a77b1c1fcb2350748d7d Mon Sep 17 00:00:00 2001
From: Simon Zünd <szuend@chromium.org>
Date: Tue, 06 Oct 2026 05:26:19 +0000
Subject: [PATCH] [parser] Restore parse restriction when reparsing Function constructor top-level
Move the Script::CompilationKind::kFunctionConstructor check into
UnoptimizedCompileFlags::SetFlagsForFunctionFromScript (guarded by
is_toplevel()) so that top-level
UnoptimizedCompileFlags::ForFunctionCompile also sets
ONLY_SINGLE_FUNCTION_LITERAL.
Also use parsing_dynamic_function_declaration_ for the eager compile
hint in Parser::ParseFunctionLiteral, and check that syntax_kind and
kind match when matching an existing SharedFunctionInfo to a
FunctionLiteral.
R=verwaest@chromium.org
Fixed: 568831099
Change-Id: Ie918dad4ee60ccbeb3d403e293d4802ba8123f2a
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/8523457
Reviewed-by: Toon Verwaest <verwaest@chromium.org>
Commit-Queue: Simon Zünd <szuend@chromium.org>
Cr-Commit-Position: refs/heads/main@{#110345}
---
diff --git a/src/codegen/compiler.cc b/src/codegen/compiler.cc
index c64c573..6e4bf13 100644
--- a/src/codegen/compiler.cc
+++ b/src/codegen/compiler.cc
@@ -737,6 +737,8 @@
void UpdateSharedFunctionFlagsAfterCompilation(FunctionLiteral* literal) {
Tagged<SharedFunctionInfo> shared_info = *literal->shared_function_info();
DCHECK_EQ(shared_info->language_mode(), literal->language_mode());
+ DCHECK_EQ(shared_info->syntax_kind(), literal->syntax_kind());
+ DCHECK_EQ(shared_info->kind(), literal->kind());
// These fields are all initialised in ParseInfo from the SharedFunctionInfo,
// and then set back on the literal after parse. Hence, they should already
diff --git a/src/objects/script.cc b/src/objects/script.cc
index 64c2f26..1a24f18 100644
--- a/src/objects/script.cc
+++ b/src/objects/script.cc
@@ -140,6 +140,8 @@
CHECK_EQ(result->EndPosition(), function_literal->end_position());
CHECK_EQ(result->function_literal_id(kRelaxedLoad),
function_literal->function_literal_id());
+ CHECK_EQ(result->syntax_kind(), function_literal->syntax_kind());
+ CHECK_EQ(result->kind(), function_literal->kind());
function_literal->set_shared_function_info(result);
return result;
}
diff --git a/src/objects/shared-function-info.h b/src/objects/shared-function-info.h
index 8297f32..46ce635 100644
--- a/src/objects/shared-function-info.h
+++ b/src/objects/shared-function-info.h
@@ -828,13 +828,13 @@
void SetFunctionTokenPosition(int function_token_position,
int start_position);
- static void EnsureBytecodeArrayAvailable(
+ V8_EXPORT_PRIVATE static void EnsureBytecodeArrayAvailable(
Isolate* isolate, Handle<SharedFunctionInfo> shared_info,
IsCompiledScope* is_compiled_scope,
CreateSourcePositions flag = CreateSourcePositions{false});
inline bool CanCollectSourcePosition(Isolate* isolate);
- static void EnsureSourcePositionsAvailable(
+ V8_EXPORT_PRIVATE static void EnsureSourcePositionsAvailable(
Isolate* isolate, DirectHandle<SharedFunctionInfo> shared_info);
template <typename IsolateT>
diff --git a/src/parsing/parse-info.cc b/src/parsing/parse-info.cc
index 30db54d..f33708ab 100644
--- a/src/parsing/parse-info.cc
+++ b/src/parsing/parse-info.cc
@@ -74,10 +74,6 @@
: ScriptType::kClassic,
v8_flags.lazy);
flags.set_outer_language_mode(script->outer_language_mode());
- if (script->compilation_kind() ==
- Script::CompilationKind::kFunctionConstructor) {
- flags.set_parse_restriction(ONLY_SINGLE_FUNCTION_LITERAL);
- }
flags.SetFlagsForFunctionFromScript(script);
if (script->is_wrapped()) {
flags.set_function_syntax_kind(FunctionSyntaxKind::kWrapped);
@@ -158,6 +154,10 @@
set_block_coverage_enabled(block_coverage_enabled() &&
script->IsUserJavaScript());
+ if (is_toplevel() && script->compilation_kind() ==
+ Script::CompilationKind::kFunctionConstructor) {
+ set_parse_restriction(ONLY_SINGLE_FUNCTION_LITERAL);
+ }
}
ReusableUnoptimizedCompileState::ReusableUnoptimizedCompileState(
diff --git a/src/parsing/parser.cc b/src/parsing/parser.cc
index 05c5e161..c5898da 100644
--- a/src/parsing/parser.cc
+++ b/src/parsing/parser.cc
@@ -2835,13 +2835,11 @@
function_name = ast_value_factory()->empty_string();
}
- // This is true if we get here through CreateDynamicFunction.
- bool params_need_validation = parameters_end_pos_ != kNoSourcePosition;
int compile_hint_position = peek_position();
FunctionLiteral::EagerCompileHint eager_compile_hint =
function_state_->next_function_is_likely_called() || is_wrapped ||
- params_need_validation ||
+ parsing_dynamic_function_declaration_ ||
(info()->flags().compile_hints_magic_enabled() &&
scanner()->SawMagicCommentCompileHintsAll()) ||
(info()->flags().compile_hints_per_function_magic_enabled() &&
diff --git a/test/unittests/regress/regress-568831099-unittest.cc b/test/unittests/regress/regress-568831099-unittest.cc
new file mode 100644
index 0000000..26f523c
--- /dev/null
+++ b/test/unittests/regress/regress-568831099-unittest.cc
@@ -0,0 +1,94 @@
+// 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.
+
+#include "src/api/api-inl.h"
+#include "src/codegen/compiler.h"
+#include "src/execution/isolate-inl.h"
+#include "src/objects/feedback-vector-inl.h"
+#include "src/objects/js-function-inl.h"
+#include "src/objects/script-inl.h"
+#include "src/objects/shared-function-info-inl.h"
+#include "src/parsing/parse-info.h"
+#include "test/unittests/test-utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace v8 {
+namespace internal {
+
+using Regress568831099Test = TestWithContext;
+
+TEST_F(Regress568831099Test, ReparseFunctionConstructorToplevel) {
+ v8::HandleScope scope(isolate());
+
+ RunJS(
+ "var anonymous = 40;\n"
+ "var fn = new Function('a', 'if (a === 0) { anonymous; a.x } a.s = 1; "
+ "return a.s + anonymous;');\n"
+ "for (var i = 0; i < 20; i++) fn({s: 0});\n");
+
+ v8::Local<v8::Object> global = context()->Global();
+ DirectHandle<JSFunction> fn = Cast<JSFunction>(v8::Utils::OpenDirectHandle(
+ *global->Get(context(), NewString("fn")).ToLocalChecked()));
+
+ Handle<SharedFunctionInfo> inner_sfi(fn->shared(), i_isolate());
+ DirectHandle<Script> script(Cast<Script>(inner_sfi->script()), i_isolate());
+ EXPECT_EQ(script->compilation_kind(),
+ Script::CompilationKind::kFunctionConstructor);
+ EXPECT_EQ(inner_sfi->syntax_kind(), FunctionSyntaxKind::kAnonymousExpression);
+
+ const int expected_slot_count = inner_sfi->feedback_metadata()->slot_count();
+ ASSERT_TRUE(fn->has_feedback_vector());
+ EXPECT_EQ(static_cast<int>(fn->feedback_vector()->length().value()),
+ expected_slot_count);
+
+ Tagged<MaybeObject> maybe_toplevel =
+ script->infos()->get(kFunctionLiteralIdTopLevel);
+ Handle<SharedFunctionInfo> toplevel_sfi(
+ Cast<SharedFunctionInfo>(maybe_toplevel.GetHeapObjectAssumeWeak()),
+ i_isolate());
+ ASSERT_TRUE(toplevel_sfi->is_toplevel());
+
+ // Top-level compiles for kFunctionConstructor scripts must restore the
+ // ONLY_SINGLE_FUNCTION_LITERAL parse restriction, while inner function
+ // compiles must not.
+ UnoptimizedCompileFlags toplevel_flags =
+ UnoptimizedCompileFlags::ForFunctionCompile(i_isolate(), *toplevel_sfi);
+ EXPECT_EQ(toplevel_flags.parse_restriction(), ONLY_SINGLE_FUNCTION_LITERAL);
+
+ UnoptimizedCompileFlags inner_flags =
+ UnoptimizedCompileFlags::ForFunctionCompile(i_isolate(), *inner_sfi);
+ EXPECT_EQ(inner_flags.parse_restriction(), NO_PARSE_RESTRICTION);
+
+ // Exercise source position collection on the top-level SFI while compiled.
+ SharedFunctionInfo::EnsureSourcePositionsAvailable(i_isolate(), toplevel_sfi);
+
+ // Flush bytecode from both the top-level SFI and the inner function SFI,
+ // while `fn` keeps its FeedbackVector.
+ if (toplevel_sfi->CanDiscardCompiled()) {
+ SharedFunctionInfo::DiscardCompiled(i_isolate(), toplevel_sfi);
+ }
+ ASSERT_TRUE(inner_sfi->CanDiscardCompiled());
+ SharedFunctionInfo::DiscardCompiled(i_isolate(), inner_sfi);
+ ASSERT_FALSE(toplevel_sfi->is_compiled());
+ ASSERT_FALSE(inner_sfi->is_compiled());
+
+ // Recompiling the top-level SFI also eagerly recompiles the parenthesized
+ // `(function anonymous(...) { ... })` inner SFI. It must still be parsed as
+ // an anonymous dynamic function declaration rather than a named function
+ // expression.
+ IsCompiledScope is_compiled_scope;
+ SharedFunctionInfo::EnsureBytecodeArrayAvailable(i_isolate(), toplevel_sfi,
+ &is_compiled_scope,
+ CreateSourcePositions{true});
+ EXPECT_TRUE(toplevel_sfi->is_compiled());
+ EXPECT_TRUE(inner_sfi->is_compiled());
+ EXPECT_EQ(inner_sfi->syntax_kind(), FunctionSyntaxKind::kAnonymousExpression);
+ EXPECT_EQ(inner_sfi->feedback_metadata()->slot_count(), expected_slot_count);
+
+ v8::Local<v8::Value> result = RunJS("fn({s: 0})");
+ EXPECT_EQ(result->Int32Value(context()).FromJust(), 41);
+}
+
+} // namespace internal
+} // namespace v8
diff --git a/test/unittests/BUILD.gn b/test/unittests/BUILD.gn
index 316577f..fc1e850 100644
--- a/test/unittests/BUILD.gn
+++ b/test/unittests/BUILD.gn
@@ -508,6 +508,7 @@
"profiler/strings-storage-unittest.cc",
"regexp/regexp-unittest.cc",
"regress/regress-536271629-unittest.cc",
+ "regress/regress-568831099-unittest.cc",
"regress/regress-crbug-1041240-unittest.cc",
"regress/regress-crbug-1056054-unittest.cc",
"regress/regress-crbug-938251-unittest.cc",
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/test/unittests/BUILD.gn b/test/unittests/BUILD.gn
index 316577f..fc1e850 100644
--- a/test/unittests/BUILD.gn
+++ b/test/unittests/BUILD.gn
@@ -508,6 +508,7 @@
"profiler/strings-storage-unittest.cc",
"regexp/regexp-unittest.cc",
"regress/regress-536271629-unittest.cc",
+ "regress/regress-568831099-unittest.cc",
"regress/regress-crbug-1041240-unittest.cc",
"regress/regress-crbug-1056054-unittest.cc",
"regress/regress-crbug-938251-unittest.cc",
diff --git a/test/unittests/regress/regress-568831099-unittest.cc b/test/unittests/regress/regress-568831099-unittest.cc
new file mode 100644
index 0000000..26f523c
--- /dev/null
+++ b/test/unittests/regress/regress-568831099-unittest.cc
@@ -0,0 +1,94 @@
+// 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.
+
+#include "src/api/api-inl.h"
+#include "src/codegen/compiler.h"
+#include "src/execution/isolate-inl.h"
+#include "src/objects/feedback-vector-inl.h"
+#include "src/objects/js-function-inl.h"
+#include "src/objects/script-inl.h"
+#include "src/objects/shared-function-info-inl.h"
+#include "src/parsing/parse-info.h"
+#include "test/unittests/test-utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace v8 {
+namespace internal {
+
+using Regress568831099Test = TestWithContext;
+
+TEST_F(Regress568831099Test, ReparseFunctionConstructorToplevel) {
+ v8::HandleScope scope(isolate());
+
+ RunJS(
+ "var anonymous = 40;\n"
+ "var fn = new Function('a', 'if (a === 0) { anonymous; a.x } a.s = 1; "
+ "return a.s + anonymous;');\n"
+ "for (var i = 0; i < 20; i++) fn({s: 0});\n");
+
+ v8::Local<v8::Object> global = context()->Global();
+ DirectHandle<JSFunction> fn = Cast<JSFunction>(v8::Utils::OpenDirectHandle(
+ *global->Get(context(), NewString("fn")).ToLocalChecked()));
+
+ Handle<SharedFunctionInfo> inner_sfi(fn->shared(), i_isolate());
+ DirectHandle<Script> script(Cast<Script>(inner_sfi->script()), i_isolate());
+ EXPECT_EQ(script->compilation_kind(),
+ Script::CompilationKind::kFunctionConstructor);
+ EXPECT_EQ(inner_sfi->syntax_kind(), FunctionSyntaxKind::kAnonymousExpression);
+
+ const int expected_slot_count = inner_sfi->feedback_metadata()->slot_count();
+ ASSERT_TRUE(fn->has_feedback_vector());
+ EXPECT_EQ(static_cast<int>(fn->feedback_vector()->length().value()),
+ expected_slot_count);
+
+ Tagged<MaybeObject> maybe_toplevel =
+ script->infos()->get(kFunctionLiteralIdTopLevel);
+ Handle<SharedFunctionInfo> toplevel_sfi(
+ Cast<SharedFunctionInfo>(maybe_toplevel.GetHeapObjectAssumeWeak()),
+ i_isolate());
+ ASSERT_TRUE(toplevel_sfi->is_toplevel());
+
+ // Top-level compiles for kFunctionConstructor scripts must restore the
+ // ONLY_SINGLE_FUNCTION_LITERAL parse restriction, while inner function
+ // compiles must not.
+ UnoptimizedCompileFlags toplevel_flags =
+ UnoptimizedCompileFlags::ForFunctionCompile(i_isolate(), *toplevel_sfi);
+ EXPECT_EQ(toplevel_flags.parse_restriction(), ONLY_SINGLE_FUNCTION_LITERAL);
+
+ UnoptimizedCompileFlags inner_flags =
+ UnoptimizedCompileFlags::ForFunctionCompile(i_isolate(), *inner_sfi);
+ EXPECT_EQ(inner_flags.parse_restriction(), NO_PARSE_RESTRICTION);
+
+ // Exercise source position collection on the top-level SFI while compiled.
+ SharedFunctionInfo::EnsureSourcePositionsAvailable(i_isolate(), toplevel_sfi);
+
+ // Flush bytecode from both the top-level SFI and the inner function SFI,
+ // while `fn` keeps its FeedbackVector.
+ if (toplevel_sfi->CanDiscardCompiled()) {
+ SharedFunctionInfo::DiscardCompiled(i_isolate(), toplevel_sfi);
+ }
+ ASSERT_TRUE(inner_sfi->CanDiscardCompiled());
+ SharedFunctionInfo::DiscardCompiled(i_isolate(), inner_sfi);
+ ASSERT_FALSE(toplevel_sfi->is_compiled());
+ ASSERT_FALSE(inner_sfi->is_compiled());
+
+ // Recompiling the top-level SFI also eagerly recompiles the parenthesized
+ // `(function anonymous(...) { ... })` inner SFI. It must still be parsed as
+ // an anonymous dynamic function declaration rather than a named function
+ // expression.
+ IsCompiledScope is_compiled_scope;
+ SharedFunctionInfo::EnsureBytecodeArrayAvailable(i_isolate(), toplevel_sfi,
+ &is_compiled_scope,
+ CreateSourcePositions{true});
+ EXPECT_TRUE(toplevel_sfi->is_compiled());
+ EXPECT_TRUE(inner_sfi->is_compiled());
+ EXPECT_EQ(inner_sfi->syntax_kind(), FunctionSyntaxKind::kAnonymousExpression);
+ EXPECT_EQ(inner_sfi->feedback_metadata()->slot_count(), expected_slot_count);
+
+ v8::Local<v8::Value> result = RunJS("fn({s: 0})");
+ EXPECT_EQ(result->Int32Value(context()).FromJust(), 41);
+}
+
+} // namespace internal
+} // namespace v8
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