Overview

High
Severity
8.8
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactType Confusion in V8
DescriptionType Confusion in V8
ComponentV8
Sub-componentBlink>JavaScript>Compiler>Maglev
Bug ClassType Confusion
Action tierpatch-now — corruption primitive, web-reachable in default config (plausible) — upheld by independent review by AI
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Independent review by AIPlausible
The mapped commit may not contain the whole fix.
Reviewer notes: Diff truncated; central effect-based clearing and CanAllocate changes not visible. Hunk description is accurate. Impact wrongly says the advisory states no severity; advisory gives high, CVSS 8.8.
A second, independent AI reviewer (not a human) re-derived this analysis from the fixing commit and the vendor advisory, looking for errors.
Tracker398065918
Fix commit7c3ccfc4429c (v8/v8) +52/-89
CWECWE-843 (Type confusion)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CISA KEVNot listed
CreditedExcello s.r.o.
Reported2025-02-21 · bounty $7000 (per vendor advisory)
Fix landed2025-02-27 (public git)
Disclosed2025-03-10
Patch gap11 days — fix public in source before the advisory

Background

Maglev
V8’s mid-tier optimizing compiler, whose MaglevGraphBuilder turns bytecode into a node graph and can inline callees by running a nested inner_graph_builder over the callee.
Allocation folding (current allocation block)
BuildInlinedAllocation places inlined objects into the builder’s current allocation block, so several objects can share one raw allocation until the block is closed with ClearCurrentAllocationBlock().
Folding invariant
according to the commit message, the block has to be closed after any instruction that can allocate and on control flow; the earlier relanding also had to add “missing CanAllocate properties to instructions”, so only non-allocating code may sit between folded allocations.
`ReduceResult::DoneWithAbort()`
the result an inlined body returns when it ends in an unconditional deopt; the caller then sets current_block_ to nullptr and returns early.

Root Cause Analysis

Verify before building a PoC

This analysis is consistent with the fixing commit but not fully provable from it: part of the vulnerable path lives in code the fix commit does not touch. Confirm the trigger and the sink against the source before relying on it for exploit development.
What the commit does not establish: Diff truncated; central effect-based clearing and CanAllocate changes not visible. Hunk description is accurate. Impact wrongly says the advisory states no severity; advisory gives high, CVSS 8.8.

Before the patch, the builder closed the current allocation block by hand: many BuildInlinedAllocation sites were followed by an explicit ClearCurrentAllocationBlock(), each marked with the removed comment “TODO(leszeks): Don’t eagerly clear the raw allocation, have the next side effect clear it”.

The fix (bug 398065918) replaces these per-site calls with a central rule that closes the block “after allocating instructions and on control flow”. That central code is not in the excerpt: the last hunk (@@ -12428) is cut off and the node CanAllocate property changes are missing. The exact pre-patch path that let a later allocation fold into a block left open across an instruction that can allocate or trigger GC, and how this became a type confusion, therefore cannot be identified from this diff and is (inferred). The visible hunks do two things: they remove the eager per-site clears, which on their own allow more folding and are safe only if the unseen central rule covers every site, and they add a new ClearCurrentAllocationBlock() call (added, not moved) in the inlining caller right after inner_graph_builder.BuildInlineFunction(...). In the same place, the existing unobserved_context_slot_stores_ copy-back and latest_checkpointed_frame_.reset() are moved above the result.IsDoneWithAbort() early return, so caller state is now reset on the deopt/abort exit too. The commit message’s “Fixed: Clear allocation block even if the inliner ends in an unconditional deopt” compares against the previous, reverted landing, so the abort-path ordering fixes a regression in that landing, not the original vulnerability.

Key insight
Closing the allocation-folding block by hand at chosen call sites does not reliably stop folding across instructions that can allocate, or across control flow and inlining boundaries. The fix moves the clear to one place, driven by effects and control flow (not shown in the excerpt), and adds an unconditional clear when returning from an inlined function, including the deopt/abort exit.

