Official patch: https://chromium-review.googlesource.com/c/v8/v8/+/4584248
Patch come from KeyedStoreIC::StoreElementHandler(), it returns fast path code(Turbofan builtin) for keyed store depends on "receiver_map" and "store_mode". Based on the content of this function is all about element STORE, I personally believe that this is an OOB writes vulnerability.
If we divide the PoC exploration into two parts based on this func, they are:
- how to reach it
- how to achieve OOB write through it
This writeup is about the latter.
Comments from v8 devs:
Allow fast behaviour for in-bounds stores while making it miss and properly handle the out of bounds store case.
From the patched if branch we may assume the buggy receiver has some traits:
- IsJSArgumentsObject
- fast packed elements
And according to the code above, there are some more some limitations:
- NOT has_sloppy_arguments_elements, so it MUST be strict arguments
Also we had STANDARD_STORE as a hard coded access mode, so we may assume the PoC needs:
- store_mode ≠ STANDARD_STORE
Conclusion:
- strict arguments object
- non STANDARD_STORE store mode
It was not patched which indicates that its logic does not have any issues, since it’s called by other funcs, if other callsites use a packed arguments object with the non-standard store mode parameter, an OOB write can still occur (which indicates that this parameter combination cannot appear in other callsites).
In EmitElementStore, if our goal is to achieve an OOB write, it means:
- intptr_key ≥ length, in order to write data to unallocated memory
- The elements array does not expand, cause what we need is out-of-bound write. Moreover, it is difficult to accurately control the written memory range after expansion(harder to find the addr of the newly allocated elements array in the bump allocation heap?)
If the function does not enter bailout at the end, it will call StoreElement() to write the target value into addr elements + intptr_key * element_size
.
JSStrictArgumentsObject is a JSArray-alike object, but it’s not a JSArray. It inherits from JSObject and has a length property/descriptor inside, just like JSArray.
extern class JSArgumentsObject extends JSObject {}
extern shape JSStrictArgumentsObject extends JSArgumentsObject {
length: JSAny;
}
extern class JSArray extends JSObject {
macro IsEmpty(): bool {
return this.length == 0;
}
length: Number;
}
So what makes JSStrictArgumentsObject special compared to an ordinary JSObject or JSArray? Some thoughts but no answer:
- the length descriptor
- strict arguments’s length is readonly constant and does not change if we add new element into its elements array
- ISSUE: we may got a SMALLER length, not a LARGER one for oob writes
- packed elements
- strict arguments’s elements are packed kind by default, compared to a JSObject. You can’t create a JSObject with packed elements(not sure…)