[2] IPInt PC desynchronization via variable-length LEB128 sub-opcodes
Severity: High | Component: JSC IPInt (In-Place Interpreter) | 0acf64e
Rated High because the observable effect is interpreter desynchronization causing the Wasm in-place interpreter to execute wrong instructions with wrong metadata from valid bytecode, and the attacker has significant control over the post-desynchronization instruction stream — the projection to type confusion primitives is at confidence 0.88, with the specific pre-patch advancePC constants inferred from the commit message and test case rather than directly visible in the diff.
Prefixed opcodes in IPInt (GC prefix 0xFB, SIMD prefix 0xFD) have sub-opcodes encoded as VarUInt32 LEB128. IPInt used hardcoded advancePC(constant) to skip past them, but LEB128 allows the same value to be encoded in variable numbers of bytes. The fix tracks dynamic instruction lengths for prefixed opcodes.
JSTests/wasm/stress/ipint-variable-length-gc-opcodes.js
+ // Helper function to create redundant LEB128 encoding of a value
+ function createRedundantLEB128(value, totalBytes) {
+ if (totalBytes === 1) {
+ return [value];
+ }
+ let result = [];
+ for (let i = 0; i < totalBytes - 1; i++) {
+ if (i === 0) {
+ result.push(value | 0x80); // First byte with continuation bit
+ } else {
+ result.push(0x80); // Middle bytes: just continuation bit
+ }
+ }
+ result.push(0x00); // Final byte: no continuation bit, value 0
+ return result;
+ }
+ // Test ref.i31 with 2-byte redundant encoding
+ {
+ let extendedOp = createRedundantLEB128(0x1C, 2); // ref.i31 redundant: [0x9C, 0x00]
+ let codeBody = [
+ 0x00, // 0 locals
+ 0x41, 0x2A, // i32.const 42
+ 0xFB, ...extendedOp, // ref.i31 with redundant encoding
+ 0x0B // end
+ ];
+ let module = new WebAssembly.Module(bytes);
+ let instance = new WebAssembly.Instance(module);
+ assert.eq(instance.exports.f(), 42);
+ }
Patch Details
The patch modifies the IPInt generator (WasmIPIntGenerator.cpp) to record dynamic PC advancement for all GC and SIMD prefixed opcode handlers — functions including addRefI31, addI31GetS, addI31GetU, addArrayLen, addArrayFill, addArrayCopy, addAnyConvertExtern, addExternConvertAny, and the SIMD variants. The calling convention in WasmFunctionParser.h::simd() is updated to pass instruction length information. Several generator methods are renamed across all JIT tiers (BBQ, OMG, ConstExpr) to match the new convention: addExtractLane → addSIMDExtractLane, addReplaceLane → addSIMDReplaceLane, addConstant → addSIMDConstant. The InPlaceInterpreter64.asm dispatch logic is also updated. A regression test constructs Wasm modules with redundant LEB128-encoded sub-opcodes and verifies correct execution.
Before: After:
Wasm bytecode: [0xFB, 0x9C, 0x00, ...] Wasm bytecode: [0xFB, 0x9C, 0x00, ...]
IPInt PC ──► advancePC(2) IPInt PC ──► advancePC(dynamicLen)
skips 2 bytes skips 3 bytes (actual encoding)
PC lands on 0x00 ← WRONG PC lands on next opcode ← CORRECT
next decode: garbage next decode: correct instruction
Fixed-length PC advancement for variable-length LEB128-encoded sub-opcodes in the Wasm in-place interpreter.
Background
WebAssembly prefixed opcodes use a two-level dispatch: a prefix byte identifies the opcode group (0xFB for GC/reference types, 0xFD for SIMD), followed by a sub-opcode that identifies the specific instruction within that group. The sub-opcode is encoded as a VarUInt32 using LEB128 — a variable-length integer encoding where each byte uses 7 bits for data and 1 bit (the high bit) as a continuation flag. LEB128 permits redundant encodings: any value can be padded with extra continuation bytes carrying zero payload. For example, the value 28 (ref.i31) can be encoded as [0x1C] (1 byte), [0x9C, 0x00] (2 bytes), or [0x9C, 0x80, 0x00] (3 bytes). All encodings are semantically equivalent and valid per the Wasm spec.
IPInt (In-Place Interpreter) is JSC's lowest-tier Wasm execution engine — it interprets Wasm bytecode directly without compilation, used for cold code and initial execution before tiering up to BBQ or OMG JIT. IPInt maintains two cursors: PC (program counter, pointing into the Wasm bytecode) and MC (metadata counter, pointing into pre-generated metadata that contains decoded constants, branch targets, and instruction lengths). These two cursors must remain synchronized — each instruction advances both by the correct amount. If they desynchronize, subsequent instructions read wrong metadata, leading to incorrect execution.
The validation pass in WasmFunctionParser correctly handles variable-length LEB128 by parsing the actual byte count. It is only the IPInt execution tier that used hardcoded constants.
Analysis
The root cause is a mismatch between IPInt's hardcoded PC advancement and the actual variable-length encoding of prefixed sub-opcodes. Before the fix, handlers for GC opcodes like ref.i31 used advancePC(2) — assuming a 1-byte prefix plus a 1-byte sub-opcode. When the sub-opcode was encoded with redundant LEB128 bytes (e.g., [0xFB, 0x9C, 0x00] instead of [0xFB, 0x1C]), the interpreter advanced PC by 2 but the actual instruction was 3 bytes. PC landed in the middle of the current instruction's trailing LEB128 bytes, and every subsequent instruction was decoded from the wrong offset.
This is an interpreter desynchronization bug. The PC and MC cursors diverge: MC advances correctly (if MC advancement is independent of PC length, as the IPInt architecture suggests), while PC is stuck one or more bytes behind. Each subsequent decode reads bytecode at an offset shifted by the desynchronization amount, interpreting arbitrary bytes as opcodes and operands while consuming metadata intended for entirely different instructions.
Aaa Aaaa Aaaa Aaaaaaaaaaaa Aaa Aaa Aaaaaaaaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaa Aaaaaaaa Aaaaaaa Aaaaaa a a Aaaaa Aaaaaa Aaaaaaaa Aa Aaaaaaaaaa Aa Aaaaaaaaaaaa Aaa Aaaaaaaa Aaaaaaa Aaaaa Aaaaaa Aa a Aaaaa Aaaaaaaaa Aaaaaaaaaaa Aaaa Aaaaaa Aaaa Aaaaaaaaaaa Aaa Aaaaaaa Aaaaaaaaa Aaaaaaa Aaaaa Aaaaaaa Aa Aa a Aaaaaaaaa Aaaaaa Aaa Aaaaaaaa Aaaaaaa Aa Aaaaaaaa Aa Aaaaaa a Aaaaa Aa Aaaaa Aaaa Aaaaaaaaa Aa Aaa Aaaa Aaaaaaa Aaa Aaaa Aaaaaaaaa Aaaa Aaaaaa Aaaaaaaaaa Aaaaaaaaaaaa Aaaaaaaaaaaa Aaaaaaaaaaaa Aaaaaaaaaaaaa Aaaaaaaaaaaaa Aaa Aaa Aaaaaaaaaaaaaa Aaaaaaaaaa Aaaaaaaa
Aaa Aaaaaaaaaaaaaaaaa Aa Aaaaaa Aaaaaaaaaaa Aaaa Aaa Aaaaaaaa Aa Aaaaaaaa Aaaaaa a Aaaa Aaaaaa Aaaa Aaaaaaaaa Aaaaaa Aaaaaaaaaa Aaaaaaaaa a Aaa Aaaaaa Aaaaaa Aaaaaaaaaa Aaaaaaa Aaaaaaaaa Aaaaaa Aa Aaaaaaaaaaa Aaaa Aaaaa Aaaaaaaa Aaa Aaa Aaaaa Aaaaaaaaaaaa Aaaaaa Aaa Aaaaaaaaaaa Aa Aaa Aaaaaaaa Aaaaaaaa Aaa Aaaaaaaaaaa a Aaaa Aaaaaaaaa Aaaaa Aaaa Aaaaaa Aa a Aaaaaaaaa Aaaaaaaaaaa Aaaaaaaaa Aaaa Aaaaa Aaaaaa Aaa Aaaaa Aaaa Aaaaaaa Aaaaaa Aa Aa Aaaaaaaaaaa Aaaaaaaaaaa Aaaaaaaaa Aaaaaaa Aa Aaa Aaaaa Aaaaaaaaaaaa Aaaa a a Aaaa Aaaaaaaaaa Aaa Aaaaa Aaaaaa Aaaaaaaa Aaaaaaa Aa Aaaaaaaaa Aaaaaaaa Aaaaaaaa Aaa Aaaaa Aaaaaaaaaaaa Aaaaaaaa Aaaaaaa Aa Aaaaaaa Aaa Aaaaa Aaaaaaaaaa Aaa Aaaaa Aaaaaa Aaaaaaaa Aaa Aaaaaaaaaaa Aaa Aaaaaaaa Aaa Aaaaaaaaaaa Aaaaaaa Aaaa Aaa Aaaaaaaa Aaaaaa Aaaaa Aaa Aaaaaaaaaaaaaaaaa Aaaaaa Aaaaa Aaaaa Aaa Aaaaaaaa Aaaaaaaaaaaaaaaaaaaa Aaaaaaaaaa Aaa Aa Aaaaaaa Aaaaaaaaaaaaa
Aaaa Aaaaaaaaaaaaa Aaaaaaa Aaa Aaaa Aaaa Aaaaaa Aaa Aaaaaa Aaaaaa Aaaaaaaaaa Aaaaaa Aaa Aaaaaaaa Aaaaaaaa Aaa Aaaa Aaaaaaaaaaaaa Aaaaaaaa Aaaa Aaaaaaaaa Aaaaaaaa Aaaaaaaa Aaaa Aaaa Aaaaaaa Aaa Aaaaaa Aaa Aaaa Aa Aaaaaaaa Aaaaa Aaaaa Aaaaa Aaaa Aaaaaaaa Aaaa Aaaaaaaaaaaaaa Aaaaaaa Aa Aaaa Aaa Aaaaaaaa Aaaaaaa Aaaa Aaaaaaaaaaaaaaaaa Aaaaa Aaaaa Aaa Aaaaaaaaaaa Aa Aaaaaaa Aaaaa Aaaaaaaaaaaa Aaaa Aaaaa Aaaaaaaaa Aaaaaaaaaaa Aaaaaaaaa Aaaa Aaaaaaa Aaaaaaaaaa Aaa Aaaa Aaaaaaa Aaaaaa Aa Aaaaaaaaa Aaaaaa Aaaa Aaaaa Aaaaa a Aaa Aaaa Aaaaaaaa Aaa Aaaaaaa Aaa Aaa Aaaaaaaaaaa Aaaa a Aaaaaaaa Aaaaaaa Aaaaaa Aaaaa Aa Aaaaaaaa Aaa Aaaa Aaaaaa Aaaaaaaaaaa Aaa Aaaa Aaaa Aaaaaaaaa Aaaaaa Aaa Aaaaaaaa Aaaaa Aaaaa Aaaaaaaaa Aaaaaaaaaa Aaaaaaaaaa Aaaaaaaaaa Aa Aaaaaaaaaa Aaa Aaaaaaaaaa Aaaaaaa Aaaaaaa
Aaaa Aaa Aaaaaaaaaa a Aaaaaaaa Aaaa Aa Aaaaaaaaaaaa Aaaa Aaaaaaaa Aaaaaaaa Aaaaaa Aaaaaa Aaa Aaaaaaaaaaa Aaaaa Aaaaaaa Aaaaaa Aaaaaaa Aaaa Aaa Aaaaaaaaa Aaaaaaaa Aaaaaaa a Aaaaaaaaaaaaaaaaa Aaaa Aaaaaaaaa Aaaaaaa Aaa Aaaaaaaaaa Aaaaaaaaaaaaa Aaa Aaaa Aaaaaa Aaaaaaaaa Aa Aaaaaaaaa Aaaaaa Aaaaaaaaa Aa Aaaaaaaaaa Aaa Aaaa Aa Aaaaaaaa Aaaa Aaaaaaaaaa Aaaaaaaaaaa Aaaaaaa a Aaaaaaaaaaaa Aaaaaaaaa Aaaaaaa Aaa Aaaaaaaaaa Aaaa Aaaaaaaaa Aaaaaaa Aaaaaaaaaaaaaaa Aaaaaa Aaaaa Aaaa Aaa Aaaaaaaaa Aaaa Aaaa Aaa Aaaaa Aaaaaaaaaaa
Aaaaaaaaa Aaa Aaaaaaaa Aaaaaaaaaaaaaa Aaaaa Aa Aaa Aaaaaaaaa Aaaaaaaaaaaaaaaaaaaaaaaa Aaa Aaa Aaaaa Aaaaaaaaaaaa Aaaaaaaaaa Aaa Aaaaaaaa Aaaa Aaa Aaaaaa Aaaaaaa Aaa Aaaa Aaaa Aaaaaa Aaaa Aaaaaaaa Aaaaa Aa Aaa Aaaa Aaaaaa Aaa Aaaa Aaaaaaaaaaaaaaaaa Aaaaaaaaa Aaa Aaa Aaa Aaa Aaaaaaaaaaaa Aaaaaaaaa Aa Aaa Aaaaa Aaa Aaaa Aaaaaaaaaa
🔒Explores the PC/MC desynchronization mechanism in depth and assesses what primitives an attacker could construct from misaligned interpreter state
Subscribe to read more
Audit directions
a Aaaaaaaaaa Aaaaaaaaa Aaaaaaaaa Aaaaaaaaaaa Aa Aaaaaaaaaaaa Aa Aaaa Aaaaaaaaaa Aaa Aaaaaaaaaaaaaaa Aaaaaaa Aaaaaaaaaaaaaaa Aaa Aaaaaaaaaaaaaaaaaaaaa Aa Aaaaaaaaaa Aa Aaaaa Aaaa Aaaa Aaa Aaaaaaa Aaa Aaaaaa Aaaaaaaaaaa Aa a Aaaaaaaaa Aaaaaaaaaaaaaaaaa Aaaa Aaaa Aaaaaaaaaaaaaaaaaaaaaaaa Aaa Aaaaaaaaa Aaaaaaaaa Aa Aaaaaaaa Aa Aaaaaaaaaaa Aa Aaaaaaaa Aaaaaa Aaaaaaaaa Aaaa Aaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaa Aaa Aaaaaaaaa Aaaaaaaa Aaaaaaa Aaaa Aaaaaa Aaaaa Aaaaaaaaaa Aaaaaa
a Aaaaaaaaaa Aaaaa Aaaaaa Aaaaaaaaaaaaaaa Aaaaaaaaa Aaaaaaaaaaaaa Aaa Aaaa Aaaa Aaaaa Aa Aaaaaaaa Aa a Aaaaaaaaa Aaaaaa Aaaa Aa Aaaaaaa Aaaaa Aaaaa Aaaaaaaa Aaaaaaaaaaaaa Aaaaa Aaa Aaaaa Aaaaaaaaa Aaa Aaaaa Aaaaa Aaaaaaaaaaaaa Aaa Aaaaaaaaaaaaa Aaaaa Aaaaaaa a Aaaaaaaaaaaa Aa Aaaaa Aaaaaa Aaaaaaaaaaa Aaaa Aaaaaaaaa Aa Aaaaa Aaaaa Aaaa Aaaaaaaaa Aaaaaaaaaa Aaaaaaaaa Aaaa Aaaaaa Aaaaaaaa Aaaa Aaaaaaaaa Aaa Aaaaaaaa Aaaaaaaa
a Aaaaaaa Aaaaaaa Aaa Aaaa Aaaaaaaaa Aaaaaaaaaaaaaaaaaaaaaaaa Aaaaaaa Aaaaaaaa Aaaaaa Aaaaaaaaa Aaaa Aaaaaa Aaa Aaaaaaaaa Aaaaaa Aaaaaaa Aaa Aaaaaaaaaaaa Aa Aa Aaaa Aaaa Aa Aaaaaaaa Aaaaa Aaa Aaaaaaaaa Aaaa Aaaaaaaaa Aaaaaaaaa Aaa Aaaaaa Aa Aaaaaaa Aaa Aaaaa Aaaaaaaaaaaaaaaaaa Aaaaa Aaaaaaaaaaaaaaaa Aa Aaaaaaaaaaaaaa Aaa Aaaaaaa Aaaaaa Aaaaaaaaaaaa
a Aaaaaaaaaa Aaaaaaaaaaaaaaaa Aaaa Aaaaa Aaa Aaaaaaaaaa Aaaa Aaaaaaa Aaaaaaaa Aaaaaaaaa Aaa Aa Aaaaaaaaa Aaaa Aaaaa Aaaaaaaaaaa Aaaaaaaaaaaaaa Aaaaa Aa Aaa Aaaaaaaaa Aaaaaaaaaaa Aaa Aaa Aaaaaaa Aaaa Aaaaaaa Aaaa Aaaaaaaa Aaaaa Aaaaaaa Aaaaa Aaaaaaa Aaa Aaaa Aaaaaaaa Aaaaaaaaaa Aaaa Aaaaa Aaaaaaaaa Aaaaaaaaa Aaaaaaa Aaaaa Aaaa Aaa Aaaa Aaaaa Aa Aaa Aaa Aaaaaa Aaaaa Aaaa Aaaaaaaaaaaaaaaaaaaaaaaa Aaaaaaaa Aaa Aaaaaaaa Aaaaa Aaaaaaaaaa
🔒Multiple reusable audit patterns identified for interpreter cursor synchronization bugs, with concrete grep targets across Wasm execution tiers
Subscribe to read more