Validation and actuality are the causes for some of the biggest bugs around! This blog post goes into a difference between how the optimizer sees code and how the code is actually represented in Chromium.
The proof of concept is literally 6 lines of code, which is absolutely wild for a JavaScript 0-day. The first part of the bug is when InstructionSelector::VisitChangeInt32ToInt64 converts a 32 bit integer into a 64-bit integer by either sign extending or padding the value with 0s.
While dealing with optimizations, the exploiteers found an edge case: if the value is XORed with 0, then we can PROVE the value should be the original output. If this is true, then the XOR operation can be entirely removed from the code during the optimization phase.
Here is where everything perfectly collides: the new value from the optimization is now UNSIGNED, even though it should be SIGNED. When the case is hit with an unsigned number, the new value gets changed after the optimization. This unsigned to signed problem creates a type confusion which is likely exploitable.
In the POC, the original value is -2^32 and the JITed value is 2^32, demonstrating an issue when the expected vs. real representation of the value. The authors of this post have a substantial amount of debug information in this post as well, which may be useful if you are getting into Chromium hacking.
In a
followup article, they actually exploit this bug. How can a simple bad JIT interpretation turn into RCE? Let's find out!
Previously, turning an bad numeric result into an OOB read/write was done by abusing array bounds check elimination optimization. However, the
optimization that allowed this to happen was removed because of how often it was exploited. The authors found a new strategy that was mentioned at
Pwn2Own 2020 by abusing
ArrayPrototypePop and
ArrayPrototypeShift.
Array.shift removes the first element from the array, returns the removed element then computes the new size of the array by subtracting one. By abusing the original vulnerability and using it for the size of an array, the confusion is created. By running Array.shift, the length of the array will become -1, since there is no bounds check at this point in the optimization as everything is supposed to be safe.
Since the subtraction leads to an integer underflow of the length, the array now has infinite size. This means that we have a relative read/write for the entire memory space. They use this to create an addrof (address of) and fakeobj (fake object) primitives.
After creating the primitives, they use it to leak the address of a
wasm function they created. Since the
wasmOverall, a good series of posting on modern exploitation in the browser. It is absolutely wild how complex these issues are becoming but are still weaponizable.