CVE-2026-6307 (Part 3): Escaping the V8 Sandbox Through WasmFX

CVE-2026-6307 (Part 3): Escaping the V8 Sandbox Through WasmFX

May 21, 2026

Authored by stratan

At this point, we are still inside the V8 heap sandbox. CVE-2026-6307 gives us addroffakeobj, and in-cage read/write.

For native code execution, we need a way out. We’ll use Chromium bug 502229895 for that step. WasmFX is still an experimental feature (--experimental-wasm-wasmfx) and thus safe for our demo, still present in V8 revision 09c9603016d80cc7a8ab9017e5ebbb57bc315941, and finally it’s an interesting upcoming feature/surface. More specifically, bug 502229895 is a WasmFX continuation type confusion. With one in-cage write, we can swap a continuation-reference field and make resume run with the wrong continuation signature. As we go through the sections, we’ll turn this into an out-of-cage (native) read/write, as well as code execution.

At this point, we are still inside the V8 heap sandbox. CVE-2026-6307 gives us addroffakeobj, and in-cage read/write.

For native code execution, we need a way out. We’ll use Chromium bug 502229895 for that step. WasmFX is still an experimental feature (--experimental-wasm-wasmfx) and thus safe for our demo, still present in V8 revision 09c9603016d80cc7a8ab9017e
5ebbb57bc315941
, and finally it’s an interesting upcoming feature/surface. More specifically, bug 502229895 is a WasmFX continuation type confusion. With one in-cage write, we can swap a continuation-reference field and make resume run with the wrong continuation signature. As we go through the sections, we’ll turn this into an out-of-cage (native) read/write, as well as code execution.

Disclaimer

This article has been prepared for educational and informational purposes in the field of cybersecurity.

The vulnerability analyzed (CVE-2026-6307) was publicly identified, is known to the vendor, and has been fully patched in current versions of the affected software (fixed in revision 1b0cbb19f825, released on Tue Mar 31 11:05:12 2026).

The content is intended for information security professionals and does not constitute a functional exploitation guide nor does it provide tools to compromise third-party systems.

Tashita Software Security condemns any use of this information for unlawful purposes.

Unauthorized access to computer systems constitutes a criminal offense under the Spanish Penal Code.

A Short Detour Through WasmFX

WasmFX adds stack switching to WebAssembly. According to the stack-switching proposal:

This proposal adds typed stack-switching to WebAssembly, enabling a single WebAssembly instance to manage multiple execution stacks concurrently.

In simple terms, one Wasm instance can manage multiple execution stacks. That is useful for coroutines, async runtimes, generators, lightweight threads, and effect handlers.

The proposal’s star is the continuation:

A continuation represents a snapshot of execution on a particular stack.

A continuation can be created, resumed, and suspended again. resume continues a suspended stack. suspend returns control to the handler installed by the resume site.

For instance:

Copy to Clipboard

In V8, the WasmFX instructions are listed in wasm-opcodes.h:

Copy to Clipboard

V8 describes WasmContinuationObject as the “token used” for the suspend/resume flow:

Copy to Clipboard

The proposal defines cont.new as the instruction that turns a function reference into a suspended continuation:

Copy to Clipboard

V8 implements that instruction in the decoder. cont.new checks that WasmFX is enabled, pops the function reference, and pushes a continuation reference:

Copy to Clipboard

After decoding cont.new, the continuation needs its actual runtime state. Runtime_WasmAllocateContinuation allocates a wasm::StackMemory from the stack pool and creates a WasmStackObject that points to it:

Copy to Clipboard

The function signature decides the argument-buffer size. The buffer is placed at the top of the new stack and stored in StackMemory. Finally, the runtime marks the jump buffer as suspended and sets the initial PC to the stack-entry wrapper. So cont.new returns a continuation reference, but that reference has native stack state behind it.

The other instruction we care about is resume. The proposal gives its type like this:

Copy to Clipboard

and then states:

The resume instruction is parameterised by a continuation type and a handler dispatch table hdl.

So the continuation type $ct decides the arguments passed into the resumed computation (t1*) and the values produced when it returns (t2*).

resume reads the continuation type from the instruction, loads the matching function signature, pops those arguments, and pushes those returns:

Copy to Clipboard

