# A Typer Bug

One of my favourite writeups and bugs is [this writeup](https://abiondo.me/2019/01/02/exploiting-math-expm1-v8/) about exploiting mis-speculation bugs, based on [this](https://project-zero.issues.chromium.org/issues/42450781) 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)](https://ieeexplore.ieee.org/document/8766229), which has signed zeroes, but I still think it's stupid.

The bug is found in the [`Math.expm1`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/expm1) function, which calculates $$e$$ to the power of a number and then subtracts $$1$$. The typer, as described in the report, sets the type of `Math.expm1` to be `Union(PlainNumber, NaN)` (found [here ](https://source.chromium.org/chromium/v8/v8.git/+/9680338c622d4693f984b49fb24d101acd2d8112:src/compiler/typer.cc;l=1437)and [here](https://source.chromium.org/chromium/v8/v8.git/+/9680338c622d4693f984b49fb24d101acd2d8112:src/compiler/operation-typer.cc;l=420)) - 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`:

```javascript
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:

```javascript
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 writeup](https://abiondo.me/2019/01/02/exploiting-math-expm1-v8/), which is of a CTF challenge based on this bug. [The report](https://project-zero.issues.chromium.org/issues/42450781) also contains information on achieving an OOB.

TODO finish this

## More Writeups

* [FizzBuzz's Turboflan writeup](https://www.willsroot.io/2021/04/turboflan-picoctf-2021-writeup-v8.html)
* [P0ch1ta's writeup of CVE-2025-2135](https://keksite.in/posts/CVE-2025-2135/)
* [Faith's writeup for CVE-2020-16040](https://faraz.faith/2021-01-07-cve-2020-16040-analysis/)
