CVE-2026-0899
Overview
Files Changed
src/ast/scopes.ccsrc/ast/scopes.hsrc/objects/call-site-info.ccsrc/objects/function-kind.hsrc/objects/shared-function-info.ccsrc/parsing/parser-base.h
Patch
From 978f2b8a73fdc1c6d17fa5966dee81393e2f1533 Mon Sep 17 00:00:00 2001
From: Toon Verwaest <verwaest@chromium.org>
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 <leszeks@chromium.org>
Commit-Queue: Toon Verwaest <verwaest@chromium.org>
Auto-Submit: Toon Verwaest <verwaest@chromium.org>
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);
Regression Test / PoC
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;
Original Bug Report
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