Attack Path

  1. Get code optimized by Maglev Web content runs JavaScript hot enough for Maglev to compile it. The function uses constructs that produce inlined allocations, such as object constructors, iterator results, arrays, generators, regexp literals, or a callee that gets inlined.
  2. Leave a folding block open On an unpatched build, a graph shape where no manual ClearCurrentAllocationBlock() runs leaves the current allocation block open past an instruction that can allocate, or past an inlining or control-flow boundary. The exact shape is in code not shown (inferred).
  3. Fold a later allocation A later BuildInlinedAllocation folds into that stale block instead of starting a fresh one, which breaks the folding invariant (inferred).
  4. Corrupt the heap state The advisory classifies the result as a type confusion in V8. How the broken invariant becomes a misused object, and any read/write primitive built from it, is (inferred) and not shown in the diff.

Impact Assessment

An attacker who can get Maglev to compile and run their JavaScript, which any web page can do by default, can cause a type confusion in V8 inside the sandboxed renderer process, according to the advisory (“Type Confusion in V8”). Escalation to memory corruption primitives or code execution in the renderer is (inferred), because the diff shows only changes to allocation-folding bookkeeping. The vendor advisory rates this high severity and does not report in-the-wild exploitation; NVD scores it CVSS 8.8.

Changed Functions

FunctionChangeNotes
if
src/maglev/maglev-graph-builder.cc
modified

Files Changed

  • src/maglev/maglev-graph-builder.cc

Audit Directions

  • Manual invalidation of compiler state
    Look for optimizer state that stays valid only until the next effect, such as allocation blocks, known node aspects or checkpointed frames, where invalidation depends on scattered manual calls. Check that every node able to allocate or GC is flagged (for example CanAllocate) so a central rule can do the invalidation.
  • Nested-builder state hand-back
    Wherever a nested builder (such as inner_graph_builder) hands state back to its caller, check that every exit, including DoneWithAbort and unconditional deopt, resets or propagates caller state such as current_allocation_block, latest_checkpointed_frame_ and unobserved_context_slot_stores_.
  • Relands that remove safety calls
    When a reland removes eager safety calls in favour of a central mechanism, diff each removed call site against what the central mechanism covers. Treat each reland’s “Fixed:” note as evidence of exits the earlier landing missed.
From 7c3ccfc4429cb9fefe586a5359a12ebcbb101dd7 Mon Sep 17 00:00:00 2001
From: Olivier Flückiger <[email protected]>
Date: Thu, 27 Feb 2025 15:27:29 +0100
Subject: [PATCH] Reland^2 "[maglev] Clear the current allocation block only on effects"

This is a reland of commit 58ad206e922fbd7f6fad7b9e2e134f0191109307

Fixed:
* Clear allocation block even if the inliner ends in an unconditional
  deopt.

