CVE-2025-8011
Overview
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
SourcePositionthat stores the inlining id, whosekMaxis 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
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.
SourcePosition::MaxInliningId() before the bitfield can wrap.Attack Path
- Craft deeply inlinable JS
Author JavaScript with a large graph of small, hot callees whose bytecode stays within Maglev’s cumulative size budget so
CanInlineCallkeeps approving them. - Trigger Maglev compilation Warm the functions until Maglev compiles the caller and greedily inlines the chain of callees.
- Overflow the inlining id
Push
graph()->inlined_functions().size()pastInliningIdField::kMaxso the inlining id assigned intoSourcePositionwraps and aliases an earlier frame. - Alias source positions / identities Exploit the collided inlining id so the compiler associates code with the wrong originating function, producing a type confusion.
- Leverage the confusion Use the mismatched type assumptions to read or write memory with an incorrect object layout, escalating toward controlled corruption.
Impact Assessment
Files Changed
src/codegen/source-position.hsrc/maglev/maglev-graph-builder.cc
Audit Directions
- Bitfield-bounded countersAudit 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 limitsReview 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 invariantsLook 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.
Patch
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();
Original Bug Report
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