Share
## https://sploitus.com/exploit?id=4A7F5A90-F268-5A64-BA5F-28A66CD01E19
# CVE-2026-6307: V8 JS-to-Wasm type confusion โ†’ RCE on Chrome 146

Local reproduction and weaponization of the V8 JS-to-Wasm deoptimization type
confusion described by [Nebula](https://nebusec.ai/research/v8-cve-2026-6307-writeup), taken to a **`xcalc`** popped from the renderer of Chrome for Testing `146.0.7680.165`
(the bug is fixed in `147.0.7727.101`).

All payloads are benign (a calculator / `exit(0x42)` / `jmp $`). 
Everything runs against a deliberately outdated browser in a local lab
do not point it at anyone else.

## Demo





https://github.com/user-attachments/assets/d450f064-ba52-4f39-a88b-367f31217143





## The bug

`JSToWasmFrameStateFunctionInfo`'s equality operator omits the Wasm signature
comparison, so CSE/GVN merges frame states that differ only in signature. A lazy
deopt then materializes a Wasm `i64` as a tagged `externref` (and vice-versa),
giving a full 64-bit `addrof`/`fakeobj` whose pointers are *not* cage-compressed
(Wasm refs use the tagged register representation, returned via `kReturnRegister0`).

Everything below was **verified live** against `146.0.7680.165`. Per-load numbers are
measured hit-rates over repeated runs (the confusion is GC-sensitive; each page
relaunches on a dirty build). No run ever produced a false positive.

## Flag-free primitives โ€” no `--allow-natives-syntax`

Most V8 bug PoCs force optimization with `%OptimizeFunctionOnNextCall`, a test-only
intrinsic gated behind `--allow-natives-syntax` that no real victim enables. These
drive the bug with **no flags at all**, by shaping the warmup feedback so the deopt
confusion fires naturally. Every address is leaked at runtime โ€” nothing is hardcoded.
Run them headless with no flags.

| File | Proves | Verified |
|------|--------|----------|
| [`flag-free/00-reachability-flagfree.html`](flag-free/00-reachability-flagfree.html) | triggers the confused state; fires on vulnerable 146, **not** on patched 147 | 6/15 per load |
| [`flag-free/01-primitives-flagfree.html`](flag-free/01-primitives-flagfree.html) | `addrof` / `fakeobj` (`fakeobj(addrof(o))===o`) | 8/12 |
| [`flag-free/02-store-flagfree.html`](flag-free/02-store-flagfree.html) | out-of-cage store lands a chosen value in a chosen object | 12/12 |
| [`flag-free/03-arw-flagfree.html`](flag-free/03-arw-flagfree.html) | in-cage `read64` / `write64` (fake `PACKED_DOUBLE` array) | 2/12 (flaky, retry) |

## Visible RCE โ€” no-ASLR lab build

`rce-no-aslr/` takes it to code execution: an optimized JS function `g` stages
`execve` shellcode as JIT double-literals (`movabs` constants, each ending in
`eb rel8` to chain the next), the confused store patches a `nop; jmp` into `g`'s code,
and calling `g()` slides RIP into the shellcode.

| File | Proves | Verified |
|------|--------|----------|
| [`rce-no-aslr/exploit-calc.html`](rce-no-aslr/exploit-calc.html) | pops a real `xcalc` window via `execve` from the renderer | pops in ~1s on a clean slate |
| [`rce-no-aslr/exploit-exit.html`](rce-no-aslr/exploit-exit.html) | renderer runs the injected shellcode to `exit(66)`; `#jmp` spins RIP inside it | **exit(66) 8/8** (strace) |

This build uses `--allow-natives-syntax` (`%Optimize` pins `g`'s JIT payload so the
deopt-GC can't flush it) and `--no-memory-protection-keys` (so the confused store can
patch the JIT page โ€” V8's PKU marks it read-only otherwise). ASLR is off
(`randomize_va_space=0`) so the addresses are deterministic
(`A_MAP = 0x5555bbc0128a`). The renderer also needs `--disable-seccomp-filter-sandbox
--no-zygote`, because seccomp otherwise blocks the shellcode's `execve`.

## Run it

Native x86_64 Linux, **ASLR off**, Chrome 146 on `PATH`, an X display on `:0` (Xvfb
or real X). `rce-no-aslr/run.sh` sets all the flags and serves over http.

```sh
cd rce-no-aslr
./run.sh calc          # pop a real xcalc over http://127.0.0.1:8080, on :0
./run.sh exit          # strace-confirms the renderer's exit(66)
./run.sh jmp           # gdb-confirms RIP inside the injected shellcode
./demo.sh calc            # clean-slate + auto-retry, full-size headful browser (for recording)
./demo.sh msgbox          # same, but the pop is a branded "0xSha :: pwned" box
```

`pocmode.sh calc|msgbox` switches what the RCE's `execve("/usr/bin/xcalc")` launches (a
real calculator vs the branded box). The flag-free primitives just need a browser:

```sh
chrome --headless=new --no-sandbox http://127.0.0.1:8080/flag-free/01-primitives-flagfree.html
```

## Layout

```
flag-free/   the no-flags contribution (NO --allow-natives-syntax)
  00-reachability   trigger + patch differential (vuln 146 vs patched 147)
  01-primitives     addrof / fakeobj
  02-store          out-of-cage store
  03-arw            in-cage read64 / write64
rce-no-aslr/ the visible RCE (deterministic, no-ASLR lab build)
  exploit-calc.html / exploit-exit.html
  run.sh, demo.sh, pocmode.sh, xcalc-msgbox.sh
```

Bug and root-cause writeup: Nebula Security (linked above). Flag-free trigger,
primitives, and the visible works in this repo are reproductions/extensions
verified in a local lab.