Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactOut of bounds memory access in V8
DescriptionOut of bounds memory access in V8
ComponentV8
Bug ClassOOB
Tracker458914193
Fix commit978f2b8a73fd (v8/v8) +96/-29
CISA KEVNot listed
Credited@p1nky4745
Disclosed2026-01-13

Files Changed

  • src/ast/scopes.cc
  • src/ast/scopes.h
  • src/objects/call-site-info.cc
  • src/objects/function-kind.h
  • src/objects/shared-function-info.cc
  • src/parsing/parser-base.h
From 978f2b8a73fdc1c6d17fa5966dee81393e2f1533 Mon Sep 17 00:00:00 2001
From: Toon Verwaest <[email protected]>
Date: Tue, 25 Nov 2025 17:37:52 +0100
Subject: [PATCH] Fix class member initializer reparsing logic

Intertwined static / public member initializers can mix up ids, so unmix them.

Bug: 458914193
Change-Id: If0708b56750a92e03eaa5530cfbff295c2acf630
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/7203465
Reviewed-by: Leszek Swirski <[email protected]>
Commit-Queue: Toon Verwaest <[email protected]>
Auto-Submit: Toon Verwaest <[email protected]>
Cr-Commit-Position: refs/heads/main@{#103958}
---

diff --git a/src/ast/scopes.cc b/src/ast/scopes.cc
index c0438fe..b476498 100644
--- a/src/ast/scopes.cc
+++ b/src/ast/scopes.cc
@@ -1448,7 +1448,7 @@
   // Functions which force eager compilation and class member initializer
   // functions are not lazily compilable.
   return !force_eager_compilation_ &&
-         !IsClassMembersInitializerFunction(function_kind());
+         !IsClassInitializerFunction(function_kind());
 }
 
 int Scope::ContextChainLength(Scope* scope) const {
diff --git a/src/ast/scopes.h b/src/ast/scopes.h
index 4b0a708..2698b64 100644
--- a/src/ast/scopes.h
+++ b/src/ast/scopes.h
@@ -996,7 +996,7 @@
 #endif  // V8_ENABLE_WEBASSEMBLY
 
   bool should_ban_arguments() const {
-    return IsClassMembersInitializerFunction(function_kind());
+    return IsClassInitializerFunction(function_kind());
   }
 
   void set_module_has_toplevel_await() {
diff --git a/src/objects/call-site-info.cc b/src/objects/call-site-info.cc
index eb9137c..f0a390c 100644
--- a/src/objects/call-site-info.cc
+++ b/src/objects/call-site-info.cc
@@ -467,7 +467,7 @@
   DirectHandle<JSFunction> function(Cast<JSFunction>(info->function()),
                                     isolate);
   // Class members initializer function is not a method.
-  if (IsClassMembersInitializerFunction(function->shared()->kind())) {
+  if (IsClassInitializerFunction(function->shared()->kind())) {
     return isolate->factory()->null_value();
   }
 
diff --git a/src/objects/function-kind.h b/src/objects/function-kind.h
index 9a1a64f..148109a 100644
--- a/src/objects/function-kind.h
+++ b/src/objects/function-kind.h
@@ -58,11 +58,13 @@
   kConciseMethod,
   kStaticConciseMethod,
   kClassMembersInitializerFunction,
+  kClassMembersInitializerFunctionPrecededByStatic,
   kClassStaticInitializerFunction,
+  kClassStaticInitializerFunctionPrecededByMember,
   // END concise methods 2
   kInvalid,
 
-  kLastFunctionKind = kClassStaticInitializerFunction,
+  kLastFunctionKind = kClassStaticInitializerFunctionPrecededByMember,
 };
 
 constexpr int kFunctionKindBitSize = 5;
@@ -105,8 +107,9 @@
 inline bool IsConciseMethod(FunctionKind kind) {
   return base::IsInRange(kind, FunctionKind::kAsyncConciseMethod,
                          FunctionKind::kStaticAsyncConciseGeneratorMethod) ||
-         base::IsInRange(kind, FunctionKind::kConciseGeneratorMethod,
-                         FunctionKind::kClassStaticInitializerFunction);
+         base::IsInRange(
+             kind, FunctionKind::kConciseGeneratorMethod,
+             FunctionKind::kClassStaticInitializerFunctionPrecededByMember);
 }
 
 inline bool IsStrictFunctionWithoutPrototype(FunctionKind kind) {
@@ -114,8 +117,9 @@
                          FunctionKind::kAsyncArrowFunction) ||
          base::IsInRange(kind, FunctionKind::kAsyncConciseMethod,
                          FunctionKind::kStaticAsyncConciseGeneratorMethod) ||
-         base::IsInRange(kind, FunctionKind::kConciseGeneratorMethod,
-                         FunctionKind::kClassStaticInitializerFunction);
+         base::IsInRange(
+             kind, FunctionKind::kConciseGeneratorMethod,
+             FunctionKind::kClassStaticInitializerFunctionPrecededByMember);
 }
 
 inline bool IsGetterFunction(FunctionKind kind) {
@@ -153,9 +157,22 @@
                          FunctionKind::kDerivedConstructor);
 }
 
