v8 Map migration doesn't respect element kind, leading to type confusion
The following sample, found by Fuzzilli and manually simplified, crashes d8 built from HEAD in both debug and release configuration:
function main() {
const v2 = {foo:1.1};
Object.seal(v2);
Object.preventExtensions(v2);
Object.freeze(v2);
const v12 = {foo:2.2};
Object.preventExtensions(v12);
Object.freeze(v12);
const v18 = {foo:Object};
v12.__proto__ = 0;
v2[5] = 1;
}
main();
In release builds, this sample will often crash when dereferencing an invalid address. In debug builds this will crash with a failed DCHECK: \"# Debug check failed: fixed_array.IsNumberDictionary(isolate)\". Another DCHECK can be triggered earlier on when the --verify-heap flag is used.
I have only very briefly analyzed this crash. Roughly what appears to be happening is that during the IC cache miss caused by the final element store, v2 is transitioned to a new Map (as the old one was deprecated when v18 was created). During that transition, happening in JSObject::MigrateInstance, v8 somehow fails to account for the changed element kind, which in this case switches from fast elements ([HOLEY_FROZEN_ELEMENTS]) to DICTIONARY_ELEMENTS (which are now used due to the modified prototype). As such, afterwards, v8 assumes that the elements_ pointer of v2 point to a NumberDictionary while in reality it still points to a FixedArray. A type confusion follows. As the content of the FixedArray (and its size) are controllable during this crash, I assume this bug to be exploitable.
In release builds, this PoC then appears to crash when v8 tries to fetch an element from the thought-to-be dictionary as the size of the dictionary (really the first element in the FixedArray) is some large number.
According to clusterfuzz, this bug affects current Stable and Beta releases.
This bug is subject to a 90 day disclosure deadline. After 90 days elapse
or a patch has been made broadly available (whichever is earlier), the bug
report will become visible to the public.
Found by: saelo@google.com