The important part is that resume is typed by the instruction itself. A resume instruction names a continuation type. The decoder uses that type to decide which values must already be on the Wasm stack before the continuation reference, and which values the resume will return. For example, if the instruction resumes a continuation type whose function is i64 -> i64, the decoder pops one i64 argument before the continuation reference. If the instruction resumes a continuation type whose function is ref $Box -> i64, the decoder expects a ref $Box instead.

That is the detail bug 502229895 depends on. resume decides the argument layout from the continuation type written in the instruction. If a continuation reference is changed after validation, resume can accept values for one type while the resumed function reads them as another. More on that soon.

Continuations And Stack Memory

wasm::StackMemory is the native object used for a resumable Wasm stack. It holds the stack bounds, the saved jump buffer, the current continuation token, and the argument buffer used by resume. The relevant parts of StackMemory are:

Copy to Clipboard

The continuation reaches this native stack through WasmStackObject, which stores an external pointer to wasm::StackMemory:

Copy to Clipboard

The saved machine state lives in JumpBuffer:

Copy to Clipboard

The argument buffer is important for us. It is where resume arguments are stored. cont.new stores it in StackMemory, and resume loads it before entering WasmFXResume:

Copy to Clipboard

At this point, the object chain looks like this:

Copy to Clipboard

Triggering Suspend And Resume

We enter the WasmFX resume path through an exported Wasm function. It takes a boxed continuation and an i64 value, reads the continuation reference from the box, then executes resume contA:

Copy to Clipboard

In WAT slang:

Copy to Clipboard

When V8 lowers that resumeCheckContAndGetStack checks the continuation and gets its StackMemory:

Copy to Clipboard

After that, Resume loads the stack’s argument buffer and calls the WasmFXResume builtin:

Copy to Clipboard

The builtin is where stack switching actually happens. WasmFXResume receives the target StackMemory and the argument buffer, switches to that stack, then loads the saved jump buffer:

Copy to Clipboard

Our exported Wasm function reaches WasmFXResume through resume contA. Because contA has an i64 -> i64 signature, the value before the continuation reference is accepted as an i64. The part to remember is that resume gets us into native stack-switching code with a StackMemory pointer. That StackMemory also carries the saved jump buffer, and later we use out-of-cage write to replace jmpbuf_.pc.

The Missing Signature Check

The previous section showed that resume checks whether the continuation reference is the current continuation for the target StackMemory. In the vulnerable build, that was the main runtime check:

Copy to Clipboard

That check only proves that the continuation belongs to this stack. It does not prove that the stack has the signature expected by this resume instruction. The patch for Chromium bug 502229895 adds that missing check. First, StackMemory gets a signature_hash_ field:

Copy to Clipboard

Then CheckContAndGetStack takes the expected continuation type, loads the stack’s stored signature hash, and traps if the two do not match:

Copy to Clipboard

Our vulnerable 09c960 build has no signature_hash_ field and no hash comparison. That’s the bug. A swapped continuation reference can still pass the “current continuation” check, even when its signature does not match the resume instruction.

Using 6307 For The Swap

The next step is to craft the resume instruction and continuation reference mismatch. Our exported function is resume_contA(boxA, value). The instruction inside it is resume contA, so value is used as the i64 argument for contA. The continuation itself is loaded from boxA.field0:

Copy to Clipboard

When boxA is created normally, boxA.field0 contains contA. We also create boxB, whose field 0 contains contB. The only thing we need from CVE-2026-6307 is an in-cage write that copies boxB.field0 into boxA.field0:

Copy to Clipboard

After the write, we still execute resume contA. What changed is the continuation reference loaded from boxA.field0. It now points to contB, so the argument that entered as i64 is received as ref boxC. This means we can pass an address-like i64, then have the resumed function use it as the base object for a WasmGC field access. From there, the WasmFX bug takes care of the rest.

Out-of-Cage Read/Write

Equipped with the continuation swap, we can now build out-of-cage read/write. For the read primitive, we create two continuation types with the same return type and different parameter types:

Copy to Clipboard

In WAT slang:

Copy to Clipboard

contA takes an i64contB takes a ref boxC. CVE-2026-6307 swaps the continuation field so that a resume contA call reaches contB instead. The value accepted as an i64 by resume is then read as ref boxC by the resumed function.

Before the resume call, we adjust the address so field 0 lands exactly where we want:

Copy to Clipboard

The read call creates new continuations, swaps the field, and enters resume_contA:

Copy to Clipboard

After the continuation swap, resume_contA still passes the first argument as i64, but the resumed function is contB‘s fB. It receives that same value as ref boxC and runs struct.get boxC, 0, which reads from the chosen address.

For write, we do the same thing with one extra argument. The resumed function performs struct.set instead of struct.get:

Copy to Clipboard

In WAT slang:

Copy to Clipboard

The write call also creates a new pair, performs the same field swap, and enters resume_contA with one extra i64 value:

Copy to Clipboard

After the swap, contB‘s fB receives the first argument as ref boxD and the second as the i64 value to store. struct.set boxD, 0 writes that value to the chosen address.

That is the jump outside the cage. CVE-2026-6307 sets up the WasmFX continuation type confusion by swapping one field inside the V8 heap. The actual out-of-cage read/write happens when the resumed WasmGC function uses our address-like value as the object for struct.get or struct.set.

Going After The Saved PC

Now we have out-of-cage write. The next question is where to write.

The clean target is the continuation’s own StackMemory. Earlier, WasmFXResume reached LoadJumpBuffer with load_pc=true. That function restores the saved frame pointer, loads the saved PC from the jump buffer, and branches to it:

Copy to Clipboard

So the final target is jmpbuf_.pc. Starting from the saved WasmContinuationObject, we walk the object chain we saw earlier: WasmContinuationObject -> WasmStackObject -> wasm::StackMemory. Once we have the native StackMemory address, we keep the stack switch valid and replace the saved PC:

Copy to Clipboard

If the stack already returned, V8 marks its jump buffer as Retired, meaning the stack is finished and the jump buffer is not valid for resume. The resume path expects the target stack to be Suspended, so we restore the state needed for the final resume:

Copy to Clipboard

The last step is another resume:

Copy to Clipboard

The path follows the usual stack-switching code, reaches LoadJumpBuffer, loads our jmpbuf_.pc, and branches to it:

Copy to Clipboard

The End

We got into the cage through Wasm. We get out through Wasm too. With out-of-cage read/write, the road to code execution is known and documented.

CVE-2026-6307 (Part 3): Escaping the V8 Sandbox Through WasmFX

CVE-2026-6307 (Part 3): Escaping the V8 Sandbox Through WasmFX

May 21, 2026

Authored by stratan

At this point, we are still inside the V8 heap sandbox. CVE-2026-6307 gives us addroffakeobj, and in-cage read/write.

For native code execution, we need a way out. We’ll use Chromium bug 502229895 for that step. WasmFX is still an experimental feature (--experimental-wasm-wasmfx) and thus safe for our demo, still present in V8 revision 09c9603016d80cc7a8ab9017e5ebbb57bc315941, and finally it’s an interesting upcoming feature/surface. More specifically, bug 502229895 is a WasmFX continuation type confusion. With one in-cage write, we can swap a continuation-reference field and make resume run with the wrong continuation signature. As we go through the sections, we’ll turn this into an out-of-cage (native) read/write, as well as code execution.

At this point, we are still inside the V8 heap sandbox. CVE-2026-6307 gives us addroffakeobj, and in-cage read/write.

For native code execution, we need a way out. We’ll use Chromium bug 502229895 for that step. WasmFX is still an experimental feature (--experimental-wasm-wasmfx) and thus safe for our demo, still present in V8 revision 09c9603016d80cc7a8ab9017e
5ebbb57bc315941
, and finally it’s an interesting upcoming feature/surface. More specifically, bug 502229895 is a WasmFX continuation type confusion. With one in-cage write, we can swap a continuation-reference field and make resume run with the wrong continuation signature. As we go through the sections, we’ll turn this into an out-of-cage (native) read/write, as well as code execution.

Disclaimer

This article has been prepared for educational and informational purposes in the field of cybersecurity.

The vulnerability analyzed (CVE-2026-6307) was publicly identified, is known to the vendor, and has been fully patched in current versions of the affected software (fixed in revision 1b0cbb19f825, released on Tue Mar 31 11:05:12 2026).

The content is intended for information security professionals and does not constitute a functional exploitation guide nor does it provide tools to compromise third-party systems.

Tashita Software Security condemns any use of this information for unlawful purposes.