-inline bool IsClassMembersInitializerFunction(FunctionKind kind) {
-  return base::IsInRange(kind, FunctionKind::kClassMembersInitializerFunction,
-                         FunctionKind::kClassStaticInitializerFunction);
+inline bool IsClassInitializerFunction(FunctionKind kind) {
+  return base::IsInRange(
+      kind, FunctionKind::kClassMembersInitializerFunction,
+      FunctionKind::kClassStaticInitializerFunctionPrecededByMember);
+}
+
+inline bool IsClassInstanceInitializerFunction(FunctionKind kind) {
+  return base::IsInRange(
+      kind, FunctionKind::kClassMembersInitializerFunction,
+      FunctionKind::kClassMembersInitializerFunctionPrecededByStatic);
+}
+
+inline bool IsClassStaticInitializerFunction(FunctionKind kind) {
+  return base::IsInRange(
+      kind, FunctionKind::kClassStaticInitializerFunction,
+      FunctionKind::kClassStaticInitializerFunctionPrecededByMember);
 }
 
 inline bool IsConstructable(FunctionKind kind) {
@@ -172,6 +189,7 @@
     case FunctionKind::kStaticAsyncConciseMethod:
     case FunctionKind::kStaticAsyncConciseGeneratorMethod:
     case FunctionKind::kClassStaticInitializerFunction:
+    case FunctionKind::kClassStaticInitializerFunctionPrecededByMember:
       return true;
     default:
       return false;
@@ -217,6 +235,10 @@
       return "ClassMembersInitializerFunction";
     case FunctionKind::kClassStaticInitializerFunction:
       return "ClassStaticInitializerFunction";
+    case FunctionKind::kClassMembersInitializerFunctionPrecededByStatic:
+      return "ClassMembersInitializerFunctionPrecededByStatic";
+    case FunctionKind::kClassStaticInitializerFunctionPrecededByMember:
+      return "ClassStaticInitializerFunctionPrecededByMember";
     case FunctionKind::kDefaultBaseConstructor:
       return "DefaultBaseConstructor";
     case FunctionKind::kDefaultDerivedConstructor:
diff --git a/src/objects/shared-function-info.cc b/src/objects/shared-function-info.cc
index e8877a4..748cde9 100644
--- a/src/objects/shared-function-info.cc
+++ b/src/objects/shared-function-info.cc
@@ -344,8 +344,8 @@
   }
 #endif  // V8_ENABLE_WEBASSEMBLY
   FunctionKind function_kind = shared->kind();
-  if (IsClassMembersInitializerFunction(function_kind)) {
-    return function_kind == FunctionKind::kClassMembersInitializerFunction
+  if (IsClassInitializerFunction(function_kind)) {
+    return IsClassInstanceInitializerFunction(function_kind)
                ? isolate->factory()->instance_members_initializer_string()
                : isolate->factory()->static_initializer_string();
   }
diff --git a/src/parsing/parser-base.h b/src/parsing/parser-base.h
index dddf315..bf412c9 100644
--- a/src/parsing/parser-base.h
+++ b/src/parsing/parser-base.h
@@ -310,7 +310,10 @@
 
   void SkipInfos(int delta) { info_id_ += delta; }
 
-  void ResetInfoId() { info_id_ = 0; }
+  void ResetInfoId(int id = 0) {
+    DCHECK_LE(0, id);
+    info_id_ = id;
+  }
 
   // The Zone where the parsing outputs are stored.
   Zone* main_zone() const { return ast_value_factory()->single_parse_zone(); }
@@ -628,8 +631,11 @@
     DeclarationScope* EnsureStaticElementsScope(ParserBase* parser, int beg_pos,
                                                 int info_id) {
       if (!has_static_elements()) {
-        static_elements_scope = parser->NewFunctionScope(
-            FunctionKind::kClassStaticInitializerFunction);
+        FunctionKind kind =
+            has_instance_members()
+                ? FunctionKind::kClassStaticInitializerFunctionPrecededByMember
+                : FunctionKind::kClassStaticInitializerFunction;
+        static_elements_scope = parser->NewFunctionScope(kind);
         static_elements_scope->SetLanguageMode(LanguageMode::kStrict);
         static_elements_scope->set_start_position(beg_pos);
         static_elements_function_id = info_id;
@@ -643,8 +649,11 @@
     DeclarationScope* EnsureInstanceMembersScope(ParserBase* parser,
                                                  int beg_pos, int info_id) {
       if (!has_instance_members()) {
-        instance_members_scope = parser->NewFunctionScope(
-            FunctionKind::kClassMembersInitializerFunction);
+        FunctionKind kind =
+            has_static_elements()
+                ? FunctionKind::kClassMembersInitializerFunctionPrecededByStatic
+                : FunctionKind::kClassMembersInitializerFunction;
+        instance_members_scope = parser->NewFunctionScope(kind);
         instance_members_scope->SetLanguageMode(LanguageMode::kStrict);
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/test/unittests/objects/object-unittest.cc b/test/unittests/objects/object-unittest.cc
index e3c7d92..18032ea 100644
--- a/test/unittests/objects/object-unittest.cc
+++ b/test/unittests/objects/object-unittest.cc
@@ -575,6 +575,9 @@
     case FunctionKind::kAsyncConciseGeneratorMethod:
     case FunctionKind::kStaticAsyncConciseGeneratorMethod:
     case FunctionKind::kClassMembersInitializerFunction:
+    case FunctionKind::kClassMembersInitializerFunctionPrecededByStatic:
+    case FunctionKind::kClassStaticInitializerFunction:
+    case FunctionKind::kClassStaticInitializerFunctionPrecededByMember:
       return true;
     default:
       return false;
@@ -661,6 +664,9 @@
     case FunctionKind::kConciseMethod:
     case FunctionKind::kStaticConciseMethod:
     case FunctionKind::kClassMembersInitializerFunction:
+    case FunctionKind::kClassMembersInitializerFunctionPrecededByStatic:
+    case FunctionKind::kClassStaticInitializerFunction:
+    case FunctionKind::kClassStaticInitializerFunctionPrecededByMember:
       return false;
     default:
       return true;
Loading diff…

Original Bug Report

reported by [email protected]

Dcheck failure in fixed-array-inl.h

Description

Compilation error occurs with nested classes having a specific structure. The error manifests as an array bounds check violation in fixed-array-inl.h.

The error happens when attempting to access a WeakFixedArray element at index 6, which is out of bounds.

Reproduction Case

class C1 {}

const v1 = {
    n() {
        try { 
            this.n();
        } catch (e) {}

        class C2 {
            constructor() {
                class C3 extends C1 {
                    constructor() {}
                    a;
                    static {};
                    b;
                    static {
                        let x = 0;
                    }
                }
            }
        };

        new C2();
    }
};
let res = v1.n();

Tested on debug build without ASAN on Linux x64

Build flags:

is_debug = true
target_cpu = "x64"
v8_enable_backtrace = true
v8_static_library = true  
is_component_build = false
dcheck_always_on = true
v8_enable_disassembler = true
v8_enable_debugging_features=true
v8_dcheck_always_on = true
./out/debug/d8 poc.js


#
# Fatal error in ../../src/objects/fixed-array-inl.h, line 116
# Debug check failed: IsInBounds(index).
#
#
#
#FailureMessage Object: 0x7ffec77d5720
==== C stack trace ===============================

    ./out/debug/d8(v8::base::debug::StackTrace::StackTrace()+0x13) [0x55980cf3cbb3]
    ./out/debug/d8(+0x2909abd) [0x55980cf3babd]
    ./out/debug/d8(V8_Fatal(char const*, int, char const*, ...)+0x194) [0x55980cf33774]
    ./out/debug/d8(+0x2901025) [0x55980cf33025]
    ./out/debug/d8(void v8::internal::DeclarationScope::AllocateScopeInfos<v8::internal::Isolate>(v8::internal::ParseInfo*, v8::internal::DirectHandle<v8::internal::Script>, v8::internal::Isolate*)+0x715) [0x55980d153d05]
    ./out/debug/d8(+0x2ac01bf) [0x55980d0f21bf]
    ./out/debug/d8(v8::internal::Compiler::Compile(v8::internal::Isolate*, v8::internal::Handle<v8::internal::SharedFunctionInfo>, v8::internal::Compiler::ClearExceptionFlag, v8::internal::IsCompiledScope*, v8::internal::CreateSourcePositions)+0x88f) [0x55980d0f16cf]
    ./out/debug/d8(v8::internal::Compiler::Compile(v8::internal::Isolate*, v8::internal::DirectHandle<v8::internal::JSFunction>, v8::internal::Compiler::ClearExceptionFlag, v8::internal::IsCompiledScope*)+0x259) [0x55980d0f2909]
    ./out/debug/d8(+0x397500d) [0x55980dfa700d]
    ./out/debug/d8(v8::internal::Runtime_CompileLazy(int, unsigned long*, v8::internal::Isolate*)+0x84) [0x55980dfa6b14]
    ./out/debug/d8(+0x62fb67d) [0x55981092d67d]
Trace/breakpoint trap

Introduced

This vulnerability was introduced in: https://chromium.googlesource.com/v8/v8/+/a96a186d4d293bb9cc728eac23709b4e79dc358a (M141 Stable)

Proposed Fix

Fixing this proved to be quite challenging for me, but for a local fix I discovered that limiting the loop in DeclarationScope::AllocateScopeInfos to infos->length() prevents the crash:

diff --git a/src/ast/scopes.cc b/src/ast/scopes.cc
index e406e916459..b2ba7218be0 100644
--- a/src/ast/scopes.cc
+++ b/src/ast/scopes.cc
@@ -2803,7 +2803,7 @@ void DeclarationScope::AllocateScopeInfos(ParseInfo* parse_info,
     // reuse. Also look at the compiled function itself, and reuse its function
     // scope info if it exists.
     for (int i = parse_info->literal()->function_literal_id();
-         i <= parse_info->max_info_id(); ++i) {
+         i <= parse_info->max_info_id() && i < infos->length(); ++i) {
       Tagged<MaybeObject> maybe_info = infos->get(i);
       if (maybe_info.IsWeak()) {
         Tagged<Object> info = maybe_info.GetHeapObjectAssumeWeak();

Additionally, reverting the changes made in commit a96a186d4d293bb9cc728eac23709b4e79dc358a also prevents the crash.

CREDIT INFORMATION

Reporter credit: @p1nky4745

View on issue tracker