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 <[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);
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