Unauthorized access to computer systems constitutes a criminal offense under the Spanish Penal Code.

A Short Detour Through WasmFX

WasmFX adds stack switching to WebAssembly. According to the stack-switching proposal:

This proposal adds typed stack-switching to WebAssembly, enabling a single WebAssembly instance to manage multiple execution stacks concurrently.

In simple terms, one Wasm instance can manage multiple execution stacks. That is useful for coroutines, async runtimes, generators, lightweight threads, and effect handlers.

The proposal’s star is the continuation:

A continuation represents a snapshot of execution on a particular stack.

A continuation can be created, resumed, and suspended again. resume continues a suspended stack. suspend returns control to the handler installed by the resume site.

For instance:

Copy to Clipboard

In V8, the WasmFX instructions are listed in wasm-opcodes.h:

Copy to Clipboard

V8 describes WasmContinuationObject as the “token used” for the suspend/resume flow:

Copy to Clipboard

The proposal defines cont.new as the instruction that turns a function reference into a suspended continuation:

Copy to Clipboard

V8 implements that instruction in the decoder. cont.new checks that WasmFX is enabled, pops the function reference, and pushes a continuation reference:

Copy to Clipboard

After decoding cont.new, the continuation needs its actual runtime state. Runtime_WasmAllocateContinuation allocates a wasm::StackMemory from the stack pool and creates a WasmStackObject that points to it:

Copy to Clipboard

The function signature decides the argument-buffer size. The buffer is placed at the top of the new stack and stored in StackMemory. Finally, the runtime marks the jump buffer as suspended and sets the initial PC to the stack-entry wrapper. So cont.new returns a continuation reference, but that reference has native stack state behind it.

The other instruction we care about is resume. The proposal gives its type like this:

Copy to Clipboard

and then states:

The resume instruction is parameterised by a continuation type and a handler dispatch table hdl.

So the continuation type $ct decides the arguments passed into the resumed computation (t1*) and the values produced when it returns (t2*).

resume reads the continuation type from the instruction, loads the matching function signature, pops those arguments, and pushes those returns:

Copy to Clipboard

The important part is that resume is typed by the instruction itself. A resume instruction names a continuation type. The decoder uses that type to decide which values must already be on the Wasm stack before the continuation reference, and which values the resume will return. For example, if the instruction resumes a continuation type whose function is i64 -> i64, the decoder pops one i64 argument before the continuation reference. If the instruction resumes a continuation type whose function is ref $Box -> i64, the decoder expects a ref $Box instead.

That is the detail bug 502229895 depends on. resume decides the argument layout from the continuation type written in the instruction. If a continuation reference is changed after validation, resume can accept values for one type while the resumed function reads them as another. More on that soon.

Continuations And Stack Memory

wasm::StackMemory is the native object used for a resumable Wasm stack. It holds the stack bounds, the saved jump buffer, the current continuation token, and the argument buffer used by resume. The relevant parts of StackMemory are:

Copy to Clipboard

The continuation reaches this native stack through WasmStackObject, which stores an external pointer to wasm::StackMemory:

Copy to Clipboard

The saved machine state lives in JumpBuffer:

Copy to Clipboard

The argument buffer is important for us. It is where resume arguments are stored. cont.new stores it in StackMemory, and resume loads it before entering WasmFXResume:

Copy to Clipboard

At this point, the object chain looks like this:

Copy to Clipboard

Triggering Suspend And Resume

We enter the WasmFX resume path through an exported Wasm function. It takes a boxed continuation and an i64 value, reads the continuation reference from the box, then executes resume contA:

Copy to Clipboard

In WAT slang:

Copy to Clipboard

When V8 lowers that resumeCheckContAndGetStack checks the continuation and gets its StackMemory:

Copy to Clipboard

After that, Resume loads the stack’s argument buffer and calls the WasmFXResume builtin:

Copy to Clipboard

The builtin is where stack switching actually happens. WasmFXResume receives the target StackMemory and the argument buffer, switches to that stack, then loads the saved jump buffer:

Copy to Clipboard

Our exported Wasm function reaches WasmFXResume through resume contA. Because contA has an i64 -> i64 signature, the value before the continuation reference is accepted as an i64. The part to remember is that resume gets us into native stack-switching code with a StackMemory pointer. That StackMemory also carries the saved jump buffer, and later we use out-of-cage write to replace jmpbuf_.pc.

