High chrome Memory Corruption 🔧 Commit mapped

Overview

High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactMemory corruption in V8
DescriptionMemory corruption in V8
ComponentV8
Sub-componentParser
Bug ClassMemory Corruption
Tracker568831099
Fix commit17f229f3ea06 (v8/v8) +106/-9
CISA KEVNot listed
CreditedXinyang Ge
Disclosed2026-10-20

Changed Functions

FunctionChangeNotes
TEST_F
test/unittests/regress/regress-568831099-unittest.cc
modified

Files Changed

  • test/unittests/BUILD.gn
  • src/parsing/parse-info.cc
  • src/codegen/compiler.cc
  • src/parsing/parser.cc
  • src/objects/script.cc
  • test/unittests/regress/regress-568831099-unittest.cc
  • src/objects/shared-function-info.h
From 17f229f3ea0662f8fce3a77b1c1fcb2350748d7d Mon Sep 17 00:00:00 2001
From: Simon Zünd <[email protected]>
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.

[email protected]

Fixed: 568831099
Change-Id: Ie918dad4ee60ccbeb3d403e293d4802ba8123f2a
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/8523457
Reviewed-by: Toon Verwaest <[email protected]>
Commit-Queue: Simon Zünd <[email protected]>
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.