CVE-2026-9968
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
AllowReindexScopesrc/ast/ast-function-literal-id-reindexer.h |
modified | |
AstFunctionLiteralIdReindexersrc/ast/ast-function-literal-id-reindexer.h |
modified | |
function_literal_id_src/parsing/expression-scope.h |
modified | |
allow_reindex_scope_src/parsing/expression-scope.h |
modified | |
ifsrc/parsing/parser-base.h |
modified |
Files Changed
src/ast/ast-function-literal-id-reindexer.ccsrc/ast/ast-function-literal-id-reindexer.hsrc/parsing/expression-scope.hsrc/parsing/parser-base.hsrc/parsing/parser.cc
Patch
From 89ba284081b3c2f96b612d2f78b79e370b0479f4 Mon Sep 17 00:00:00 2001
From: Toon Verwaest <verwaest@chromium.org>
Date: Mon, 11 May 2026 18:21:14 +0200
Subject: [PATCH] [parsing] Fix eval index overflow in bit-field
The parser now tracks the maximum potential drift that can accumulate
during arrow function parameter and computed member name reindexing.
This drift is accounted for when validating the `eval_scope_info_index`
to prevent overflows in the 20-bit field.
Enforced `AllowReindexScope` to guarantee that reindexing only happens
when properly accounted for.
TAG=agy
CONV=4cab861a-b43c-4c44-8f9b-82d0f4388a01
Bug: 506499280
Change-Id: I3f4a39b1c4600b964ecc0927a5808608e5cedd5b
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/7831209
Commit-Queue: Toon Verwaest <verwaest@chromium.org>
Reviewed-by: Igor Sheludko <ishell@chromium.org>
Cr-Commit-Position: refs/heads/main@{#107263}
---
diff --git a/src/ast/ast-function-literal-id-reindexer.cc b/src/ast/ast-function-literal-id-reindexer.cc
index e60afaa..99ff7ac 100644
--- a/src/ast/ast-function-literal-id-reindexer.cc
+++ b/src/ast/ast-function-literal-id-reindexer.cc
@@ -15,7 +15,8 @@
AstFunctionLiteralIdReindexer::~AstFunctionLiteralIdReindexer() = default;
-void AstFunctionLiteralIdReindexer::Reindex(Expression* pattern) {
+void AstFunctionLiteralIdReindexer::Reindex(Expression* pattern,
+ const AllowReindexScope& scope) {
#ifdef DEBUG
visited_.clear();
#endif
diff --git a/src/ast/ast-function-literal-id-reindexer.h b/src/ast/ast-function-literal-id-reindexer.h
index f7a41e0..8555f78 100644
--- a/src/ast/ast-function-literal-id-reindexer.h
+++ b/src/ast/ast-function-literal-id-reindexer.h
@@ -14,6 +14,19 @@
namespace v8 {
namespace internal {
+class AllowReindexScope {
+ public:
+ explicit AllowReindexScope(int* counter) : counter_(counter) {
+ if (counter_) (*counter_)++;
+ }
+ ~AllowReindexScope() {
+ if (counter_) (*counter_)--;
+ }
+
+ private:
+ int* counter_;
+};
+
// Changes the ID of all FunctionLiterals in the given Expression by adding the
// given delta.
class AstFunctionLiteralIdReindexer final
@@ -25,7 +38,7 @@
const AstFunctionLiteralIdReindexer&) = delete;
~AstFunctionLiteralIdReindexer();
- void Reindex(Expression* pattern);
+ void Reindex(Expression* pattern, const AllowReindexScope& scope);
// AstTraversalVisitor implementation.
void VisitFunctionLiteral(FunctionLiteral* lit);
diff --git a/src/parsing/expression-scope.h b/src/parsing/expression-scope.h
index 2a8a7f9..eee29e7 100644
--- a/src/parsing/expression-scope.h
+++ b/src/parsing/expression-scope.h
@@ -7,6 +7,7 @@
#include <utility>
+#include "src/ast/ast-function-literal-id-reindexer.h"
#include "src/ast/scopes.h"
#include "src/common/message-template.h"
#include "src/objects/function-kind.h"
@@ -334,6 +335,7 @@
return base::IsInRange(type_, kMaybeArrowParameterDeclaration,
kMaybeAsyncArrowParameterDeclaration);
}
+
bool IsCertainlyPattern() const { return IsCertainlyDeclaration(); }
bool CanBeParameterDeclaration() const {
return base::IsInRange(type_, kMaybeArrowParameterDeclaration,
@@ -761,7 +763,8 @@
kind == FunctionKind::kArrowFunction
? ExpressionScope<Types>::kMaybeArrowParameterDeclaration
: ExpressionScope<Types>::kMaybeAsyncArrowParameterDeclaration),
- function_literal_id_(function_literal_id) {
+ function_literal_id_(function_literal_id),
+ allow_reindex_scope_(&parser->max_drift_) {
DCHECK(kind == FunctionKind::kAsyncArrowFunction ||
kind == FunctionKind::kArrowFunction);
DCHECK(this->CanBeDeclaration());
@@ -852,6 +855,7 @@
int function_literal_id_;
bool has_simple_parameter_list_ = true;
bool uses_this_ = false;
+ AllowReindexScope allow_reindex_scope_;
};
} // namespace internal
diff --git a/src/parsing/parser-base.h b/src/parsing/parser-base.h
index 0cd2770..834cb59 100644
--- a/src/parsing/parser-base.h
+++ b/src/parsing/parser-base.h
@@ -746,6 +746,7 @@
bool is_private;
bool is_static;
bool is_rest;
+ std::optional<AllowReindexScope> allow_reindex_scope;
};
void DeclareLabel(ZonePtrList<const AstRawString>** labels,
@@ -1703,6 +1704,10 @@
return expression_scope_;
}
+ public:
+ V8_INLINE void set_max_drift(int drift) { max_drift_ = drift; }
+ V8_INLINE int max_drift() const { return max_drift_; }
+
bool MaybeParsingArrowhead() const {
return expression_scope_ != nullptr &&
expression_scope_->has_possible_arrow_parameter_in_scope_chain();
@@ -1766,6 +1771,7 @@
FuncNameInferrer fni_;
AstValueFactory* ast_value_factory_; // Not owned.
typename Types::Factory ast_node_factory_;
+ int max_drift_ = 0;
RuntimeCallStats* runtime_call_stats_;
internal::V8FileLogger* v8_file_logger_;
bool parsing_on_main_thread_;
@@ -1806,6 +1812,7 @@
DeclarationScope* scope = nullptr;
int function_literal_id = -1;
bool could_be_immediately_invoked = false;
+ const AllowReindexScope* allow_reindex_scope = nullptr;
bool HasInitialState() const { return scope == nullptr; }
@@ -1814,6 +1821,7 @@
function_literal_id = -1;
ClearStrictParameterError();
could_be_immediately_invoked = false;
+ allow_reindex_scope = nullptr;
DCHECK(HasInitialState());
}
@@ -2597,6 +2605,7 @@
prop_info->is_computed_name = true;
Consume(Token::kLeftBracket);
AcceptINScope scope(this, true);
+ prop_info->allow_reindex_scope.emplace(&max_drift_);
ExpressionT expression = ParseAssignmentExpression();
Expect(Token::kRightBracket);
if (prop_info->kind == ParsePropertyKind::kNotSet) {
@@ -2766,7 +2775,9 @@
if (!has_error() && next_info_id != PeekNextInfoId() &&
!(prop_info->is_static ? class_info->has_static_elements()
: class_info->has_instance_members())) {
- impl()->ReindexComputedMemberName(name_expression);
+ DCHECK(prop_info->allow_reindex_scope.has_value());
+ impl()->ReindexComputedMemberName(name_expression,
+ *prop_info->allow_reindex_scope);
}
} else {
CheckClassFieldName(prop_info->name, prop_info->is_static);
@@ -3348,7 +3359,8 @@
// not, we'll reindex the arrow function formal parameters to shift them all
// 1 down to make space for the arrow function.
if (function_literal_id != GetNextInfoId()) {
- impl()->ReindexArrowFunctionFormalParameters(¶meters);
+ AllowReindexScope dummy_scope(nullptr);
+ impl()->ReindexArrowFunctionFormalParameters(¶meters, dummy_scope);
}
expression = ParseArrowFunctionLiteral(parameters, function_literal_id,
@@ -4048,7 +4060,8 @@
int eval_scope_info_index = 0;
if (CheckPossibleEvalCall(result, is_optional, scope())) {
eval_scope_info_index = GetNextInfoId();
- if (!Call::EvalScopeInfoIndexField::is_valid(eval_scope_info_index)) {
+ if (!Call::EvalScopeInfoIndexField::is_valid(eval_scope_info_index +
+ max_drift_)) {
ReportMessage(MessageTemplate::kTooManyEvals);
return impl()->FailureExpression();
}
diff --git a/src/parsing/parser.cc b/src/parsing/parser.cc
index ad49c58..5e30c28 100644
--- a/src/parsing/parser.cc
+++ b/src/parsing/parser.cc
@@ -2781,13 +2781,13 @@
Regression Test / PoC
diff --git a/test/mjsunit/regress/regress-506499280-extended.js b/test/mjsunit/regress/regress-506499280-extended.js
new file mode 100644
index 0000000..b3d3cb1
--- /dev/null
+++ b/test/mjsunit/regress/regress-506499280-extended.js
@@ -0,0 +1,31 @@
+// 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: --fuzzing --disable-abortjs --disable-in-process-stack-traces
+
+const count = 1048573; // Pushes close to the 2^20 limit
+const filler = "()=>0;";
+
+// Helper to create a large string of functions
+function getFiller(n) {
+ return filler.repeat(n);
+}
+
+// Test case 1: Arrow function in parameter
+assertThrows(() => {
+ let s = getFiller(count) + "(x = eval('')) => {}";
+ new Function(s);
+}, SyntaxError, "Too many eval calls in script");
+
+// Test case 2: Nested arrow functions in parameters
+assertThrows(() => {
+ let s = getFiller(count - 1) + "(x = (y = eval('')) => {}) => {}";
+ new Function(s);
+}, SyntaxError, "Too many eval calls in script");
+
+// Test case 3: Computed member name in class
+assertThrows(() => {
+ let s = getFiller(count) + "class C { [(() => eval(''))()] = 1 }";
+ new Function(s);
+}, SyntaxError, "Too many eval calls in script");
diff --git a/test/mjsunit/regress/regress-506499280.js b/test/mjsunit/regress/regress-506499280.js
new file mode 100644
index 0000000..607f629
--- /dev/null
+++ b/test/mjsunit/regress/regress-506499280.js
@@ -0,0 +1,13 @@
+// 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: --fuzzing --disable-abortjs --disable-in-process-stack-traces
+
+const count = 1048573;
+const filler = "()=>0;";
+const s = filler.repeat(count) + "(x = eval('')) => {}";
+
+assertThrows(() => {
+ new Function(s);
+}, SyntaxError, "Too many eval calls in script");
Original Bug Report
CHECK failure: is_valid(value) in bit-field.h
Detailed Report: https://clusterfuzz.com/testcase?key=5005399816175616
Fuzzer: big_sleep Job Type: linux_asan_d8_dbg Platform Id: linux
Crash Type: CHECK failure Crash Address: Crash State: is_valid(value) in bit-field.h v8::internal::AstTraversalVisitorv8::internal::AstFunctionLiteralIdReindexer:: v8::internal::AstFunctionLiteralIdReindexer::Reindex
Sanitizer: address (ASAN)
Regressed: https://clusterfuzz.com/revisions?job=linux_asan_d8_dbg&range=95042:95043
Reproducer Testcase: https://clusterfuzz.com/download?testcase_id=5005399816175616
Issue filed automatically.
To reproduce this, please build the target in this report and run it against the reproducer testcase. Please use the GN arguments provided at bottom of this report when building the binary.
If you have trouble reproducing, please also export the environment variables listed under “[Environment]” in the crash stacktrace.
If you have any feedback on reproducing test cases, let us know at https://forms.gle/Yh3qCYFveHj6E5jz5 so we can improve.