Original change's description:
> Reland "[maglev] Clear the current allocation block only on effects"
>
> This is a reland of commit 8dc89d8bc1020bd2a6d7677e0804fda30e786055
>
> Fixed: adding some missing CanAllocate properties to instructions
>
> Original change's description:
> > [maglev] Clear the current allocation block only on effects
> >
> > Instead of clearing manually we clear the block after allocating
> > instructions and on control flow.
> >
> > Bug: 398065918
> > Change-Id: If3baa726d3f140df66aa08f1d1364dd2458096e6
> > Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/6297973
> > Reviewed-by: Victor Gomes <[email protected]>
> > Commit-Queue: Victor Gomes <[email protected]>
> > Auto-Submit: Olivier Flückiger <[email protected]>
> > Cr-Commit-Position: refs/heads/main@{#98921}
>
> Bug: 398065918
> Change-Id: Ia0497301b728dd8891c1d90e2f6bca01f89ee8d4
> Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/6305040
> Auto-Submit: Olivier Flückiger <[email protected]>
> Reviewed-by: Victor Gomes <[email protected]>
> Commit-Queue: Olivier Flückiger <[email protected]>
> Cr-Commit-Position: refs/heads/main@{#98962}

Bug: 398065918
Change-Id: I7f5cc44ee8fb84ed954376172459dac220261a0f
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/6309164
Reviewed-by: Victor Gomes <[email protected]>
Auto-Submit: Olivier Flückiger <[email protected]>
Commit-Queue: Victor Gomes <[email protected]>
Cr-Commit-Position: refs/heads/main@{#98977}
---

diff --git a/src/maglev/maglev-graph-builder.cc b/src/maglev/maglev-graph-builder.cc
index 660697a..d11b2c6 100644
--- a/src/maglev/maglev-graph-builder.cc
+++ b/src/maglev/maglev-graph-builder.cc
@@ -2752,9 +2752,6 @@
     ValueNode* allocation =
         BuildInlinedAllocation(cons_string, allocation_type);
 
-    // TODO(leszeks): Don't eagerly clear the raw allocation, have the
-    // next side effect clear it.
-    ClearCurrentAllocationBlock();
     return allocation;
   };
 
@@ -8123,7 +8120,6 @@
           object = BuildInlinedAllocation(
               CreateJSConstructor(new_target_function->AsJSFunction()),
               AllocationType::kYoung);
-          ClearCurrentAllocationBlock();
         } else {
           object = BuildCallBuiltin<Builtin::kFastNewObject>(
               {GetConstant(current_function), GetTaggedValue(new_target)});
@@ -8479,18 +8475,22 @@
   // Build inline function.
   ReduceResult result =
       inner_graph_builder.BuildInlineFunction(context, function, new_target);
+
+  // Prapagate back (or reset) builder state.
+  unobserved_context_slot_stores_ =
+      inner_graph_builder.unobserved_context_slot_stores_;
+  latest_checkpointed_frame_.reset();
+  ClearCurrentAllocationBlock();
+
   if (result.IsDoneWithAbort()) {
     DCHECK_NULL(inner_graph_builder.current_block_);
     current_block_ = nullptr;
     return ReduceResult::DoneWithAbort();
   }
 
-  // Propagate information back to the caller.
-  latest_checkpointed_frame_.reset();
+  // Propagate frame information back to the caller.
   current_interpreter_frame_.set_known_node_aspects(
       inner_graph_builder.current_interpreter_frame_.known_node_aspects());
-  unobserved_context_slot_stores_ =
-      inner_graph_builder.unobserved_context_slot_stores_;
   current_interpreter_frame_.set_virtual_objects(
       inner_graph_builder.current_interpreter_frame_.virtual_objects());
   current_for_in_state.receiver_needs_map_check =
@@ -8982,9 +8982,6 @@
       map, subgraph.get(ret_value), subgraph.get(is_done));
   ValueNode* allocation =
       BuildInlinedAllocation(iter_result, AllocationType::kYoung);
-  // TODO(leszeks): Don't eagerly clear the raw allocation, have the
-  // next side effect clear it.
-  ClearCurrentAllocationBlock();
   return allocation;
 }
 
@@ -9100,9 +9097,6 @@
   VirtualObject* string_iterator = CreateJSStringIterator(map, receiver);
   ValueNode* allocation =
       BuildInlinedAllocation(string_iterator, AllocationType::kYoung);
-  // TODO(leszeks): Don't eagerly clear the raw allocation, have the
-  // next side effect clear it.
-  ClearCurrentAllocationBlock();
   return allocation;
 }
 
@@ -11195,9 +11189,6 @@
   VirtualObject* iter_result = CreateJSIteratorResult(map, value, done);
   ValueNode* allocation =
       BuildInlinedAllocation(iter_result, AllocationType::kYoung);
-  // TODO(leszeks): Don't eagerly clear the raw allocation, have the
-  // next side effect clear it.
-  ClearCurrentAllocationBlock();
   SetAccumulator(allocation);
   return ReduceResult::Done();
 }
@@ -11349,9 +11340,6 @@
       array, CreateJSArray(map, map.instance_size(), GetInt32Constant(2)));
   array->set(JSArray::kElementsOffset, elements);
   ValueNode* allocation = BuildInlinedAllocation(array, AllocationType::kYoung);
-  // TODO(leszeks): Don't eagerly clear the raw allocation, have the
-  // next side effect clear it.
-  ClearCurrentAllocationBlock();
   return allocation;
 }
 
@@ -11370,9 +11358,6 @@
                GetRootConstant(RootIndex::kUndefinedValue));
   }
   ValueNode* allocation = BuildInlinedAllocation(array, allocation_type);
-  // TODO(leszeks): Don't eagerly clear the raw allocation, have the
-  // next side effect clear it.
-  ClearCurrentAllocationBlock();
   return allocation;
 }
 
