Overview

High
Severity
8.8
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactType Confusion in V8
DescriptionType Confusion in V8
ComponentV8
Sub-componentMaglev
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
Reviewer notes: Diff only adds an inlining-count cap (MaxInliningId=InliningIdField::kMax), confirming the id-overflow risk; the claimed inlining-id aliasing, source-position-mapping corruption, and type-confusion manifestation are inferred, not shown by the diff, yet stated as fact.
A second, independent AI reviewer (not a human) re-derived this analysis from the fixing commit and the vendor advisory, looking for errors.
Tracker430572435
Fix commitf22ca7b61a92 (v8/v8) +9/-0
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
CreditedShaheen Fazim
Reported2025-07-09 (per vendor advisory)
Fix landed2025-07-15 (public git)
Disclosed2025-07-22
Patch gap7 days — fix public in source before the advisory

Background

`SourcePosition`
V8’s compact encoding of a source location and inlining chain, packing an inlining identity into a fixed-width bitfield.
`InliningIdField`
the bitfield inside SourcePosition that stores the inlining id, whose kMax is the largest value it can hold without overflow.
Maglev inlining
the mid-tier JIT compiler’s inlining of callees into a caller graph, where each inlined function is assigned an inlining id recorded in SourcePosition.
`MaglevGraphBuilder::CanInlineCall`
the predicate that decides whether a candidate call may be inlined, checking budgets such as cumulative bytecode size.

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 only adds an inlining-count cap (MaxInliningId=InliningIdField::kMax), confirming the id-overflow risk; the claimed inlining-id aliasing, source-position-mapping corruption, and type-confusion manifestation are inferred, not shown by the diff, yet stated as fact.

When Maglev inlined a call, each inlined function received an inlining id stored in the InliningIdField bitfield of SourcePosition, but CanInlineCall gated inlining only on budgets like total_inlined_bytecode_size() and never bounded the count of inlined functions against the bitfield’s capacity. With sufficiently many inlined callees, graph()->inlined_functions().size() could exceed InliningIdField::kMax, so the inlining id overflowed and wrapped, causing distinct inlined frames to share the same encoded inlining identity. This aliasing violated the invariant that every inlined function maps to a unique inlining id, corrupting the mapping from source positions back to their originating function and enabling type confusion.

The fix adds SourcePosition::MaxInliningId() and makes CanInlineCall refuse further inlining once inlined_functions().size() reaches that bound, setting set_could_not_inline_all_candidates() so ids stay within the bitfield’s range.

Key insight
The single mistake was inlining without checking the count of already-inlined functions against the width of the inlining-id bitfield, allowing the id to overflow; the fix caps inlining at SourcePosition::MaxInliningId() before the bitfield can wrap.

Attack Path

  1. Craft deeply inlinable JS Author JavaScript with a large graph of small, hot callees whose bytecode stays within Maglev’s cumulative size budget so CanInlineCall keeps approving them.
  2. Trigger Maglev compilation Warm the functions until Maglev compiles the caller and greedily inlines the chain of callees.
  3. Overflow the inlining id Push graph()->inlined_functions().size() past InliningIdField::kMax so the inlining id assigned into SourcePosition wraps and aliases an earlier frame.
  4. Alias source positions / identities Exploit the collided inlining id so the compiler associates code with the wrong originating function, producing a type confusion.
  5. Leverage the confusion Use the mismatched type assumptions to read or write memory with an incorrect object layout, escalating toward controlled corruption.

Impact Assessment

An attacker gains a type confusion inside the V8 engine of the renderer process, corrupting the compiler’s notion of an object’s type and enabling out-of-bounds or type-incorrect memory access. Reaching it requires only that the victim run attacker-controlled JavaScript that drives a function into Maglev compilation with enough inlinable callees to overflow the inlining-id bitfield.

Files Changed

  • src/codegen/source-position.h
  • src/maglev/maglev-graph-builder.cc

Audit Directions

  • Bitfield-bounded counters
    Audit any code that assigns an incrementing id into a fixed-width bitfield (SourcePosition, node/frame ids, register indices) but gates growth only on unrelated size or byte budgets.
  • Inlining and frame limits
    Review other CanInline* and budget checks across Maglev and TurboFan for missing caps on the count of inlined functions or deopt/frame-state slots versus their encoded field widths.
  • Overflow-to-aliasing invariants
    Look for places where exceeding a numeric field silently wraps instead of asserting, since aliased identities can degrade into type confusion rather than a clean failure.
