Chrome · V8
CVE-2025-13223
Type Confusion in V8
Overview
High
Severity
—
CVSS
Yes
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
forsrc/compiler/js-native-context-specialization.cc |
modified |
Files Changed
src/compiler/access-builder.ccsrc/compiler/access-builder.hsrc/compiler/js-native-context-specialization.ccsrc/compiler/turboshaft/turbolev-early-lowering-reducer-inl.h
Patch
From 4cf9311810b0561ee8b532a694526c7904e18367 Mon Sep 17 00:00:00 2001 From: Leszek Swirski <[email protected]> Date: Wed, 12 Nov 2025 16:46:01 +0100 Subject: [PATCH] [compiler] Preserve field repr in property array extension Walk the descriptor array in lockstep with the property array when extending the latter. Fixed: 460017370 Change-Id: If0b4fc3c5f62fc0cc373588cbddc3c0a95c7225c Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/7146166 Commit-Queue: Leszek Swirski <[email protected]> Reviewed-by: Nico Hartmann <[email protected]> Reviewed-by: Igor Sheludko <[email protected]> Cr-Commit-Position: refs/heads/main@{#103674} --- diff --git a/src/compiler/access-builder.cc b/src/compiler/access-builder.cc index d8f618f..ad6172b 100644 --- a/src/compiler/access-builder.cc +++ b/src/compiler/access-builder.cc @@ -4,6 +4,8 @@ #include "src/compiler/access-builder.h" +#include "src/codegen/machine-type.h" +#include "src/compiler/property-access-builder.h" #include "src/compiler/type-cache.h" #include "src/handles/handles-inl.h" #include "src/objects/arguments.h" @@ -1070,12 +1072,16 @@ } // static -FieldAccess AccessBuilder::ForPropertyArraySlot(int index) { +FieldAccess AccessBuilder::ForPropertyArraySlot(int index, + Representation representation) { int offset = PropertyArray::OffsetOfElementAt(index); - FieldAccess access = {kTaggedBase, offset, - Handle<Name>(), OptionalMapRef(), - Type::Any(), MachineType::AnyTagged(), - kFullWriteBarrier, "PropertyArraySlot"}; + MachineType machine_type = + representation.IsHeapObject() || representation.IsDouble() + ? MachineType::TaggedPointer() + : MachineType::AnyTagged(); + FieldAccess access = { + kTaggedBase, offset, Handle<Name>(), OptionalMapRef(), + Type::Any(), machine_type, kFullWriteBarrier, "PropertyArraySlot"}; return access; } diff --git a/src/compiler/access-builder.h b/src/compiler/access-builder.h index 8e7d9cd..4feaefc 100644 --- a/src/compiler/access-builder.h +++ b/src/compiler/access-builder.h @@ -11,6 +11,7 @@ #include "src/compiler/write-barrier-kind.h" #include "src/objects/elements-kind.h" #include "src/objects/js-objects.h" +#include "src/objects/property-details.h" namespace v8 { namespace internal { @@ -318,7 +319,8 @@ static FieldAccess ForFeedbackVectorSlot(int index); // Provides access to PropertyArray slots. - static FieldAccess ForPropertyArraySlot(int index); + static FieldAccess ForPropertyArraySlot(int index, + Representation representation); // Provides access to ScopeInfo flags. static FieldAccess ForScopeInfoFlags(); diff --git a/src/compiler/js-native-context-specialization.cc b/src/compiler/js-native-context-specialization.cc index faeda3e..dc8fcd4 100644 --- a/src/compiler/js-native-context-specialization.cc +++ b/src/compiler/js-native-context-specialization.cc @@ -38,6 +38,7 @@ #include "src/objects/elements-kind.h" #include "src/objects/feedback-vector.h" #include "src/objects/heap-number.h" +#include "src/objects/property-details.h" #include "src/objects/string.h" namespace v8 { @@ -4235,25 +4236,59 @@ // for intermediate states of chains of property additions. That makes // it unclear what the best approach is here. DCHECK_EQ(map.UnusedPropertyFields(), 0); - int length = map.NextFreePropertyIndex() - map.GetInObjectProperties(); + int in_object_length = map.GetInObjectProperties(); + int length = map.NextFreePropertyIndex() - in_object_length; // Under normal circumstances, NextFreePropertyIndex() will always be larger // than GetInObjectProperties(). However, an attacker able to corrupt heap // memory can break this invariant, in which case we'll get confused here, // potentially causing a sandbox violation. This CHECK defends against that. SBXCHECK_GE(length, 0); int new_length = length + JSObject::kFieldsAdded; + + // Find the descriptor index corresponding to the first out-of-object + // property. + DescriptorArrayRef descs = map.instance_descriptors(broker()); + InternalIndex first_out_of_object_descriptor(in_object_length); + InternalIndex number_of_descriptors(descs.object()->number_of_descriptors()); + for (InternalIndex i(in_object_length); i < number_of_descriptors; ++i) { + PropertyDetails details = descs.GetPropertyDetails(i); + // Skip over non-field properties. + if (details.location() != PropertyLocation::kField) { + continue; + } + // Skip over in-object fields. + // TODO(leszeks): We could make this smarter, like a binary search. + if (details.field_index() < in_object_length) { + continue; + } + first_out_of_object_descriptor = i; + break; + } + // Collect the field values from the {properties}. - ZoneVector<Node*> values(zone()); + ZoneVector<std::pair<Node*, Representation>> values(zone()); values.reserve(new_length); - for (int i = 0; i < length; ++i) { + + // Walk the property descriptors alongside the property values, to make + // sure to get and store them with the right machine type. + InternalIndex descriptor = first_out_of_object_descriptor; + for (int i = 0; i < length; ++i, ++descriptor) { + PropertyDetails details = descs.GetPropertyDetails(descriptor); + while (details.location() != PropertyLocation::kField) { + ++descriptor; + details = descs.GetPropertyDetails(descriptor); + } + DCHECK_EQ(i, details.field_index() - in_object_length); Node* value = effect = graph()->NewNode( - simplified()->LoadField(AccessBuilder::ForFixedArraySlot(i)), + simplified()->LoadField( + AccessBuilder::ForPropertyArraySlot(i, details.representation())), properties, effect, control); - values.push_back(value); + values.push_back({value, details.representation()}); } // Initialize the new fields to undefined. for (int i = 0; i < JSObject::kFieldsAdded; ++i) { - values.push_back(jsgraph()->UndefinedConstant()); + values.push_back( + {jsgraph()->UndefinedConstant(), Representation::Tagged()}); } // Compute new length and hash. @@ -4291,7 +4326,8 @@ a.Store(AccessBuilder::ForMap(), jsgraph()->PropertyArrayMapConstant()); a.Store(AccessBuilder::ForPropertyArrayLengthAndHash(), new_length_and_hash); for (int i = 0; i < new_length; ++i) { - a.Store(AccessBuilder::ForFixedArraySlot(i), values[i]); + a.Store(AccessBuilder::ForPropertyArraySlot(i, values[i].second), + values[i].first); } return a.Finish(); } diff --git a/src/compiler/turboshaft/turbolev-early-lowering-reducer-inl.h b/src/compiler/turboshaft/turbolev-early-lowering-reducer-inl.h index 0c63f5c..d8b5571 100644 --- a/src/compiler/turboshaft/turbolev-early-lowering-reducer-inl.h +++ b/src/compiler/turboshaft/turbolev-early-lowering-reducer-inl.h @@ -14,6 +14,7 @@ #include "src/compiler/turboshaft/representations.h" #include "src/deoptimizer/deoptimize-reason.h" #include "src/objects/contexts.h" +#include "src/objects/descriptor-array-inl.h" #include "src/objects/instance-type-inl.h" namespace v8::internal::compiler::turboshaft { @@ -318,8 +319,32 @@ } V<PropertyArray> ExtendPropertiesBackingStore( - V<PropertyArray> old_property_array, V<JSObject> object, int old_length, + V<PropertyArray> old_property_array, V<JSObject> object, + const compiler::MapRef& old_map, int old_length, V<FrameState> frame_state, const FeedbackSource& feedback) { + int in_object_length = old_map.GetInObjectProperties(); + + // Find the descriptor index corresponding to the first out-of-object + // property. + DescriptorArrayRef descs = old_map.instance_descriptors(broker_); + InternalIndex first_out_of_object_descriptor(in_object_length); + InternalIndex number_of_descriptors( + descs.object()->number_of_descriptors()); + for (InternalIndex i(in_object_length); i < number_of_descriptors; ++i) { + PropertyDetails details = descs.GetPropertyDetails(i); + // Skip over non-field properties. + if (details.location() != PropertyLocation::kField) { + continue; + } + // Skip over in-object fields. + // TODO(leszeks): We could make this smarter, like a binary search. + if (details.field_index() < in_object_length) { + continue;
Loading diff…
Original Bug Report
The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.
References
On This Page