@@ -11383,9 +11368,6 @@
   VirtualObject* iterator = CreateJSArrayIterator(map, array, iteration_kind);
   ValueNode* allocation =
       BuildInlinedAllocation(iterator, AllocationType::kYoung);
-  // TODO(leszeks): Don't eagerly clear the raw allocation, have the
-  // next side effect clear it.
-  ClearCurrentAllocationBlock();
   return allocation;
 }
 
@@ -11430,9 +11412,6 @@
 
   ValueNode* allocation =
       BuildInlinedAllocation(generator, AllocationType::kYoung);
-  // TODO(leszeks): Don't eagerly clear the raw allocation, have the
-  // next side effect clear it.
-  ClearCurrentAllocationBlock();
   return allocation;
 }
 
@@ -11567,9 +11546,6 @@
         RETURN_IF_ABORT(BuildCheckValueByReference(target, builtin));
         ValueNode* result = BuildInlinedAllocation(CreateJSConstructor(builtin),
                                                    AllocationType::kYoung);
-        // TODO(leszeks): Don't eagerly clear the raw allocation, have the
-        // next side effect clear it.
-        ClearCurrentAllocationBlock();
         return result;
       }
       break;
@@ -11626,9 +11602,6 @@
     if (map.GetConstructor(broker()).equals(function)) {
       implicit_receiver = BuildInlinedAllocation(CreateJSConstructor(function),
                                                  AllocationType::kYoung);
-      // TODO(leszeks): Don't eagerly clear the raw allocation, have the
-      // next side effect clear it.
-      ClearCurrentAllocationBlock();
     }
   }
   if (implicit_receiver == nullptr) {
@@ -12354,9 +12327,6 @@
         native_context.regexp_function(broker()).initial_map(broker());
     SetAccumulator(BuildInlinedAllocation(
         CreateRegExpLiteralObject(map, literal), AllocationType::kYoung));
-    // TODO(leszeks): Don't eagerly clear the raw allocation, have the next side
-    // effect clear it.
-    ClearCurrentAllocationBlock();
     return ReduceResult::Done();
   }
   // Fallback.