From f22ca7b61a92d3cd2b856485a55a1519cb11b627 Mon Sep 17 00:00:00 2001
From: Toon Verwaest <[email protected]>
Date: Mon, 14 Jul 2025 17:01:40 +0200
Subject: [PATCH] [maglev] Cap inlining at MaxInliningId

Bug: 430572435
Change-Id: I4f20bad6c99e9d3d5a959cb801485dfb117e9884
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/6732846
Commit-Queue: Toon Verwaest <[email protected]>
Auto-Submit: Toon Verwaest <[email protected]>
Reviewed-by: Olivier Flückiger <[email protected]>
Cr-Commit-Position: refs/heads/main@{#101423}
---

diff --git a/src/codegen/source-position.h b/src/codegen/source-position.h
index 85dcd96..3b0ff0e 100644
--- a/src/codegen/source-position.h
+++ b/src/codegen/source-position.h
@@ -116,6 +116,8 @@
     value_ = InliningIdField::update(value_, inlining_id + 1);
   }
 
+  static constexpr int MaxInliningId() { return InliningIdField::kMax; }
+
   static const int kNotInlined = -1;
   static_assert(kNoSourcePosition == -1);
 
diff --git a/src/maglev/maglev-graph-builder.cc b/src/maglev/maglev-graph-builder.cc
index 861d64b..3d5a3a3 100644
--- a/src/maglev/maglev-graph-builder.cc
+++ b/src/maglev/maglev-graph-builder.cc
@@ -8002,6 +8002,13 @@
 
 bool MaglevGraphBuilder::CanInlineCall(compiler::SharedFunctionInfoRef shared,
                                        float call_frequency) {
+  if (static_cast<int>(graph()->inlined_functions().size()) >=
+      SourcePosition::MaxInliningId()) {
+    compilation_unit_->info()->set_could_not_inline_all_candidates();
+    TRACE_CANNOT_INLINE("maximum inlining ids");
+    return false;
+  }
+
   if (graph()->total_inlined_bytecode_size() >
       max_inlined_bytecode_size_cumulative()) {
     compilation_unit_->info()->set_could_not_inline_all_candidates();
Loading diff…

Original Bug Report

reported by [email protected]

JIT type confusion via corrupted inlining metadata



#
# Fatal error in ../../src/base/bit-field.h, line 56
# Debug check failed: is_valid(value).
#
#
#
#FailureMessage Object: 0x7bc04a228c60
==== C stack trace ===============================

    ./out/test/d8(__interceptor_backtrace+0x46) [0x564f0c1e4c96]
    /home/user/v8-bisect/v8/out/test/libv8_libbase.so(v8::base::debug::StackTrace::StackTrace()+0x13) [0x7fc077c80a23]
    /home/user/v8-bisect/v8/out/test/libv8_libplatform.so(+0x36a8a) [0x7fc077bd5a8a]
    /home/user/v8-bisect/v8/out/test/libv8_libbase.so(V8_Fatal(char const*, int, char const*, ...)+0x2a0) [0x7fc077c4b8e0]
    /home/user/v8-bisect/v8/out/test/libv8_libbase.so(+0x5699f) [0x7fc077c4a99f]
    /home/user/v8-bisect/v8/out/test/libv8.so(v8::internal::SourcePosition::SetInliningId(int)+0x140) [0x7fc07c9bc9f0]
    /home/user/v8-bisect/v8/out/test/libv8.so(v8::internal::maglev::MaglevGraphBuilder::MaglevGraphBuilder(v8::internal::LocalIsolate*, v8::internal::maglev::MaglevCompilationUnit*, v8::internal::maglev::Graph*, float, v8::internal::BytecodeOffset, bool, int, v8::internal::maglev::MaglevGraphBuilder*)+0x7b8) [0x7fc07f0758f8]
    /home/user/v8-bisect/v8/out/test/libv8.so(v8::internal::maglev::MaglevGraphBuilder::TryBuildInlinedCall(v8::internal::maglev::ValueNode*, v8::internal::maglev::ValueNode*, v8::internal::maglev::ValueNode*, v8::internal::compiler::SharedFunctionInfoRef, v8::internal::compiler::OptionalRef<v8::internal::compiler::FeedbackVectorRef>, v8::internal::maglev::CallArguments&, v8::internal::compiler::FeedbackSource const&)+0xd9b) [0x7fc07f0f2e9b]
    /home/user/v8-bisect/v8/out/test/libv8.so(v8::internal::maglev::MaglevGraphBuilder::TryBuildCallKnownJSFunction(v8::internal::maglev::ValueNode*, v8::internal::maglev::ValueNode*, v8::internal::maglev::ValueNode*, v8::internal::compiler::SharedFunctionInfoRef, v8::internal::compiler::OptionalRef<v8::internal::compiler::FeedbackVectorRef>, v8::internal::maglev::CallArguments&, v8::internal::compiler::FeedbackSource const&)+0x137) [0x7fc07f11c1d7]
    /home/user/v8-bisect/v8/out/test/libv8.so(v8::internal::maglev::MaglevGraphBuilder::TryBuildCallKnownJSFunction(v8::internal::compiler::JSFunctionRef, v8::internal::maglev::ValueNode*, v8::internal::maglev::CallArguments&, v8::internal::compiler::FeedbackSource const&)+0x2ea) [0x7fc07f11be7a]
    /home/user/v8-bisect/v8/out/test/libv8.so(v8::internal::maglev::MaglevGraphBuilder::ReduceCallForConstant(v8::internal::compiler::JSFunctionRef, v8::internal::maglev::CallArguments&, v8::internal::compiler::FeedbackSource const&)+0x3d2) [0x7fc07f0b66c2]
    /home/user/v8-bisect/v8/out/test/libv8.so(v8::internal::maglev::MaglevGraphBuilder::ReduceCall(v8::internal::maglev::ValueNode*, v8::internal::maglev::CallArguments&, v8::internal::compiler::FeedbackSource const&)+0x305) [0x7fc07f0f9625]
    /home/user/v8-bisect/v8/out/test/libv8.so(v8::internal::maglev::MaglevGraphBuilder::BuildCallWithFeedback(v8::internal::maglev::ValueNode*, v8::internal::maglev::CallArguments&, v8::internal::compiler::FeedbackSource const&)+0x395) [0x7fc07f11f965]
    /home/user/v8-bisect/v8/out/test/libv8.so(v8::internal::maglev::MaglevGraphBuilder::BuildCallFromRegisters(int, v8::internal::ConvertReceiverMode)+0x28f) [0x7fc07f12317f]
    /home/user/v8-bisect/v8/out/test/libv8.so(v8::internal::maglev::MaglevGraphBuilder::VisitSingleBytecode()+0x19df) [0x7fc07ef3db9f]
    /home/user/v8-bisect/v8/out/test/libv8.so(v8::internal::maglev::MaglevGraphBuilder::BuildBody()+0x52b) [0x7fc07ef3584b]
    /home/user/v8-bisect/v8/out/test/libv8.so(v8::internal::maglev::MaglevGraphBuilder::Build()+0x7df) [0x7fc07ef29aef]
    /home/user/v8-bisect/v8/out/test/libv8.so(v8::internal::maglev::MaglevCompiler::Compile(v8::internal::LocalIsolate*, v8::internal::maglev::MaglevCompilationInfo*)+0x15ca) [0x7fc07ef2629a]
    /home/user/v8-bisect/v8/out/test/libv8.so(v8::internal::maglev::MaglevCompilationJob::ExecuteJobImpl(v8::internal::RuntimeCallStats*, v8::internal::LocalIsolate*)+0x10b) [0x7fc07f0688db]
    /home/user/v8-bisect/v8/out/test/libv8.so(v8::internal::OptimizedCompilationJob::ExecuteJob(v8::internal::RuntimeCallStats*, v8::internal::LocalIsolate*)+0x1a4) [0x7fc07c6b2414]
    /home/user/v8-bisect/v8/out/test/libv8.so(v8::internal::maglev::MaglevConcurrentDispatcher::JobTask::Run(v8::JobDelegate*)+0x85b) [0x7fc07f06d76b]
    /home/user/v8-bisect/v8/out/test/libv8_libplatform.so(v8::platform::DefaultJobWorker::Run()+0x2b9) [0x7fc077bd28a9]
    /home/user/v8-bisect/v8/out/test/libv8_libplatform.so(v8::platform::DefaultWorkerThreadsTaskRunner::WorkerThread::Run()+0x1e8) [0x7fc077bd97e8]
    /home/user/v8-bisect/v8/out/test/libv8_libbase.so(+0x8a33e) [0x7fc077c7e33e]
    ./out/test/d8(+0x163de7) [0x564f0c23ade7]
    /lib/x86_64-linux-gnu/libc.so.6(+0x9caa4) [0x7fc0762c3aa4]
    /lib/x86_64-linux-gnu/libc.so.6(+0x129c3c) [0x7fc076350c3c]
Trace/breakpoint trap

VERSION

V8 version 13.9.0 (candidate)

REPRODUCTION CASE

Build: python3 tools/dev/gm.py x64.debug

Run: ./d8 poc.js


Reporter credit: Shaheen Fazim

View on issue tracker
Uploaded PoC files