CVE-2025-13224
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
switchsrc/codegen/code-stub-assembler.cc |
modified | |
ifsrc/codegen/code-stub-assembler.cc |
modified |
Files Changed
src/codegen/code-stub-assembler.cc
Patch
From 62af07e96173fb03b504f60b01e67d9f2dda695b Mon Sep 17 00:00:00 2001
From: Igor Sheludko <ishell@chromium.org>
Date: Mon, 13 Oct 2025 14:22:20 +0200
Subject: [PATCH] [ic] Cleanup AccessorAssembler::CallGetterIfAccessor()
This CL
- reorders parameters to make |expected_receiver_mode| a mandatory
one and properly computed,
- makes sure we don't pass PropertyCell as a holder when JSReceiver is
expected.
Bug: 450328966
Change-Id: I921dfbd99245d01143600b4f4713fe602c817657
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/7036691
Commit-Queue: Igor Sheludko <ishell@chromium.org>
Reviewed-by: Leszek Swirski <leszeks@chromium.org>
Cr-Commit-Position: refs/heads/main@{#103085}
---
diff --git a/src/codegen/code-stub-assembler.cc b/src/codegen/code-stub-assembler.cc
index a8e0c85..b749310 100644
--- a/src/codegen/code-stub-assembler.cc
+++ b/src/codegen/code-stub-assembler.cc
@@ -11662,7 +11662,8 @@
var_value = CallGetterIfAccessor(
value_or_accessor, object, var_details.value(), context,
- object, next_key, &slow_load, kCallJSGetterUseCachedName);
+ object, kExpectingJSReceiver, next_key, &slow_load,
+ kCallJSGetterUseCachedName);
Goto(&value_ready);
BIND(&slow_load);
@@ -12158,15 +12159,11 @@
TNode<SwissNameDictionary> dictionary, TNode<IntPtrT> name_index,
TVariable<Uint32T>* var_details, TVariable<Object>* var_value);
-// |value| is the property backing store's contents, which is either a value or
-// an accessor pair, as specified by |details|. |holder| is a JSReceiver or a
-// PropertyCell. Returns either the original value, or the result of the getter
-// call.
TNode<Object> CodeStubAssembler::CallGetterIfAccessor(
- TNode<Object> value, TNode<Union<JSReceiver, PropertyCell>> holder,
+ TNode<Object> value, std::optional<TNode<JSReceiver>> holder,
TNode<Uint32T> details, TNode<Context> context, TNode<JSAny> receiver,
- TNode<Object> name, Label* if_bailout, GetOwnPropertyMode mode,
- ExpectedReceiverMode expected_receiver_mode) {
+ ExpectedReceiverMode expected_receiver_mode, TNode<Object> name,
+ Label* if_bailout, GetOwnPropertyMode mode) {
TVARIABLE(Object, var_value, value);
Label done(this), if_accessor_info(this, Label::kDeferred);
@@ -12207,44 +12204,51 @@
BIND(&if_function_template_info);
{
- Label use_cached_property(this);
- TNode<HeapObject> cached_property_name = LoadObjectField<HeapObject>(
- getter, FunctionTemplateInfo::kCachedPropertyNameOffset);
+ if (holder.has_value()) {
+ Label use_cached_property(this);
+ TNode<HeapObject> cached_property_name = LoadObjectField<HeapObject>(
+ getter, FunctionTemplateInfo::kCachedPropertyNameOffset);
- Label* has_cached_property = mode == kCallJSGetterUseCachedName
- ? &use_cached_property
- : if_bailout;
- GotoIfNot(IsTheHole(cached_property_name), has_cached_property);
+ Label* has_cached_property = mode == kCallJSGetterUseCachedName
+ ? &use_cached_property
+ : if_bailout;
+ GotoIfNot(IsTheHole(cached_property_name), has_cached_property);
- TNode<JSReceiver> js_receiver;
- switch (expected_receiver_mode) {
- case kExpectingJSReceiver:
- js_receiver = CAST(receiver);
- break;
- case kExpectingAnyReceiver:
- // TODO(ishell): in case the function template info has a signature
- // and receiver is not a JSReceiver the signature check in
- // CallFunctionTemplate builtin will fail anyway, so we can short
- // cut it here and throw kIllegalInvocation immediately.
- js_receiver = ToObject_Inline(context, receiver);
- break;
- }
- TNode<JSReceiver> holder_receiver = CAST(holder);
- TNode<NativeContext> creation_context =
- GetCreationContext(holder_receiver, if_bailout);
- TNode<Context> caller_context = context;
- var_value = CallBuiltin(
- Builtin::kCallFunctionTemplate_Generic, creation_context, getter,
- Int32Constant(i::JSParameterCount(0)), caller_context, js_receiver);
- Goto(&done);
-
- if (mode == kCallJSGetterUseCachedName) {
- Bind(&use_cached_property);
-
- var_value =
- GetProperty(context, holder_receiver, cached_property_name);
-
+ TNode<JSReceiver> js_receiver;
+ switch (expected_receiver_mode) {
+ case kExpectingJSReceiver:
+ js_receiver = CAST(receiver);
+ break;
+ case kExpectingAnyReceiver:
+ // TODO(ishell): in case the function template info has a
+ // signature and receiver is not a JSReceiver the signature check
+ // in CallFunctionTemplate builtin will fail anyway, so we can
+ // short cut it here and throw kIllegalInvocation immediately.
+ js_receiver = ToObject_Inline(context, receiver);
+ break;
+ }
+ TNode<JSReceiver> holder_receiver = *holder;
+ TNode<NativeContext> creation_context =
+ GetCreationContext(holder_receiver, if_bailout);
+ TNode<Context> caller_context = context;
+ var_value = CallBuiltin(Builtin::kCallFunctionTemplate_Generic,
+ creation_context, getter,
+ Int32Constant(i::JSParameterCount(0)),
+ caller_context, js_receiver);
Goto(&done);
+
+ if (mode == kCallJSGetterUseCachedName) {
+ Bind(&use_cached_property);
+
+ var_value =
+ GetProperty(context, holder_receiver, cached_property_name);
+
+ Goto(&done);
+ }
+ } else {
+ // |holder| must be available in order to handle lazy AccessorPair
+ // case (we need it for computing the function's context).
+ Unreachable();
}
}
} else {
@@ -12256,56 +12260,61 @@
// AccessorInfo case.
BIND(&if_accessor_info);
{
- TNode<AccessorInfo> accessor_info = CAST(value);
- Label if_array(this), if_function(this), if_wrapper(this);
+ if (holder.has_value()) {
+ TNode<AccessorInfo> accessor_info = CAST(value);
+ Label if_array(this), if_function(this), if_wrapper(this);
+ // Dispatch based on {holder} instance type.
+ TNode<Map> holder_map = LoadMap(*holder);
+ TNode<Uint16T> holder_instance_type = LoadMapInstanceType(holder_map);
+ GotoIf(IsJSArrayInstanceType(holder_instance_type), &if_array);
+ GotoIf(IsJSFunctionInstanceType(holder_instance_type), &if_function);
+ Branch(IsJSPrimitiveWrapperInstanceType(holder_instance_type),
+ &if_wrapper, if_bailout);
- // Dispatch based on {holder} instance type.
- TNode<Map> holder_map = LoadMap(holder);
- TNode<Uint16T> holder_instance_type = LoadMapInstanceType(holder_map);
- GotoIf(IsJSArrayInstanceType(holder_instance_type), &if_array);
- GotoIf(IsJSFunctionInstanceType(holder_instance_type), &if_function);
- Branch(IsJSPrimitiveWrapperInstanceType(holder_instance_type), &if_wrapper,
- if_bailout);
+ // JSArray AccessorInfo case.
+ BIND(&if_array);
+ {
+ // We only deal with the "length" accessor on JSArray.
+ GotoIfNot(IsLengthString(LoadObjectField(accessor_info,
+ AccessorInfo::kNameOffset)),
+ if_bailout);
+ TNode<JSArray> array = CAST(*holder);
+ var_value = LoadJSArrayLength(array);
+ Goto(&done);
+ }
- // JSArray AccessorInfo case.
- BIND(&if_array);
- {
- // We only deal with the "length" accessor on JSArray.
- GotoIfNot(IsLengthString(
- LoadObjectField(accessor_info, AccessorInfo::kNameOffset)),
- if_bailout);
- TNode<JSArray> array = CAST(holder);
- var_value = LoadJSArrayLength(array);
- Goto(&done);
- }
+ // JSFunction AccessorInfo case.
+ BIND(&if_function);
+ {
+ // We only deal with the "prototype" accessor on JSFunction here.
+ GotoIfNot(IsPrototypeString(LoadObjectField(accessor_info,
+ AccessorInfo::kNameOffset)),
+ if_bailout);
- // JSFunction AccessorInfo case.
- BIND(&if_function);
- {
- // We only deal with the "prototype" accessor on JSFunction here.
- GotoIfNot(IsPrototypeString(
- LoadObjectField(accessor_info, AccessorInfo::kNameOffset)),
Original Bug Report
V8: Type Confusion in LoadSuperIC
We are tracking this issue with the public ID BIGSLEEP-450458037. Please use this identifier for reference in any future communication.
Vulnerability Details
There is a type confusion bug in the handling of super property loads with accessors.
The function CodeStubAssembler::CallGetterIfAccessor [1] is used for example by ICs to invoke a getter function for a property. One of its parameters, expected_receiver_mode, indicates if the function can assume the receiver to be a JSReceiver or if it may need to perform a ToObject conversion. By default, the value is kExpectingJSReceiver [2] and so the function will assume that the receiver is always a JSReceiver instance. As such, all callers to this function that don’t pass this parameter need to guarantee that this is the case.
One place where this goes wrong is in the handling of super property accesses, specifically in the LoadSuperIC [3]. Here, the (generic) property lookup will not be performed starting from the receiver, but instead start at the lookup_start_object [4], which will be the prototype of the instance type of the current class. However, in case of an accessor function, the accessor will be invoked with the actual receiver [5]. As such, it becomes possible for the lookup to find and invoke an accessor function with an invalid receiver (e.g. a Smi).
The below testcase demonstrates this and causes a DCHECK failure in debug builds and a segmentation fault in release builds.
[1] https://source.chromium.org/chromium/chromium/src/+/main:v8/src/codegen/code-stub-assembler.cc;l=12166;drc=693f06700fd9b978f690957cde985a98463a0d87
[2] https://source.chromium.org/chromium/chromium/src/+/main:v8/src/codegen/code-stub-assembler.h;l=4651;drc=693f06700fd9b978f690957cde985a98463a0d87
[3] https://source.chromium.org/chromium/chromium/src/+/main:v8/src/ic/accessor-assembler.cc;l=3404;drc=693f06700fd9b978f690957cde985a98463a0d87
[4] https://source.chromium.org/chromium/chromium/src/+/main:v8/src/ic/accessor-assembler.cc;l=3415;drc=693f06700fd9b978f690957cde985a98463a0d87
[5] https://source.chromium.org/chromium/chromium/src/+/main:v8/src/codegen/code-stub-assembler.cc;l=12223;drc=693f06700fd9b978f690957cde985a98463a0d87
Affected Version(s)
The issue has been successfully reproduced:
- at HEAD (commit 1370b0bd0727045ac742c2a8bec77ccb3fdcc105)
- in stable release 14.1.146.11 (commit ad8af0fc661d278e87627fcaa3a7cf795ee80dd8)
Reproduction
Test Case
// Create an Error instance. Error instances have an own property 'stack'
// which is an accessor backed by a FunctionTemplateInfo (API accessor).
const err = new Error();
class B {
m() {
// Access 'stack' via super.
// The lookup starts at the prototype of B.prototype.
return super.stack;
}
}
// Set the prototype of B.prototype to the Error instance.
// Now, the super lookup will start at 'err'.
Object.setPrototypeOf(B.prototype, err);
const b = new B();
// Call the method m with a primitive receiver.
// This triggers LoadSuperIC and ultimately calls CallGetterIfAccessor with a
// Smi as receiver and the default kExpectingJSReceiver mode.
b.m.call(0x4141414 >> 1);
Build Instructions
Follow the instructions at https://v8.dev/docs/build. The crash was verified on a debug build:
gm.py x64.debug
Command
./out/x64.debug/d8 crash.js
ASan Report
#
# Fatal error in ../../src/objects/object-type.cc, line 87
# Type cast failed in CAST(receiver) at ../../src/codegen/code-stub-assembler.cc:12223
Expected JSReceiver but found Smi: 0x20a0a0a (34212362)
#
#
#
#FailureMessage Object: 0x7b8dd8b89060
==== C stack trace ===============================
./out/x64.debug/d8(___interceptor_backtrace+0x46) [0x55806b2944c6]
v8/v8/out/x64.debug/libv8_libbase.so(v8::base::debug::StackTrace::StackTrace()+0x1e) [0x7f8df467192e]
v8/v8/out/x64.debug/libv8_libplatform.so(+0x7e0fb) [0x7f8ddc7ba0fb]
v8/v8/out/x64.debug/libv8_libbase.so(V8_Fatal(char const*, int, char const*, ...)+0x2e2) [0x7f8df4617052]
v8/v8/out/x64.debug/libv8.so(v8::internal::CheckObjectType(unsigned long, unsigned long, unsigned long)+0x10ed5) [0x7f8dea2bfc85]
[0x7b8d7c8e911d]
Reporter Credit
Google Big Sleep
Disclosure Policy
This bug is subject to a 90-day disclosure deadline. If a fix for this issue is made available to users before the end of the 90-day deadline, this bug report will become public 30 days after the fix was made available. Otherwise, this bug report will become public at the deadline. The scheduled deadline is 2026-01-07. For more information, visit https://goo.gle/bigsleep
- https://goo.gle/bigsleep
- https://source.chromium.org/chromium/chromium/src/+/main:v8/src/codegen/code-stub-assembler.cc;l=12166;drc=693f06700fd9b978f690957cde985a98463a0d87
- https://source.chromium.org/chromium/chromium/src/+/main:v8/src/codegen/code-stub-assembler.cc;l=12223;drc=693f06700fd9b978f690957cde985a98463a0d87
- https://source.chromium.org/chromium/chromium/src/+/main:v8/src/codegen/code-stub-assembler.h;l=4651;drc=693f06700fd9b978f690957cde985a98463a0d87
- https://source.chromium.org/chromium/chromium/src/+/main:v8/src/ic/accessor-assembler.cc;l=3404;drc=693f06700fd9b978f690957cde985a98463a0d87
- https://source.chromium.org/chromium/chromium/src/+/main:v8/src/ic/accessor-assembler.cc;l=3415;drc=693f06700fd9b978f690957cde985a98463a0d87
- https://v8.dev/docs/build