High chrome Integer Overflow 📄 Reporter bug report 🔧 Commit mapped

Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInteger overflow in V8
DescriptionInteger overflow in V8
ComponentV8
Bug ClassInteger Overflow
Tracker506499280
Fix commit89ba284081b3 (v8/v8) +94/-15
CISA KEVNot listed
CreditedGoogle
Disclosed2026-05-27

Changed Functions

FunctionChangeNotes
AllowReindexScope
src/ast/ast-function-literal-id-reindexer.h
modified
AstFunctionLiteralIdReindexer
src/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
if
src/parsing/parser-base.h
modified

Files Changed

  • src/ast/ast-function-literal-id-reindexer.cc
  • src/ast/ast-function-literal-id-reindexer.h
  • src/parsing/expression-scope.h
  • src/parsing/parser-base.h
  • src/parsing/parser.cc
From 89ba284081b3c2f96b612d2f78b79e370b0479f4 Mon Sep 17 00:00:00 2001
From: Toon Verwaest <[email protected]>
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 <[email protected]>
Reviewed-by: Igor Sheludko <[email protected]>
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(&parameters);
+      AllowReindexScope dummy_scope(nullptr);
+      impl()->ReindexArrowFunctionFormalParameters(&parameters, 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 @@
Loading diff…

Regression Test / PoC

shipped with the fix
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");
Loading diff…

Original Bug Report

reported by [email protected]

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.

View on issue tracker