# What: backport CVE-2026-85046 to V8 15.2.124.1 by rejecting mixed # elements kinds when inlining Array.prototype.sort. # Scope: Maglev/TurboFan implementation guards and the upstream regression test. # Upstream M152 fix: https://github.com/v8/v8/commit/94c34b200ae9caf83d06c167a070746a1c7e825c # CVE advisory: https://chromereleases.googleblog.com/2026/09/stable-channel-update-for-desktop_01882797386.html diff --git a/orig/v8-15.2.124.1/src/compiler/js-call-reducer.cc b/mod/v8-15.2.124.1/src/compiler/js-call-reducer.cc index e8707261d3e2..0e2d9bfd0a74 100644 --- a/orig/v8-15.2.124.1/src/compiler/js-call-reducer.cc +++ b/mod/v8-15.2.124.1/src/compiler/js-call-reducer.cc @@ -4068,6 +4068,10 @@ class IteratingArrayBuiltinHelper { return; } + all_elements_kinds_equal_ = std::all_of( + receiver_maps.begin(), receiver_maps.end(), + [&](MapRef map) { return map.elements_kind() == elements_kind_; }); + // TODO(jgruber): May only be needed for holey elements kinds. if (!dependencies->DependOnNoElementsProtector()) return; @@ -4083,10 +4087,13 @@ class IteratingArrayBuiltinHelper { Control control() const { return control_; } MapInference* inference() { return &inference_; } ElementsKind elements_kind() const { return elements_kind_; } + // False when elements_kind() is a union over differing receiver kinds. + bool all_elements_kinds_equal() const { return all_elements_kinds_equal_; } private: bool can_reduce_ = false; bool has_stability_dependency_ = false; + bool all_elements_kinds_equal_ = false; Node* receiver_; Effect effect_; Control control_; @@ -4126,6 +4133,11 @@ Reduction JSCallReducer::ReduceArraySort(Node* node, IteratingArrayBuiltinHelper h(node, broker(), jsgraph(), dependencies()); if (!h.can_reduce()) return h.inference()->NoChange(); + // The union kind is only an upper bound on each receiver's own kind. Loads + // through it widen and are sound; stores narrow and are not. Unlike the + // iterating builtins, sort writes its snapshot back into the receiver. + if (!h.all_elements_kinds_equal()) return h.inference()->NoChange(); + // Only non-holey, non-double PACKED kinds are supported. Holey arrays need // hole handling; double arrays need a FixedDoubleArray temp copy. if (IsHoleyElementsKind(h.elements_kind())) return h.inference()->NoChange(); diff --git a/orig/v8-15.2.124.1/src/maglev/maglev-graph-builder.cc b/mod/v8-15.2.124.1/src/maglev/maglev-graph-builder.cc index c40cadb32d92..c6ae7f791267 100644 --- a/orig/v8-15.2.124.1/src/maglev/maglev-graph-builder.cc +++ b/mod/v8-15.2.124.1/src/maglev/maglev-graph-builder.cc @@ -9797,6 +9797,16 @@ MaybeReduceResult MaglevGraphBuilder::TryReduceArrayPrototypeSort( " array iteration"); } + // The union kind is only an upper bound on each receiver's own kind. Loads + // through it widen and are sound; stores narrow and are not. Unlike the + // iterating builtins, sort writes its snapshot back into the receiver. + if (std::any_of(possible_maps->begin(), possible_maps->end(), + [&](compiler::MapRef map) { + return map.elements_kind() != elements_kind; + })) { + FAIL(" to reduce Array.prototype.sort - receiver elements kinds disagree"); + } + // Holey arrays require holes to be moved to the end of the sorted result // (ECMA-262 23.1.3.30 step 5, skip-holes mode). The insertion sort does // not implement this, so bail out for any holey kind. diff --git a/orig/v8-15.2.124.1/test/mjsunit/regress/regress-crbug-542403045.js b/mod/v8-15.2.124.1/test/mjsunit/regress/regress-crbug-542403045.js new file mode 100644 index 000000000000..e60a974f8784 --- /dev/null +++ b/mod/v8-15.2.124.1/test/mjsunit/regress/regress-crbug-542403045.js @@ -0,0 +1,89 @@ +// Copyright 2026 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Flags: --allow-natives-syntax --maglev + +// The inlined sort snapshots the receiver's elements, runs comparefn, then +// copies the snapshot back specialized on the receiver's elements kind. With +// polymorphic {PACKED_SMI,PACKED} feedback that kind is the union +// PACKED_ELEMENTS, which no longer describes a receiver that comparefn has +// narrowed back to PACKED_SMI_ELEMENTS. + +function sort(a) { + function compare() { + a.fill(0); + return 0; + } + return a.sort(compare); +} + +%PrepareFunctionForOptimization(sort); +for (let i = 0; i < 100; ++i) { + sort([1, 2]); + sort([{}, {}]); +} +%OptimizeMaglevOnNextCall(sort); +sort([1, 2]); + +const object = {}; +const bad = [object, {}]; +sort(bad); + +// A Smi-elements array must never hold a HeapObject. +assertFalse(%HasSmiElements(bad) && bad[0] === object); + +// Mixed-kind feedback is not inlined, but still sorts correctly. +function sortMixed(a) { + return a.sort((x, y) => x - y); +} +%PrepareFunctionForOptimization(sortMixed); +for (let i = 0; i < 100; ++i) { + sortMixed([2, 1]); + sortMixed([{}, {}]); +} +%OptimizeMaglevOnNextCall(sortMixed); +assertEquals([1, 2, 3], sortMixed([3, 1, 2])); + +// Same via a Smi/double mix, whose union kind PACKED_DOUBLE_ELEMENTS is +// rejected by the double bailout rather than the kind-agreement one. +function sortDouble(a) { + function compare(x, y) { + a.fill(0); + return x - y; + } + return a.sort(compare); +} +%PrepareFunctionForOptimization(sortDouble); +for (let i = 0; i < 100; ++i) { + sortDouble([2, 1]); + sortDouble([2.5, 1.5]); +} +%OptimizeMaglevOnNextCall(sortDouble); +sortDouble([2, 1]); +// The sort writes its snapshot back, so comparefn's fill is not observable. +assertEquals([1.5, 2.5], sortDouble([2.5, 1.5])); + +// Sites whose receiver kinds agree keep the inlined path, including when the +// maps themselves differ. +function withProperty() { + const a = [{}, {}]; + a.foo = 1; + return a; +} +function sortSameKind(a) { + return a.sort((x, y) => 0); +} +%PrepareFunctionForOptimization(sortSameKind); +for (let i = 0; i < 100; ++i) { + sortSameKind([{}, {}]); + sortSameKind(withProperty()); +} +%OptimizeMaglevOnNextCall(sortSameKind); +sortSameKind([{}, {}]); +assertOptimized(sortSameKind); +for (let i = 0; i < 100; ++i) { + sortSameKind([{}, {}]); + sortSameKind(withProperty()); +} +assertOptimized(sortSameKind);