The Missing Signature Check

The previous section showed that resume checks whether the continuation reference is the current continuation for the target StackMemory. In the vulnerable build, that was the main runtime check:

Copy to Clipboard

That check only proves that the continuation belongs to this stack. It does not prove that the stack has the signature expected by this resume instruction. The patch for Chromium bug 502229895 adds that missing check. First, StackMemory gets a signature_hash_ field:

Copy to Clipboard

Then CheckContAndGetStack takes the expected continuation type, loads the stack’s stored signature hash, and traps if the two do not match:

Copy to Clipboard

Our vulnerable 09c960 build has no signature_hash_ field and no hash comparison. That’s the bug. A swapped continuation reference can still pass the “current continuation” check, even when its signature does not match the resume instruction.

Using 6307 For The Swap

The next step is to craft the resume instruction and continuation reference mismatch. Our exported function is resume_contA(boxA, value). The instruction inside it is resume contA, so value is used as the i64 argument for contA. The continuation itself is loaded from boxA.field0:

Copy to Clipboard

When boxA is created normally, boxA.field0 contains contA. We also create boxB, whose field 0 contains contB. The only thing we need from CVE-2026-6307 is an in-cage write that copies boxB.field0 into boxA.field0:

Copy to Clipboard

After the write, we still execute resume contA. What changed is the continuation reference loaded from boxA.field0. It now points to contB, so the argument that entered as i64 is received as ref boxC. This means we can pass an address-like i64, then have the resumed function use it as the base object for a WasmGC field access. From there, the WasmFX bug takes care of the rest.

Out-of-Cage Read/Write

Equipped with the continuation swap, we can now build out-of-cage read/write. For the read primitive, we create two continuation types with the same return type and different parameter types:

Copy to Clipboard

In WAT slang:

Copy to Clipboard

contA takes an i64contB takes a ref boxC. CVE-2026-6307 swaps the continuation field so that a resume contA call reaches contB instead. The value accepted as an i64 by resume is then read as ref boxC by the resumed function.

Before the resume call, we adjust the address so field 0 lands exactly where we want:

Copy to Clipboard

The read call creates new continuations, swaps the field, and enters resume_contA:

Copy to Clipboard

After the continuation swap, resume_contA still passes the first argument as i64, but the resumed function is contB‘s fB. It receives that same value as ref boxC and runs struct.get boxC, 0, which reads from the chosen address.

For write, we do the same thing with one extra argument. The resumed function performs struct.set instead of struct.get:

Copy to Clipboard

In WAT slang:

Copy to Clipboard

The write call also creates a new pair, performs the same field swap, and enters resume_contA with one extra i64 value:

Copy to Clipboard

After the swap, contB‘s fB receives the first argument as ref boxD and the second as the i64 value to store. struct.set boxD, 0 writes that value to the chosen address.

That is the jump outside the cage. CVE-2026-6307 sets up the WasmFX continuation type confusion by swapping one field inside the V8 heap. The actual out-of-cage read/write happens when the resumed WasmGC function uses our address-like value as the object for struct.get or struct.set.

Going After The Saved PC

Now we have out-of-cage write. The next question is where to write.

The clean target is the continuation’s own StackMemory. Earlier, WasmFXResume reached LoadJumpBuffer with load_pc=true. That function restores the saved frame pointer, loads the saved PC from the jump buffer, and branches to it:

Copy to Clipboard

So the final target is jmpbuf_.pc. Starting from the saved WasmContinuationObject, we walk the object chain we saw earlier: WasmContinuationObject -> WasmStackObject -> wasm::StackMemory. Once we have the native StackMemory address, we keep the stack switch valid and replace the saved PC:

Copy to Clipboard

If the stack already returned, V8 marks its jump buffer as Retired, meaning the stack is finished and the jump buffer is not valid for resume. The resume path expects the target stack to be Suspended, so we restore the state needed for the final resume:

Copy to Clipboard

The last step is another resume:

Copy to Clipboard

The path follows the usual stack-switching code, reaches LoadJumpBuffer, loads our jmpbuf_.pc, and branches to it:

Copy to Clipboard

The End

We got into the cage through Wasm. We get out through Wasm too. With out-of-cage read/write, the road to code execution is known and documented.