@@ -12428,9 +12398,6 @@
   GET_VALUE_OR_ABORT(
Loading diff…

Original Bug Report

reported by [email protected]

V8 Maglev improper folded allocation handling (leading to memory safety issues)

VULNERABILITY DETAILS

Intro

The vulnerability uses two V8 concepts:

  • Folded allocation - is an optimization where multiple allocation operations are combined into a single allocation.
  • Memory is divided into two main regions Young Generation (Young Space) and Old Generation (Old Space). When an object in the Young Space survives multiple garbage collections, it is promoted or reallocated to the Old Space. One of the moment when GC is called is during Builtins_AllocateInYoungGeneration, which is called when need to allocate more memory.

The issue gets triggered when reallocation is performed in the middle of folded allocation.

The bug

Let’s consider a simplified code

function triggerFunction(...a) {
    ...
}
function __f_0() {
    ...
    triggerFunction(v, pi, r);
    ...
}

When Maglev creates folded allocation and GC/reallocation is performed in the middle of such allocation, the variable “V” will be overlapped by array “A”.

The process looks as follow:

  1. Allocate memory for “V” and create “V”
  2. Allocate memory for both “PI” and “A” (folded allocation) and create “PI”.
  3. Allocate memory for “R” and create “R”. As not enough memory in current block - extend it. During extention the garbage collection and reallocation (from Young to Old space) is done.
  4. Reallocate “PI” from Young to Old space. Reallocate “V” from Young to Old space. “V” is put right after “PI”.
  5. Create array “A”. As space after “PI” is reserved for “A” (in step 2) it is put right after “PI”. But variable “V” now uses this space (becuase of step 4). “V” is overwritten by “A”.

So, the “PI” is reallocated to the new place. The “V” varialbe is also, and it got placed right after “PI”. However, due to the folded allocation, the place after “PI” is reserved for “A”.

Variable “V” before overwritten by array “A”

0x33920064002d: [HeapNumber]
 - map: 0x339200000515 <Map[12](HEAP_NUMBER_TYPE)>
 - value: 1073751774.0

Variable “V” after overwritten by array “A”

0x33920064002d: [FixedArray]
 - map: 0x339200000565 <Map(FIXED_ARRAY_TYPE)>
 - length: 10
           0: 0x33920064002d <FixedArray[10]>
           1: 0x339200640021 <HeapNumber 3.14159>
           2: 0x339200000011 <undefined>
           3: 0x3392006400ad <HeapNumber 107375179900.0>
           4: 0x339200000011 <undefined>
           5: 0x339200640011 <Number map = 0x339200509f0d value = 0>
         6-9: 0x339200000011 <undefined>

Restrictions/requirements for triggering the bug

Small modification in script result in triggering GC/reallocation in other functions, breaking the process. To trigger the issue this is a must:

  • Folded allocation of “PI” and “A” (we always have it with our code)
  • The quota to reallocate from Young to Old space for both “V” and “PI” should be reached right before step 3 (sensitive to side-effects)
  • Should not be enough space to allocate new variable “R” at step 3, so Builtins_AllocateInYoungGeneration would be called and trigger reallocation (sensitive to side-effects)

To accommodate the different memory layouts (between various source commits, different run flags, build arguments), we had to introduce additional allocations by adding stub variable declarations. We’ve made a script search_allocations.py to do this automatically.

How it probably should work

Non-vulnerable, intended behavior probably is one of the following:

  • Reallocator should be aware of folded allocation and keep track reserved memory
  • Reallocation shouldn’t happen in the middle of folded allocation (between step 2 and 5). All objects from folded allocation should be created before reallocation. Currently, “A” is created after reallocation.

Root cause

Rest arguments create such sequence “V”, “PI”, “R”, “A”, where “PI” and “A” are folded-allocated.

Proof of concept

PoC 1

Branch: main, commit: f30c2930794e1347a85909c0272964692e4311f6

args.gn (release build)

is_component_build = false
is_debug = false
target_cpu = "x64"
v8_enable_sandbox = true
v8_enable_backtrace = true
v8_enable_disassembler = true
v8_enable_object_print = true
v8_enable_verify_heap = true
dcheck_always_on = false

Command: d8 --allow-natives-syntax --no-js-atomics-pause --no-script-context-mutable-heap-number --predictable --wasm-staging --isolate poc1.js

Error:

Stacktrace:
    ptr1=0x9b1001c002d
    ptr2=(nil)
    ptr3=(nil)
    ptr4=(nil)
    ptr5=(nil)
    ptr6=(nil)
    failure_message_object=0x75c7c3df7130

==== JS stack trace =========================================

    0: ExitFrame [pc: 0x5f92730281b6]
    1: StubFrame [pc: 0x5f927307ce95]
    2: StubFrame [pc: 0x5f9273070a45]
    3: toString [0x9b1003431d1](this=10000000,0x09b1001c002d <FixedArray[10]>,0x09b1001c0021 <HeapNumber 3.14159>,0x09b100000011 <undefined>,0x09b1001c00ad <HeapNumber 78383880524.0>,0x09b100000011 <undefined>,0x09b1001c0011 <Number map = 0x9b10034308d value = 0>,0x09b100000011 <undefined>,0x09b100000011 <undefined>,0x09b100000011 <undefined>,0x09b100000011 <undefined>)
    4: __f_0 [0x9b10035bc81] [poc1.js:~170] [pc=0x5f92d2f4a95e](this=0x09b100341a85 <JSGlobalProxy>)
    5: __f_1 [0x9b10035bcb5] [poc1.js:190] [bytecode=0x31a0018013d offset=40](this=0x09b100341a85 <JSGlobalProxy>)
    6: /* anonymous */ [0x9b10035b3a1] [poc1.js:193] [bytecode=0x31a001800d1 offset=43](this=0x09b100341a85 <JSGlobalProxy>)
    7: InternalFrame [pc: 0x5f9272f7e71c]
    8: EntryFrame [pc: 0x5f9272f7e46b]

==== Details ================================================

[0]: ExitFrame [pc: 0x5f92730281b6]
[1]: StubFrame [pc: 0x5f927307ce95]
[2]: StubFrame [pc: 0x5f9273070a45]
[3]: toString [0x9b1003431d1](this=10000000,0x09b1001c002d <FixedArray[10]>,0x09b1001c0021 <HeapNumber 3.14159>,0x09b100000011 <undefined>,0x09b1001c00ad <HeapNumber 78383880524.0>,0x09b100000011 <undefined>,0x09b1001c0011 <Number map = 0x9b10034308d value = 0>,0x09b100000011 <undefined>,0x09b100000011 <undefined>,0x09b100000011 <undefined>,0x09b100000011 <undefined>) {
// optimized frame
--------- s o u r c e   c o d e ---------
<No Source>
-----------------------------------------
}
[4]: __f_0 [0x9b10035bc81] [poc1.js:~170] [pc=0x5f92d2f4a95e](this=0x09b100341a85 <JSGlobalProxy>) {
// optimized frame
--------- s o u r c e   c o d e ---------
function __f_0() {\x0a  let __v_2 = __v_1;\x0a\x0a  for (var i = 0; i < 100; i++) { \x0a    --__v_1;\x0a    __v_2 += __v_1;\x0a    console.log(i);\x0a    console.log(__v_1);\x0a    triggerFunction(10000000, 456109613, __v_1,   Math.PI, __getRandomObject(5161),__v_2, __getRandomObject(639756),new Number(), undefined,undefined, undefined,...

-----------------------------------------
}
[5]: __f_1 [0x9b10035bcb5] [poc1.js:190] [bytecode=0x31a0018013d offset=40](this=0x09b100341a85 <JSGlobalProxy>) {
  // expression stack (top to bottom)
  [01] : 0x09b100341a85 <JSGlobalProxy>
  [00] : 0x09b10035bc81 <JSFunction __f_0 (sfi = 0x9b10035b259)>
--------- s o u r c e   c o d e ---------
function __f_1() {\x0a  %PrepareFunctionForOptimization(__f_0);  \x0a  __f_0();\x0a  %OptimizeMaglevOnNextCall(__f_0);\x0a  __v_1 = 1073751824;\x0a  __f_0();\x0a}
-----------------------------------------
}

[6]: /* anonymous */ [0x9b10035b3a1] [poc1.js:193] [bytecode=0x31a001800d1 offset=43](this=0x09b100341a85 <JSGlobalProxy>) {
  // heap-allocated locals
  var __v_1 = 0x09b10035eb35 <FixedArray[1]>
  // expression stack (top to bottom)
  [04] : 0x09b100341a85 <JSGlobalProxy>
  [03] : 0x09b10035d63d <Object map = 0x9b10034281d>
  [02] : 0x09b10035d659 <Object map = 0x9b10034281d>
  [01] : 0x09b10035bcb5 <JSFunction __f_1 (sfi = 0x9b10035b289)>
  [00] : 0x09b100000011 <undefined>
--------- s o u r c e   c o d e ---------
var my_simple_var_0;\x0avar my_simple_var_1;\x0avar my_simple_var_2;\x0avar my_simple_var_3;\x0avar my_simple_var_4;\x0avar my_simple_var_5;\x0avar my_simple_var_6;\x0avar my_simple_var_7;\x0avar my_simple_var_8;\x0avar my_simple_var_9;\x0avar my_simple_var_10;\x0avar my_simple_var_11;\x0avar my_simple_var_12;\x0avar my_simple_var_13;\x0ava...

-----------------------------------------
}

[7]: InternalFrame [pc: 0x5f9272f7e71c]
[8]: EntryFrame [pc: 0x5f9272f7e46b]
=====================

Trace/breakpoint trap (core dumped)

The crash itself happens in toString (NumberPrototypeToString in number.tq), where the first argument is considered as radix. But as number value is overlapped by array the crash will happen during the convertion.

PoC 2

Branch: main, commit: f30c2930794e1347a85909c0272964692e4311f6

args.gn (build with dchecks)

symbol_level = 2
is_debug = false
v8_optimized_debug = false
dcheck_always_on = true
v8_win64_unwinding_info = true
v8_enable_verify_csa = true
v8_enable_object_print = true
v8_enable_maglev_graph_printer = true
v8_enable_disassembler = true
v8_enable_slow_dchecks = true
v8_enable_verify_heap = true
v8_enable_sandbox = true

target_cpu = "x64"
v8_enable_backtrace = true
v8_enable_gdbjit = true

Command: d8 --allow-natives-syntax --no-js-atomics-pause --no-script-context-mutable-heap-number --predictable --wasm-staging --isolate poc2.js

Error:

#
# Fatal error in ../../src/objects/object-type.cc, line 82
# Type cast failed in CAST(LoadFromObject(machine_type, object, raw_offset)) at ../../src/codegen/code-stub-assembler.h:1166
  Expected Map but found Smi: 0x2a22168c (706877068)

#
#
#
#FailureMessage Object: 0x7ce5a47fee00Trace/breakpoint trap (core dumped)

Here we trigger a crash in different place, which may be more useful for the exploitation.

PoC 3

Issue is also present in older versions. This is the earliest commit for which we made a PoC, but the issue may have existed even earlier.

Branch: main, commit: 4bfcab00943b1e35f4fb2326aafea5a64752f75e

args.gn (release)

is_component_build = false
is_debug = false
target_cpu = "x64"
v8_enable_sandbox = true

v8_enable_backtrace = true
v8_enable_disassembler = true
v8_enable_object_print = true
v8_enable_verify_heap = true
dcheck_always_on = false

Command: d8 --allow-natives-syntax --predictable --isolate poc3.js

Error:

Stacktrace:
    ptr1=0x9b0001c002d
    ptr2=(nil)
    ptr3=(nil)
    ptr4=(nil)
    ptr5=(nil)
    ptr6=(nil)
    failure_message_object=0x7d52007f7150

==== JS stack trace =========================================

    0: ExitFrame [pc: 0x6452ba4f81b6]
    1: StubFrame [pc: 0x6452ba54cf94]
    2: StubFrame [pc: 0x6452ba5410c5]
    3: toString [0x9b000349b09](this=10000000,0x09b0001c002d <FixedArray[10]>,0x09b0001c0021 <HeapNumber 3.14159>,0x09b000000069 <undefined>,0x09b0001c00ad <HeapNumber 13958773634.0>,0x09b000000069 <undefined>,0x09b0001c0011 <Number map = 0x9b0003499c5 value = 0>,0x09b000000069 <undefined>,0x09b000000069 <undefined>,0x09b000000069 <undefined>,0x09b000000069 <undefined>)
    4: __f_0 [0x9b00035c99d] [../../poc3_4bfcab00943b1e35f4fb2326aafea5a64752f75e.js:~360] [pc=0x6452e000a190](this=0x09b0003416c9 <JSGlobalProxy>)
    5: __f_1 [0x9b00035c9d1] [../../poc3_4bfcab00943b1e35f4fb2326aafea5a64752f75e.js:380] [bytecode=0x31a000800b5 offset=40](this=0x09b0003416c9 <JSGlobalProxy>)
    6: /* anonymous */ [0x9b00035b209] [../../poc3_4bfcab00943b1e35f4fb2326aafea5a64752f75e.js:383] [bytecode=0x31a00080035 offset=61](this=0x09b0003416c9 <JSGlobalProxy>)
    7: InternalFrame [pc: 0x6452ba45671c]
    8: EntryFrame [pc: 0x6452ba45645f]

==== Details ================================================

[0]: ExitFrame [pc: 0x6452ba4f81b6]
[1]: StubFrame [pc: 0x6452ba54cf94]
[2]: StubFrame [pc: 0x6452ba5410c5]
[3]: toString [0x9b000349b09](this=10000000,0x09b0001c002d <FixedArray[10]>,0x09b0001c0021 <HeapNumber 3.14159>,0x09b000000069 <undefined>,0x09b0001c00ad <HeapNumber 13958773634.0>,0x09b000000069 <undefined>,0x09b0001c0011 <Number map = 0x9b0003499c5 value = 0>,0x09b000000069 <undefined>,0x09b000000069 <undefined>,0x09b000000069 <undefined>,0x09b000000069 <undefined>) {
// optimized frame
--------- s o u r c e   c o d e ---------
<No Source>
-----------------------------------------
}
[4]: __f_0 [0x9b00035c99d] [../../poc3_4bfcab00943b1e35f4fb2326aafea5a64752f75e.js:~360] [pc=0x6452e000a190](this=0x09b0003416c9 <JSGlobalProxy>) {
// optimized frame
--------- s o u r c e   c o d e ---------
function __f_0() {\x0a  let __v_2 = __v_1;\x0a\x0a  for (var i = 0; i < 100; i++) { \x0a    --__v_1;\x0a    __v_2 += __v_1;\x0a    console.log(i);\x0a    console.log(__v_1);\x0a    triggerFunction(10000000, 456109613, __v_1,   Math.PI, __getRandomObject(5161),__v_2, __getRandomObject(639756),new Number(), undefined,undefined, undefined,...

-----------------------------------------
}
[5]: __f_1 [0x9b00035c9d1] [../../poc3_4bfcab00943b1e35f4fb2326aafea5a64752f75e.js:380] [bytecode=0x31a000800b5 offset=40](this=0x09b0003416c9 <JSGlobalProxy>) {
  // expression stack (top to bottom)
  [01] : 0x09b0003416c9 <JSGlobalProxy>
  [00] : 0x09b00035c99d <JSFunction __f_0 (sfi = 0x9b00035b0a9)>
--------- s o u r c e   c o d e ---------
function __f_1() {\x0a  %PrepareFunctionForOptimization(__f_0);  \x0a  __f_0();\x0a  %OptimizeMaglevOnNextCall(__f_0);\x0a  __v_1 = 1073751824;\x0a  __f_0();\x0a}
-----------------------------------------
}

[6]: /* anonymous */ [0x9b00035b209] [../../poc3_4bfcab00943b1e35f4fb2326aafea5a64752f75e.js:383] [bytecode=0x31a00080035 offset=61](this=0x09b0003416c9 <JSGlobalProxy>) {
  // heap-allocated locals
  var __v_1 = 0x09b000360995 <FixedArray[1]>
  // expression stack (top to bottom)
  [04] : 0x09b0003416c9 <JSGlobalProxy>
  [03] : 0x09b00035e391 <Object map = 0x9b000342445>
  [02] : 0x09b00035e3ad <Object map = 0x9b000342445>
  [01] : 0x09b00035c9d1 <JSFunction __f_1 (sfi = 0x9b00035b0e9)>
  [00] : 0x09b000000069 <undefined>
--------- s o u r c e   c o d e ---------
var my_simple_var_0;\x0avar my_simple_var_1;\x0avar my_simple_var_2;\x0avar my_simple_var_3;\x0avar my_simple_var_4;\x0avar my_simple_var_5;\x0avar my_simple_var_6;\x0avar my_simple_var_7;\x0avar my_simple_var_8;\x0avar my_simple_var_9;\x0avar my_simple_var_10;\x0avar my_simple_var_11;\x0avar my_simple_var_12;\x0avar my_simple_var_13;\x0ava...

-----------------------------------------
}

[7]: InternalFrame [pc: 0x6452ba45671c]
[8]: EntryFrame [pc: 0x6452ba45645f]
=====================

Trace/breakpoint trap (core dumped)

Fix

The fix should reset/split folded allocation done in rest arguments.

diff --git a/src/maglev/maglev-graph-builder.cc b/src/maglev/maglev-graph-builder.cc
index 7c7c1c36c95..b36b64df4eb 100644
--- a/src/maglev/maglev-graph-builder.cc
+++ b/src/maglev/maglev-graph-builder.cc
@@ -12900,6 +12900,7 @@ ValueNode* MaglevGraphBuilder::BuildInlinedAllocation(
       SmallZoneVector<ValueNode*, 8> values(zone());
       vobject->ForEachDeoptInputLocation(
           [&](ValueNode* node, ValueNode*& input) {
+            ClearCurrentAllocationBlock();
             if (node->Is<VirtualObject>()) {
               VirtualObject* nested = node->Cast<VirtualObject>();
               node = BuildInlinedAllocation(nested, allocation_type);

Reproduction

See the attached files and the “Restrictions/requirements for triggering the bug” section

CREDIT INFORMATION

Externally reported security bugs may appear in Chrome release notes. If this bug is included, how would you like to be credited? Reporter credit: Excello s.r.o.

View on issue tracker