← All issues

[5] Wasm GC struct.new_default Information Leak via Missing v128 Initialization

Severity: Medium | Component: JSC Wasm OMG JIT | 4be0a7f

Rated Medium because the observable effect is a reliable leak of 8 bytes of uninitialized process memory per struct allocation, useful for ASLR bypass — the missing init branch is confirmed with confidence 0.95 from the diff, though whether the backing store is pre-zeroed or relies entirely on per-field initialization is unverified.

Initialize v128 fields in struct.new_default with a v128_t { }. Also drive-by changes array.new_default to use the constant() helper.

Source/JavaScriptCore/wasm/WasmOMGIRGenerator.cpp

for (StructFieldCount i = 0; i < structType.fieldCount(); ++i) {
Value* initValue;
- if (Wasm::isRefType(structType.field(i).type))
+ auto fieldType = structType.field(i).type;
+ if (Wasm::isRefType(fieldType))
initValue = m_currentBlock->appendNew<WasmConstRefValue>(m_proc, origin(), JSValue::encode(jsNull()));
- else if (typeSizeInBytes(structType.field(i).type) <= 4)
+ else if (typeSizeInBytes(fieldType) == 16)
+ initValue = constant(V128, v128_t { });
+ else if (typeSizeInBytes(fieldType) <= 4)
initValue = constant(Int32, 0);
else
initValue = constant(Int64, 0);

JSTests/wasm/gc/struct-new-default-v128.js

+ (type $S1 (struct (field $vec (mut v128))))
+ (func (export "leak") (result i64)
+ (struct.new_default $S1)
+ (struct.get $S1 0)
+ (i64x2.extract_lane 1)
+ )
+...
+ for (let i = 0; i < 1000000; i++) {
+ const value = instance.exports.leak();
+ assert.eq(value, 0n);
+ }

In OMGIRGenerator::addStructNewDefault, the field initialization loop lacked a branch for 16-byte (v128) fields. Before the fix, a v128 field (size 16) was not a ref type and its size was > 4, so it fell into the else branch and was initialized as constant(Int64, 0) — an 8-byte zero value for a 16-byte slot. The fix inserts an explicit typeSizeInBytes(fieldType) == 16 check that initializes v128 fields with constant(V128, v128_t { }), a proper 128-bit zero. A drive-by change in addArrayNewDefault replaces a verbose Const128Value construction with the same helper for consistency.

Wasm GC struct.new_default creates a new struct instance with all fields set to their default (zero) values per the WebAssembly GC specification. v128 is a 128-bit SIMD type used in Wasm SIMD and Wasm GC; its fields occupy 16 bytes in memory. The OMG tier is JSC's top-tier optimizing JIT for Wasm, which compiles hot functions via B3 IR. The constant() helper in the OMG IR generator creates typed constant B3 values; using constant(Int64, 0) for a 16-byte field produces a value that only covers 8 of the 16 required bytes, leaving the upper half uninitialized.

The root cause is a missing case in a size-based type dispatch. The initialization logic branched on ref types first, then fields with size ≤ 4 bytes (Int32), and fell through to Int64 (8 bytes) for everything else. A v128 field has typeSizeInBytes == 16, so it matched the else branch and was initialized with an 8-byte zero. This left the upper 8 bytes of the 16-byte v128 slot uninitialized. The JIT-compiled code writes only 8 zero bytes into a 16-byte field, and subsequent reads of the field (e.g., i64x2.extract_lane 1) return whatever happened to be in the uninitialized upper half — if the backing store relies on per-field initialization rather than being pre-zeroed, this exposes stale heap or stack contents to JavaScript.

🔒

Detailed vulnerability analysis & security impact assessment

Subscribe to read more

🔒

Pattern-based audit directions for variant discovery

Subscribe to read more