CVE-2026-19151
Overview
Background
- Write barrier
- A code sequence the garbage collector requires whenever a pointer into the heap is stored, recording the mutated slot so the GC can keep its remembered sets and mark state consistent.
- Register aliasing
- On arm64 the same physical register is exposed as both a 64-bit
Xview and a 32-bitWview, sow3andx3are distinctRegisterobjects that nonetheless refer to the same hardware register. - `MoveObjectAndSlot`
- A
MacroAssemblerhelper that, given a destination object register, a destination slot register, and anOperandoffset, computes the slot address (object + offset) while carefully arranging which register may be overwritten. - `Operand` extend
- An arm64 addressing feature (
UXTW,SXTW, etc.) that lets a 32-bitWregister be used as an offset with automatic extension, meaning the offset register can legitimately be theWalias of a register already in use.
Root Cause Analysis
The MoveObjectAndSlot helper in the arm64 MacroAssembler decides how to compute a slot address based on whether the offset register overlaps the object registers, and it performed those overlap checks with plain Register inequality (offset.reg() != object). That comparison is exact and treats w3 as different from x3, so when the write-barrier emission supplied the offset as a W register that was the 32-bit alias of the destination object register, the code took the “no overlap” branch and clobbered the register before it had finished using its value.
The fix replaces every exact comparison with Register::Aliases, which returns true when two register operands map to the same underlying hardware register regardless of width, and it rewrites the register-swap sequence to add and subtract the full offset operand (respecting its extend) instead of the bare dst_object register. This restores the invariant that the offset register is never treated as free while it still holds a needed value, so the computed slot address passed into the write barrier is correct. Because a wrong slot address corrupts the GC’s remembered-set bookkeeping, the corrected aliasing checks prevent the collector from later operating on a stale or freed object.
!= instead of Aliases, which let the 32-bit W alias of an in-use X register slip through as “non-overlapping”; the fix uses Register::Aliases (and reuses the full offset operand in the swap) so width-aliased registers are correctly recognized as the same register.Attack Path
- Craft a WebAssembly GC array
Build a module with an
anyrefarray type and export helpersmkarrandfill, then allocate an array and force it into old space with repeatedgc()calls so a real write barrier is required on subsequent stores. - Trigger the aliased offset emission
Invoke
array.fill(kExprArrayFill) on the promoted array so the arm64 backend emitsMoveObjectAndSlotwith the element offset passed as aUXTW-extendedWregister that aliases the destination object register. - Miscompute the slot address
The faulty
offset.reg() != dst_objectcheck takes the wrong branch and overwrites the object register with the extend applied incorrectly, so the write barrier records or updates the wrong slot address. - Corrupt GC metadata The garbage collector acts on the bad slot, leaving a live reference untracked or a remembered-set entry pointing at the wrong memory, which lets an object be freed while still reachable.
- Exploit the dangling reference A subsequent access to the prematurely freed object yields a use-after-free that an attacker grooms into a controlled read/write primitive.
Impact Assessment
MoveObjectAndSlot is emitted with a W-aliased offset register, as produced by the array.fill path in the regression test.Changed Functions
| Function | Change | Notes |
|---|---|---|
iftest/unittests/assembler/macro-assembler-arm64-unittest.cc |
modified |
Files Changed
test/unittests/assembler/macro-assembler-arm64-unittest.ccsrc/codegen/arm64/macro-assembler-arm64.cctest/mjsunit/regress/wasm/regress-530663440.js
Audit Directions
- Register identity comparisonsSearch arm64 (and other multi-width) codegen for
==/!=onRegisteroperands and confirm they useAliaseswherever aW/Xalias could reach the same code path. - Offset operand reuseReview helpers that overwrite an input register based on overlap checks, verifying they operate on the full
Operand(including extend) rather than the bare register when a value is still live. - Write-barrier address computationAudit every backend site that computes a slot address for a write barrier, since a miscomputed slot silently corrupts remembered sets and manifests only later as a use-after-free.
Patch
From 2c3e7f7d4d65f274951d58eec078b2aacdfba58e Mon Sep 17 00:00:00 2001
From: Jakob Kummerow <jkummerow@chromium.org>
Date: Tue, 28 Jul 2026 12:51:35 +0200
Subject: [PATCH] [arm64] Fix write barrier register aliasing
Use `r1.Aliases(r2)` to handle W and X registers correctly.
Fixed: 530663440
Change-Id: Ib17e8f186bbea17d2b845202399ce39d21dbf107
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/8158501
Auto-Submit: Jakob Kummerow <jkummerow@chromium.org>
Reviewed-by: Manos Koukoutos <manoskouk@chromium.org>
Commit-Queue: Manos Koukoutos <manoskouk@chromium.org>
Cr-Commit-Position: refs/heads/main@{#108911}
---
diff --git a/src/codegen/arm64/macro-assembler-arm64.cc b/src/codegen/arm64/macro-assembler-arm64.cc
index 0da0f6d..f6af185 100644
--- a/src/codegen/arm64/macro-assembler-arm64.cc
+++ b/src/codegen/arm64/macro-assembler-arm64.cc
@@ -4223,7 +4223,7 @@
ASM_CODE_COMMENT(this);
DCHECK_NE(dst_object, dst_slot);
// If `offset` is a register, it cannot overlap with `object`.
- DCHECK_IMPLIES(!offset.IsImmediate(), offset.reg() != object);
+ DCHECK_IMPLIES(!offset.IsImmediate(), !offset.reg().Aliases(object));
// If the slot register does not overlap with the object register, we can
// overwrite it.
@@ -4237,19 +4237,19 @@
// If the destination object register does not overlap with the offset
// register, we can overwrite it.
- if (offset.IsImmediate() || (offset.reg() != dst_object)) {
+ if (offset.IsImmediate() || !offset.reg().Aliases(dst_object)) {
Mov(dst_object, dst_slot);
Add(dst_slot, dst_slot, offset);
return;
}
- DCHECK_EQ(dst_object, offset.reg());
+ DCHECK(dst_object.Aliases(offset.reg()));
// We only have `dst_slot` and `dst_object` left as distinct registers so we
// have to swap them. We write this as a add+sub sequence to avoid using a
// scratch register.
- Add(dst_slot, dst_slot, dst_object);
- Sub(dst_object, dst_slot, dst_object);
+ Add(dst_slot, dst_slot, offset);
+ Sub(dst_object, dst_slot, offset);
}
// If lr_status is kLRHasBeenSaved, lr will be clobbered.
diff --git a/test/mjsunit/regress/wasm/regress-530663440.js b/test/mjsunit/regress/wasm/regress-530663440.js
new file mode 100644
index 0000000..6e67423
--- /dev/null
+++ b/test/mjsunit/regress/wasm/regress-530663440.js
@@ -0,0 +1,36 @@
+// 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: --expose-gc
+
+d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js');
+
+const builder = new WasmModuleBuilder();
+const arrT = builder.addArray(kWasmAnyRef);
+
+let fill_sig = makeSig(
+ [kWasmI32, kWasmI32, kWasmI32, kWasmI32, wasmRefType(arrT), kWasmAnyRef],
+ []);
+builder.addFunction('fill', fill_sig).exportFunc().addBody([
+ kExprLocalGet, 4,
+ kExprLocalGet, 0,
+ kExprI32Eqz,
+ kExprLocalGet, 5,
+ kExprI32Const, 1,
+ kGCPrefix, kExprArrayFill, arrT,
+ ]);
+
+builder.addFunction('mkarr', makeSig([kWasmI32], [kWasmAnyRef])).exportFunc()
+ .addBody([
+ kExprLocalGet, 0,
+ kGCPrefix, kExprArrayNewDefault, arrT,
+ ]);
+
+const instance = builder.instantiate();
+
+const arr = instance.exports.mkarr(4);
+gc(); // Promote {arr} to old space.
+gc();
+
+instance.exports.fill(1, 0, 0, 0, arr, {});
diff --git a/test/unittests/assembler/macro-assembler-arm64-unittest.cc b/test/unittests/assembler/macro-assembler-arm64-unittest.cc
index 26a1e3b..2ef1b31 100644
--- a/test/unittests/assembler/macro-assembler-arm64-unittest.cc
+++ b/test/unittests/assembler/macro-assembler-arm64-unittest.cc
@@ -141,25 +141,33 @@
Register dst_slot;
Register object;
Register offset_register = no_reg;
+ Extend offset_extend = NO_EXTEND;
};
const MoveObjectAndSlotTestCase kMoveObjectAndSlotTestCases[] = {
{"no overlap", x0, x1, x2},
{"no overlap", x0, x1, x2, x3},
+ {"no overlap", x0, x1, x2, w3, UXTW},
{"object == dst_object", x2, x1, x2},
{"object == dst_object", x2, x1, x2, x3},
+ {"object == dst_object", x2, x1, x2, w3, UXTW},
{"object == dst_slot", x1, x2, x2},
{"object == dst_slot", x1, x2, x2, x3},
+ {"object == dst_slot", x1, x2, x2, w3, UXTW},
{"offset == dst_object", x0, x1, x2, x0},
+ {"offset == dst_object", x0, x1, x2, w0, UXTW},
{"offset == dst_object && object == dst_slot", x0, x1, x1, x0},
+ {"offset == dst_object && object == dst_slot", x0, x1, x1, w0, UXTW},
{"offset == dst_slot", x0, x1, x2, x1},
+ {"offset == dst_slot", x0, x1, x2, w1, UXTW},
- {"offset == dst_slot && object == dst_object", x0, x1, x0, x1}};
+ {"offset == dst_slot && object == dst_object", x0, x1, x0, x1},
+ {"offset == dst_slot && object == dst_object", x0, x1, x0, w1, UXTW}};
// Make sure we include offsets that cannot be encoded in an add instruction.
const int kOffsets[] = {0, 42, kMaxRegularHeapObjectSize, 0x101001};
@@ -194,7 +202,12 @@
offset_operand = Operand(offset);
} else {
__ Mov(test_case.offset_register, Operand(offset));
- offset_operand = Operand(test_case.offset_register);
+ if (test_case.offset_extend == NO_EXTEND) {
+ offset_operand = Operand(test_case.offset_register);
+ } else {
+ offset_operand =
+ Operand(test_case.offset_register, test_case.offset_extend);
+ }
}
std::stringstream comment;
Regression Test / PoC
diff --git a/test/mjsunit/regress/wasm/regress-530663440.js b/test/mjsunit/regress/wasm/regress-530663440.js
new file mode 100644
index 0000000..6e67423
--- /dev/null
+++ b/test/mjsunit/regress/wasm/regress-530663440.js
@@ -0,0 +1,36 @@
+// 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: --expose-gc
+
+d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js');
+
+const builder = new WasmModuleBuilder();
+const arrT = builder.addArray(kWasmAnyRef);
+
+let fill_sig = makeSig(
+ [kWasmI32, kWasmI32, kWasmI32, kWasmI32, wasmRefType(arrT), kWasmAnyRef],
+ []);
+builder.addFunction('fill', fill_sig).exportFunc().addBody([
+ kExprLocalGet, 4,
+ kExprLocalGet, 0,
+ kExprI32Eqz,
+ kExprLocalGet, 5,
+ kExprI32Const, 1,
+ kGCPrefix, kExprArrayFill, arrT,
+ ]);
+
+builder.addFunction('mkarr', makeSig([kWasmI32], [kWasmAnyRef])).exportFunc()
+ .addBody([
+ kExprLocalGet, 0,
+ kGCPrefix, kExprArrayNewDefault, arrT,
+ ]);
+
+const instance = builder.instantiate();
+
+const arr = instance.exports.mkarr(4);
+gc(); // Promote {arr} to old space.
+gc();
+
+instance.exports.fill(1, 0, 0, 0, arr, {});
diff --git a/test/unittests/assembler/macro-assembler-arm64-unittest.cc b/test/unittests/assembler/macro-assembler-arm64-unittest.cc
index 26a1e3b..2ef1b31 100644
--- a/test/unittests/assembler/macro-assembler-arm64-unittest.cc
+++ b/test/unittests/assembler/macro-assembler-arm64-unittest.cc
@@ -141,25 +141,33 @@
Register dst_slot;
Register object;
Register offset_register = no_reg;
+ Extend offset_extend = NO_EXTEND;
};
const MoveObjectAndSlotTestCase kMoveObjectAndSlotTestCases[] = {
{"no overlap", x0, x1, x2},
{"no overlap", x0, x1, x2, x3},
+ {"no overlap", x0, x1, x2, w3, UXTW},
{"object == dst_object", x2, x1, x2},
{"object == dst_object", x2, x1, x2, x3},
+ {"object == dst_object", x2, x1, x2, w3, UXTW},
{"object == dst_slot", x1, x2, x2},
{"object == dst_slot", x1, x2, x2, x3},
+ {"object == dst_slot", x1, x2, x2, w3, UXTW},
{"offset == dst_object", x0, x1, x2, x0},
+ {"offset == dst_object", x0, x1, x2, w0, UXTW},
{"offset == dst_object && object == dst_slot", x0, x1, x1, x0},
+ {"offset == dst_object && object == dst_slot", x0, x1, x1, w0, UXTW},
{"offset == dst_slot", x0, x1, x2, x1},
+ {"offset == dst_slot", x0, x1, x2, w1, UXTW},
- {"offset == dst_slot && object == dst_object", x0, x1, x0, x1}};
+ {"offset == dst_slot && object == dst_object", x0, x1, x0, x1},
+ {"offset == dst_slot && object == dst_object", x0, x1, x0, w1, UXTW}};
// Make sure we include offsets that cannot be encoded in an add instruction.
const int kOffsets[] = {0, 42, kMaxRegularHeapObjectSize, 0x101001};
@@ -194,7 +202,12 @@
offset_operand = Operand(offset);
} else {
__ Mov(test_case.offset_register, Operand(offset));
- offset_operand = Operand(test_case.offset_register);
+ if (test_case.offset_extend == NO_EXTEND) {
+ offset_operand = Operand(test_case.offset_register);
+ } else {
+ offset_operand =
+ Operand(test_case.offset_register, test_case.offset_extend);
+ }
}
std::stringstream comment;
Original Bug Report
Missed write-barrier UAF via register aliasing flaw in ARM64 MoveObjectAndSlot
Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports.
Overview: In the ARM64 macro-assembler, MoveObjectAndSlot incorrectly uses CPURegister::operator!= to check for register aliasing. Because this operator includes a register size check, a 32-bit W-register offset mis-evaluates as distinct from its 64-bit X-register counterpart. This causes Liftoff write barriers to clobber the offset register, passing a corrupted slot address to the RecordWrite stub and potentially leading to an in-cage Use-After-Free.
Affected files:
src/codegen/arm64/macro-assembler-arm64.ccsrc/codegen/arm64/register-arm64.h
1. Summary of the Issue (Meant for Human Triage)
A potential layer-1 vulnerability exists in V8’s ARM64 macro-assembler due to a flawed register aliasing check in MacroAssembler::MoveObjectAndSlot. The function uses the standard equality operator (operator!=) on CPURegister objects to determine whether the offset register aliases the destination dst_object register before emitting instructions to set up the write barrier stub arguments.
However, CPURegister::operator== strictly compares the register’s size (reg_size_) alongside its code and type. Consequently, a 32-bit W register and a 64-bit X register with the same physical register code (e.g., w1 and x1) will evaluate as unequal (w1 != x1 returns true).
When V8’s baseline WebAssembly compiler (Liftoff) emits a write barrier for a Wasm GC array operation (like array.fill) using a 32-bit offset (e.g., Operand(w1, UXTW)), it passes this Operand down to the RecordWrite stub. If the register allocator assigns x1 as the destination object and x5 as the slot, MoveObjectAndSlot mis-evaluates the aliasing condition and mistakenly clobbers the w1 offset register with the 64-bit array pointer before the offset is read. This forces the RecordWrite stub to calculate and record a corrupted slot address (cage_base + 2 * compressed(array)). Because the correct slot is never recorded in the garbage collector’s remembered set, any young-generation object written to the array will not be tracked. Upon the next minor GC, this unrecorded old-to-young edge leads to a stale pointer, exposing an in-cage Use-After-Free (UAF) primitive.
2. Proof-of-Concept & Detailed Execution Flow
Note: Our tooling agent does not have the ability to run code. The following execution flow traces the verified mechanism in the codebase, and the steps below represent the potential attack path to trigger the vulnerability.
The Defect:
The issue resides in src/codegen/arm64/macro-assembler-arm64.cc inside MoveObjectAndSlot:
// src/codegen/arm64/macro-assembler-arm64.cc:4221-4253
void MacroAssembler::MoveObjectAndSlot(Register dst_object, Register dst_slot,
Register object, Operand offset) {
...
DCHECK_IMPLIES(!offset.IsImmediate(), offset.reg() != object); // Passes
if (dst_slot != object) { ... return; } // Skips if equal
DCHECK_EQ(dst_slot, object);
// BUG: operator!= checks reg_size_. w1 != x1 evaluates to true.
if (offset.IsImmediate() || (offset.reg() != dst_object)) {
Mov(dst_object, dst_slot); // x1 := x5 (Clobbers w1!)
Add(dst_slot, dst_slot, offset); // reads clobbered w1
return;
}
...
}
The check relies on operator!=, defined in src/codegen/arm64/register-arm64.h:160-165:
constexpr bool operator==(const CPURegister& other) const {
return RegisterBase::operator==(other) && reg_size_ == other.reg_size_ &&
reg_type_ == other.reg_type_;
}
Because w1.reg_size_ == 32 and x1.reg_size_ == 64, w1 != x1 evaluates to true.
Potential Trigger Steps & Execution Trace:
- An attacker compiles a Wasm module defining an array of mutable references:
(type $arr (array (mut anyref))). - The attacker grooms the V8 sandbox memory to allocate an
ArrayBufferbacking store mapped into the[4GB, 8GB)range, filling it with forged 64-bit values that decode as old-generation, non-shared heap pages. - The attacker instantiates a Wasm array of type
$arr, ensuring it survives to the old generation. - The attacker crafts a function invoking
array.fillon the old-generation array, filling it with a young-generation reference. - The attacker shapes the preceding Wasm instructions to force Liftoff to allocate the array object to the 5th general-purpose parameter (
kGpParamRegisters[5]=x5) and the 32-bit index operand to the 1st return value of a preceding call (kGpReturnRegisters[1]=x1). - The attacker ensures incremental marking is active (
v8_flags.incremental_marking). - During Liftoff compilation,
ArrayFillImplinvokesStoreTaggedPointer(dst_addr=x5, offset_reg=x1, offset_imm=0, src, ...)(src/wasm/baseline/arm64/liftoff-assembler-arm64-inl.h:694). - Liftoff constructs a 32-bit offset
Operand(offset_reg.W(), UXTW)which resolves toOperand(w1, UXTW). - The store is emitted:
StoreTaggedField(src, MemOperand(x5, Operand(w1, UXTW))). The young-generation reference is written to the correct array slot. - A write barrier is emitted:
CallRecordWriteStubSaveRegisters(x5, Operand(w1, UXTW), ...). - The generated code enters
MoveObjectAndSlotto arrange arguments.dst_objectmust bex1anddst_slotmust bex5(WriteBarrierDescriptor::registers()).MoveObjectAndSlotreceivesdst_object=x1,dst_slot=x5,object=x5,offset=Operand(w1, UXTW). - The check
offset.reg() != dst_objectevaluatesw1 != x1, which erroneously evaluates totrue. Mov(x1, x5)executes. The 64-bit array pointer inx5clobbers the lower 32 bits (w1) inx1.Add(x5, x5, w1, uxtw)executes.x5becomesx5 + UXTW(w1). Due to pointer compression,x5storescage_base + 2 * compressed(array) + 2 * kHeapObjectTag.- The
RecordWritestub (src/builtins/builtins-internal-gen.cc:242) receives this corrupted slot address. - Because the address lands within the groomed
ArrayBufferin the[4GB, 8GB)range, the loadedvaluedecodes as an old-generation, non-shared page.InYoungGeneration(:367) andInSharedHeap(:376) both return false. - The stub falls through to
IncrementalWriteBarrierMinor(:422), which takes no action on old-gen values. The stub returns cleanly without crashing and without recording the actual array slot. - On the next minor GC, the young-generation reference is scavenged/reclaimed. Because the slot was not recorded, it is not updated by the GC, leaving a dangling stale pointer.
- A subsequent Wasm
array.getinstruction dereferences the stale pointer, achieving an in-cage UAF primitive.
Suggested Fix:
In src/codegen/arm64/macro-assembler-arm64.cc, line 4240 (and 4226) should be updated to use CPURegister::Aliases instead of !=:
if (offset.IsImmediate() || (!offset.reg().Aliases(dst_object))) {
Additionally, the swap-case at :4251-4252 must be adjusted. It currently naively assumes dst_object and offset are identical without their extension/shift operand modifiers. A complete fix requires converting both != checks to !Aliases and utilizing a scratch register to safely handle extended operands during swaps.
3. Technical Verification Details (Automated Audit Logs - Reviewers may skip this section)
Prior Critic Verdict:
“The report accurately details a layer-1 bug in MacroAssembler::MoveObjectAndSlot on arm64 (classes H and I). The bug stems from using CPURegister::operator!=, which checks reg_size_, to determine if the offset register aliases the destination object register. A W-register offset (e.g., w1) will not match its corresponding X-register (x1). When Liftoff emits a write barrier and passes a W-register offset with UXTW (e.g. in array.fill), MoveObjectAndSlot mis-evaluates the condition and clobbers the offset register with a Mov before reading it with an Add.
Consequently, the RecordWrite stub receives the wrong slot address (array + compressed(array) instead of array + offset). The correct slot is never recorded in the remembered set. This unrecorded old-to-young edge leads to a stale pointer and an in-cage Use-After-Free (UAF) upon the next minor GC.
This is a layer-1 write primitive on a tier-1 architecture with default flags. The baseline severity for an in-cage UAF is high (1). However, the report correctly applies the ‘contrived-but-plausible’ modifier, downgrading the severity to medium (2) due to two significant constraints:
- Reachability depends on a highly specific Liftoff register allocation (
dst_addr=x5andoffset_reg=x1). While the report’s trace ofPopToRegistermakes this theoretically plausible, achieving it requires a precisely crafted Wasm module. - The miscalculated slot address often triggers a release crash (e.g., failing the
CSA_CHECKatbuiltins-internal-gen.cc:211or hitting an unmapped page), narrowing the exploitation window. Exploitation requires incremental marking to be active and the garbage slot to reside within the mapped cage and decode as an old-gen non-shared page.
All codebase citations, including the operator!= semantics and the MoveObjectAndSlot logic, were verified. The severity rating is sound and adheres to the classification criteria.”
Audit Logs & Code-Reachability Proofs:
CPURegister::operator!=: Confirmed atsrc/codegen/arm64/register-arm64.h:160-165. Comparesreg_size_.Aliases()is present at:156and correctly ignoresreg_size_.- Operand Registration:
Operand::reg()verified to return the exact 32-bitWregister instance atsrc/codegen/arm64/assembler-arm64-inl.h:375-378. - Liftoff Offset Emission: Verified at
src/wasm/baseline/arm64/liftoff-assembler-arm64-inl.h:699. Whenoffset_imm==0, Liftoff explicitly createsOperand(offset_reg.W(), UXTW). - ArrayFillImpl Logic: Confirmed at
src/wasm/baseline/liftoff-compiler.cc:11020callingStoreObjectField(..., obj.gp(), offset.gp(), 0, ...), mapping perfectly to theoffset_imm==0trigger condition. - Register Assignment Trace: Verified
kGpParamRegisters[5]=x5andkGpReturnRegisters[1]=x1atsrc/wasm/wasm-linkage.h:65-66. Liftoff’sPopToRegisterandPopToModifiableRegisterdeterministically assign based on virtual stack cache locations (src/wasm/baseline/liftoff-assembler.h:433-452). - WriteBarrier Stub Fast-Paths: Traced via
builtins-internal-gen.cc:362through:422. Incremental marking triggersInYoungGeneration(value, ...)andInSharedHeap(value, ...). If a controlled value (via ArrayBuffer overlap) is read that tests false for both, the stub returns cleanly without invokingCSA_CHECK(bucket_index < num_buckets)at:211. - Environmental Assumptions: The severity evaluation relies on standard 64-bit sandbox pointer compression behavior where
low32(x5) == compressed(array). - Limitation Caveat: The validator acknowledges
[repro-not-constructed]regarding the exact Wasm bytecode sequence required to forcex5andx1allocation, but accepts it as mechanistically feasible through Liftoff’s single-pass cache state resolution.
Evaluated with target repo at version: HEAD (Google3)
Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:
- If you are familiar with the severity guidelines, you may adjust the severity.
- If this is a false positive, and there’s no work to be done, please close as WAI.
- If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.
Data from false positives will be used to improve accuracy over time.
Note: This bug has been automatically redirected to the top-level Chromium component. Please move it to the actual component: https://b.corp.google.com/components/1456566 once PoCs have been generated.