A Typer Bug

One of my favourites

One of my favourite writeups and bugs is this writeuparrow-up-right about exploiting mis-speculation bugs, based on thisarrow-up-right report by Project Zero.

Conceptually, the bug is actually very simple. In JavaScript, there are two types of zero - 0 and -0. This is because JavaScript implements the IEEE Standard for Floating-Point Arithmetic (IEEE 754)arrow-up-right, which has signed zeroes, but I still think it's stupid.

The bug is found in the Math.expm1arrow-up-right function, which calculates ee to the power of a number and then subtracts 11. The typer, as described in the report, sets the type of Math.expm1 to be Union(PlainNumber, NaN) (found here arrow-up-rightand herearrow-up-right) - that is, regardless of the input to Math.expm1, the output will either be classified as a PlainNumber (any floating-point number except -0) or NaN. The problem, however, is that Math.expm(-0) = -0, so the typer is wrong.

As shown in the PoC, we can abuse this fact to cause mis-speculation. The idea is to compare Math.expm(-0) to -0. While this is actually true, when optimized, Turbofan will use the incorrect type and note that -0 is not a possible output, so it will optimize it away for speed and set the value to false:

function foo() {  
  return Object.is(Math.expm1(-0), -0);  
}  
  
console.log(foo());  
%OptimizeFunctionOnNextCall(foo);  
console.log(foo());  

When you run this in d8:

% d8 --allow-natives-syntax expm1-poc.js
true
false

Once you get to this point, you have everything you need. The idea is to create something else that would normally trigger a check but the JIT compiler has optimized the check away. For example:

function foo(x) {
    let a = [1.1, 2.2];
    let b = Object.is(Math.expm1(x), -0);
    return a[b * 1337];
}

Since b is optimized to be false, which is equivalent to 0, the bounds check is removed and, in theory, we would access OOB at index 1337! From here, it's the same as the classic OOB challenge.

However, the typer is a little smarter than this, and since b can only possibly be 0 it will fold out b entirely and the whole function will just return 1.1. There are several tricks required to stop this folding, but I won't go over them right now - I recommend you check out the rest of the writeuparrow-up-right, which is of a CTF challenge based on this bug. The reportarrow-up-right also contains information on achieving an OOB.

TODO finish this

More Writeups

Last updated

Was this helpful?