Chrome · V8
CVE-2026-17989
Type Confusion in V8
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Files Changed
src/base/macros.hsrc/codegen/bailout-reason.hsrc/wasm/baseline/arm/liftoff-assembler-arm-inl.hsrc/wasm/baseline/arm64/liftoff-assembler-arm64-inl.hsrc/wasm/baseline/ia32/liftoff-assembler-ia32-inl.hsrc/wasm/baseline/liftoff-assembler.hsrc/wasm/baseline/liftoff-compiler.cc
Patch
From 701254bc23346416c19c6793da86ba13de35bef4 Mon Sep 17 00:00:00 2001
From: Clemens Backes <clemensb@chromium.org>
Date: Tue, 16 Jun 2026 14:35:34 +0200
Subject: [PATCH] [wasm][liftoff] Harden OSR state on x64
On x64, Liftoff uses a deferred OSR mechanism where an interrupt sets an
OSR target in a stack slot. This request must be consumed by a
MaybeOSR() call before the next instruction, otherwise type confusion
can occur if execution continues in old code with a mismatched stack.
This CL:
1. Adds missing MaybeOSR() calls after instructions that handle
interrupts (AtomicWait, MemoryGrow, TableGrow, TableCopy, StructWait,
RefTest, and various array allocation/initialization builtins).
2. Introduces a debug-only AssertOSREmpty() check at the start of every
breakable instruction (via EmitDebuggingInfo) to ensure no pending
OSR request was missed.
3. Refactors OSR-related methods to be strictly architecture-isolated
to x64, where the deferred mechanism is used.
4. Adds IF_TARGET_ARCH_X64 macro to facilitate this isolation.
R=thibaudm@chromium.org
TAG=agy
Bug: 520017306
Change-Id: Iba67891bfcd45b1c7cbf9ab03f2e511c007e0d13
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/7946779
Reviewed-by: Thibaud Michaud <thibaudm@chromium.org>
Commit-Queue: Clemens Backes <clemensb@chromium.org>
Cr-Commit-Position: refs/heads/main@{#108048}
---
diff --git a/src/base/macros.h b/src/base/macros.h
index 4355dec8..c2c24e8 100644
--- a/src/base/macros.h
+++ b/src/base/macros.h
@@ -606,6 +606,15 @@
#define IF_TARGET_ARCH_32_BIT(V, ...) EXPAND(V(__VA_ARGS__))
#endif // V8_TARGET_ARCH_64_BIT
+// Defines IF_TARGET_ARCH_X64, to be used in macro lists for elements that
+// should only be there if the target architecture is x64.
+#if V8_TARGET_ARCH_X64
+// EXPAND is needed to work around MSVC's broken __VA_ARGS__ expansion.
+#define IF_TARGET_ARCH_X64(V, ...) EXPAND(V(__VA_ARGS__))
+#else
+#define IF_TARGET_ARCH_X64(V, ...)
+#endif // V8_TARGET_ARCH_X64
+
// Defines IF_V8_WASM_RANDOM_FUZZERS and IF_NO_V8_WASM_RANDOM_FUZZERS, to be
// used in macro lists for elements that should only be there/absent when
// building the Wasm fuzzers.
diff --git a/src/codegen/bailout-reason.h b/src/codegen/bailout-reason.h
index bdae933..be534d6 100644
--- a/src/codegen/bailout-reason.h
+++ b/src/codegen/bailout-reason.h
@@ -129,6 +129,8 @@
V(k32BitValueInRegisterIsNotSignExtended, \
"32 bit value in register is not sign-extended") \
V(kUnexpectedSandboxMode, "The sandboxing mode is not as expected") \
+ IF_TARGET_ARCH_X64(V, kOSREmptyCheckFailed, \
+ "OSR target slot should be empty") \
V(kLastReason, "")
#define TERMINAL_BAILOUT_MESSAGES_LIST(V) \
diff --git a/src/wasm/baseline/arm/liftoff-assembler-arm-inl.h b/src/wasm/baseline/arm/liftoff-assembler-arm-inl.h
index 1008602..3de6328 100644
--- a/src/wasm/baseline/arm/liftoff-assembler-arm-inl.h
+++ b/src/wasm/baseline/arm/liftoff-assembler-arm-inl.h
@@ -5134,7 +5134,6 @@
add(sp, sp, Operand(size));
}
-void LiftoffAssembler::MaybeOSR() {}
void LiftoffStackSlots::Construct(int param_slots) {
DCHECK_LT(0, slots_.size());
diff --git a/src/wasm/baseline/arm64/liftoff-assembler-arm64-inl.h b/src/wasm/baseline/arm64/liftoff-assembler-arm64-inl.h
index 1ebd338a..a2c220c 100644
--- a/src/wasm/baseline/arm64/liftoff-assembler-arm64-inl.h
+++ b/src/wasm/baseline/arm64/liftoff-assembler-arm64-inl.h
@@ -4659,7 +4659,6 @@
Drop(size, 1);
}
-void LiftoffAssembler::MaybeOSR() {}
void LiftoffStackSlots::Construct(int param_slots) {
DCHECK_LT(0, slots_.size());
diff --git a/src/wasm/baseline/ia32/liftoff-assembler-ia32-inl.h b/src/wasm/baseline/ia32/liftoff-assembler-ia32-inl.h
index c1b7750..de73188 100644
--- a/src/wasm/baseline/ia32/liftoff-assembler-ia32-inl.h
+++ b/src/wasm/baseline/ia32/liftoff-assembler-ia32-inl.h
@@ -5343,7 +5343,6 @@
add(esp, Immediate(size));
}
-void LiftoffAssembler::MaybeOSR() {}
void LiftoffStackSlots::Construct(int param_slots) {
DCHECK_LT(0, slots_.size());
diff --git a/src/wasm/baseline/liftoff-assembler.h b/src/wasm/baseline/liftoff-assembler.h
index 966498d..3d1b96c 100644
--- a/src/wasm/baseline/liftoff-assembler.h
+++ b/src/wasm/baseline/liftoff-assembler.h
@@ -1639,8 +1639,11 @@
inline void AllocateStackSlot(Register addr, uint32_t size);
inline void DeallocateStackSlot(uint32_t size);
+#if V8_TARGET_ARCH_X64
// Instrumentation for shadow-stack-compatible OSR on x64.
inline void MaybeOSR();
+ inline void AssertOSREmpty();
+#endif
inline bool supports_f16_mem_access();
diff --git a/src/wasm/baseline/liftoff-compiler.cc b/src/wasm/baseline/liftoff-compiler.cc
index 91b5892..5b178d9 100644
--- a/src/wasm/baseline/liftoff-compiler.cc
+++ b/src/wasm/baseline/liftoff-compiler.cc
@@ -1556,6 +1556,12 @@
V8_NOINLINE void EmitDebuggingInfo(FullDecoder* decoder, WasmOpcode opcode) {
DCHECK(for_debugging_);
+#if V8_TARGET_ARCH_X64
+ // If OSR is triggered during a builtin call, it must be consumed by
+ // {MaybeOSR} before the next instruction.
+ __ AssertOSREmpty();
+#endif
+
if (V8_UNLIKELY(v8_flags.wasm_code_coverage && (opcode != kExprLoop))) {
// Coverage instrumention for 'loop' instructions is emitted in Loop().
EmitCoverageInstrumentationIfReachable(decoder, opcode);
@@ -4649,6 +4655,7 @@
__ CallBuiltin(Builtin::kWasmMemoryGrow);
DefineSafepoint();
RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill);
+ MaybeOSR();
if (kReturnRegister0 != result.gp()) {
__ Move(result.gp(), kReturnRegister0, kI32);
@@ -6531,6 +6538,7 @@
RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill);
__ PushRegister(kI32, LiftoffRegister(kReturnRegister0));
+ MaybeOSR();
}
void AtomicNotify(FullDecoder* decoder, const MemoryAccessImmediate& imm) {
@@ -6956,6 +6964,7 @@
VarState{kRef, LiftoffRegister{kReturnRegister0}, 0}},
decoder->position());
__ PushRegister(kI32, LiftoffRegister{kReturnRegister0});
+ MaybeOSR();
}
void WaitqueueNotify(FullDecoder* decoder, const Value& waitqueue,
@@ -7649,6 +7658,7 @@
decoder->position());
RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill);
+ MaybeOSR();
}
void TableGrow(FullDecoder* decoder, const TableIndexImmediate& imm,
@@ -7672,6 +7682,7 @@
decoder->position());
RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill);
+ MaybeOSR();
__ SmiToInt32(kReturnRegister0);
if (imm.table->is_table64()) {
LiftoffRegister result64 = LiftoffRegister(kReturnRegister0);
@@ -7995,6 +8006,7 @@
__ cache_state()->stack_state.end()[-1], // length
VarState{kI32, elem_size, 0}},
decoder->position());
+ MaybeOSR();
}
LiftoffRegister obj(kReturnRegister0);
@@ -8250,6 +8262,7 @@
{VarState{kRef, rtt, 0}, VarState{kI32, elem_count, 0},
VarState{kI32, value_kind_size(elem_kind), 0}},
decoder->position());
+ MaybeOSR();
// Initialize the array with stack arguments.
LiftoffRegister array(kReturnRegister0);
@@ -8418,6 +8431,8 @@
// Pop parameters from the value stack.
__ DropValues(2);
+
+ MaybeOSR();
RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill);
LiftoffRegister result(kReturnRegister0);
Loading diff…
Original Bug Report
The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.
References
On This Page