CVE-2026-78956
Overview
Background
- Liftoff
- V8’s baseline single-pass WebAssembly compiler that emits machine code quickly, used heavily when a module is being debugged (
for_debugging_). - OSR (On-Stack Replacement)
- the mechanism that hot-swaps a running function’s execution into a freshly recompiled code object mid-execution; Liftoff records where to resume via an OSR target stored in
kOSRTargetSlot. - Exception landing pad
- the code region a Liftoff frame unwinds to when a callee throws, where the handler runs the Wasm
catchbody viaExceptionHandlerandPushException. - `ResetOSRTarget`
- a Liftoff assembler helper that clears any pending OSR target so the next instruction does not attempt to jump into a recompiled continuation.
Root Cause Analysis
When a Wasm module is debugged, setting a breakpoint in a caller while paused in a callee triggers recompilation of the caller and computes an OSR target for the call’s return continuation, stored in kOSRTargetSlot. If the callee then throws, control unwinds not to that return continuation but to the caller’s exception landing pad emitted by LiftoffCompiler, so the previously computed OSR target is stale for the path actually being taken. The vulnerable code in the landing-pad emission bound the handler, called ExceptionHandler, and proceeded to PushException without invalidating the pending OSR target, violating the invariant that an OSR target is only consumed on the normal return continuation it was computed for. As a result the next instruction could consume the stale OSR target and transfer control into a code object/continuation that does not match the current frame state, producing the type confusion.
The fix adds a guarded __ ResetOSRTarget() in the landing pad under V8_UNLIKELY(for_debugging_), clearing the slot so the catch body keeps executing in the current code object instead of jumping through a mismatched OSR continuation.
ResetOSRTarget on the exceptional path so no stale continuation is ever consumed.Attack Path
- Attacker-controlled Wasm under debug
The attacker arranges for a WebAssembly module to run with
for_debugging_Liftoff code, which is reachable through DevTools/debugger interactions the page can influence. - Force caller recompilation
A breakpoint is set in a caller function while execution is paused inside a callee, causing the caller to be recompiled and an OSR target to be written into
kOSRTargetSlotfor the call’s return continuation. - Throw from the callee The callee raises an exception so the engine unwinds to the caller’s exception landing pad instead of returning normally to the continuation the OSR target described.
- Consume the stale OSR target
Because the landing pad never cleared
kOSRTargetSlot, the next instruction consumes the stale target and transfers into a continuation mismatched with the live frame, yielding a type confusion. - Leverage the confusion The attacker uses the resulting type-confused state to corrupt memory and escalate toward arbitrary read/write in the renderer.
Impact Assessment
for_debugging_ Liftoff code) with a breakpoint set in a caller while paused in a callee, and a callee exception to force the exceptional unwind. The window is narrow but reachable via debugger-driven state, making it a serious in-process escalation primitive.Files Changed
src/wasm/baseline/liftoff-compiler.cc
Audit Directions
- Stale continuation state on exceptional pathsReview other Liftoff and baseline exception/unwind paths to confirm any OSR target, deopt data, or return-continuation slot computed for normal returns is invalidated before the handler resumes.
- Debug-only recompilation transitionsAudit code guarded by
for_debugging_where breakpoint-driven recompilation mutates per-frame slots likekOSRTargetSlot, ensuring every alternate exit (throw, tail call, trap) resets or re-derives that state. - OSR slot lifecycleTrace all writers and consumers of
kOSRTargetSlotandResetOSRTarget/SetOSRTargetto verify the slot is always cleared on paths that diverge from the continuation it was computed for.
Patch
From ef82f91627adf19af488e1190eeedfcb0d8278ff Mon Sep 17 00:00:00 2001 From: Thibaud Michaud <[email protected]> Date: Tue, 30 Jun 2026 17:13:21 +0200 Subject: [PATCH] [wasm][debug] Reset OSR target on exception landing pad in Liftoff When debugging, if we set a breakpoint in a caller function while paused in a callee, the caller function is recompiled. If the callee then throws an exception, we unwind to the caller's exception landing pad. Any pending OSR target computed for the call's return continuation is stale on the exception path. Clear it to ensure we don't consume it at the next instruction. Fixed: 520016142 Change-Id: I923c3d7d9c05ce596e5ea6e879f8ee4722f4eb85 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/8024909 Commit-Queue: Thibaud Michaud <[email protected]> Reviewed-by: Clemens Backes <[email protected]> Cr-Commit-Position: refs/heads/main@{#108357} --- diff --git a/src/wasm/baseline/liftoff-compiler.cc b/src/wasm/baseline/liftoff-compiler.cc index 1b9ec0a..681efb6 100644 --- a/src/wasm/baseline/liftoff-compiler.cc +++ b/src/wasm/baseline/liftoff-compiler.cc @@ -6139,6 +6139,16 @@ CODE_COMMENT("-- landing pad --"); __ bind(handler.get()); __ ExceptionHandler(); + if (V8_UNLIKELY(for_debugging_)) { + // Any pending OSR target was computed for the call's return continuation + // and is stale on the exception path; clear it so we keep executing the + // catch body in this code object. + // We might miss breakpoint updates by staying in the old code. + // TODO(thibaudm): Get the exception handler address in the new code and + // jump to it. Maybe by using a second OSR slot on top of kOSRTargetSlot, + // specifically for exceptional returns. + __ ResetOSRTarget(); + } __ PushException(); handlers_.push_back({std::move(handler), handler_offset}); Control* current_try =