Exploit-DB #46939
Description: Spidermonkey - IonMonkey Leaks JS_OPTIMIZED_OUT Magic Value to Script
Type: dos
Platform: multiple
Date: 2019-05-29
Author: Google Security Research

Exploit:
IonMonkey can, during a bailout, leak an internal JS_OPTIMIZED_OUT magic value to the running script. This magic value can then be used to achieve memory corruption.

# Prerequisites

## Magic Values

Spidermonkey represents JavaScript values with the C++ type JS::Value [1], which is a NaN-boxed value that can encode a variety of different types [2] such as doubles, string pointers, integers, or object pointers. Besides the types available in JavaScript, JS::Value can also store special ("magic") [3] values for various internal purposes. For example, JS_ELEMENTS_HOLE is used to represent holes in arrays, and JS_OPTIMIZED_ARGUMENTS represents the `arguments` object during a function call (so that no actual memory allocation is required for it).

## Branch Pruning

IonMonkey (Spidermonkey's JIT engine) represents JavaScript code as a control-flow graph (CFG) of MIR (mid-level IR) instructions. When starting to compile a function, IonMonkey first translates the bytecode to the MIR, keeping the same CFG. Afterwards, it tries to remove subtrees in the CFG that appear to not be used in order to save compilation time and potentially improve various optimizations. As an example, consider the following code, and assume further that in all previous executions only the if branch had been taken:

    if (cond_that_has_always_been_true) {
        // do something
    } else {
        // do something else
    }

In this case, branch pruning would likely decide to discard the else branch entirely and instead replace it with a bailout instruction to bailout to the baseline JIT should the branch ever be taken:

    if (cond_that_has_always_been_true) {
        // do something
    } else {
        bailout();      // will continue execution in baseline JIT
    }

## Phi Elimination

IonMonkey uses static single assignment (SSA) form for its intermediate representation of the code (MIR). In SSA form, every variable is assigned exactly once. Reassignments of variables on different branches in the CFG are handled with special Phi instructions. Consider the following example:

    var x;
    if (c) {
        x = 1337;
    } else {
        x = 1338;
    }
    print(x);

After translation to SSA form it would look something like this:

    if (c) {
        x1 = 1337;
    } else {
        x2 = 1338;
    }
    x3 = Phi(x1, x2);
    print(x3);

Phi Elimination is an optimization pass that tries to remove Phi instructions that are either redundant or unobservable (which frequently appear as result of SSA conversion and various optimizations). Quoting from the source code [4]:

    // Eliminates redundant or unobservable phis from the graph.  A
    // redundant phi is something like b = phi(a, a) or b = phi(a, b),
    // both of which can be replaced with a.  An unobservable phi is
    // one that whose value is never used in the program.

Unobservable Phis are then replaced a special value, MagicOptimizedOut [5]. In case of a bailout from the JIT, such an optimized-out value will be materialized as a JS_OPTIMIZED_OUT [6] JS magic value. This should not be observable by script since the compiler was able to prove that the variable is never used. Spidermonkey can, however, not simply leave the slot for an optimized-out variable uninitialized as e.g. the garbage collector expects a valid JS::Value in it.

Phi elimination can lead to problems in combination with branch pruning. Consider the following example:

    var only_used_in_else_branch = ...;
    if (cond_that_has_always_been_true) {
        // do something, but don't use only_used_in_else_branch
    } else {
        // do something else and use only_used_in_else_branch
    }

Here again, branch pruning might decide to remove the else branch, in which case no use of the variable remains. As such, it would be replaced by a magic JS constant (JS_OPTIMIZED_OUT) in the JIT. Later, if the else branch was actually taken, the JIT code would perform a bailout and try to restore the variable. However, as it has been removed, it would now (incorrectly) restore it as JS_OPTIMIZED_OUT magic and continue using it in the baseline JIT, where it could potentially be observed by the executing script. To avoid this, branch pruning marks SSA variables that are used in removed blocks as "useRemoved" [7], in which case the variables will not be optimized out [8].

# Bug description

While fuzzing Spidermonkey, I encountered the following sample which crashes Spidermonkey built from the current release branch:

	function poc() {
		const x = "asdf";
		for (let v7 = 0; v7 < 2; v7++) {
			function v8() {
				let v13 = 0;
				do {
					v13++;
				} while (v13 < 1200000);
			}
			const v15 = v8();
			for (let v25 = 0; v25 < 100000; v25++) {
				if (x) {
				} else {
					const v26 = {get:v8};
					for (let v30 = 0; v30 < 1000; v30++) { }
				}
			}
		}
	}
	poc();

It appears what is happening here is roughly the following:

At the beginning of JIT compilation (somewhere in one of the inner loops), IonMonkey produces the following simplified CFG (annotated with the different SSA variables for x):

          +-------+
          |   0   +-----+
          | Entry |     |
          +-------+     |
      x1 = "asdf"       v
                    +-----------+
                    |     1     |
   +--------------->| Loop Head |
   |                +--+--------+
   |                   |  x2 = Phi(x1, x5)
   |                   +--------+
   |                            v
   |   +-----------+     +------------+
   |   |     3     |     |    2...    |
   |   | OSR Entry |     | Inlined v8 |
   |   +-+---------+     +----------+-+
   |     | x3 = osrval('x')         |
   |     +---------+     +----------+
   |               v     v
   |           +-------------+
   |           |      4      |
   |           |    Merge    |
   |           +-----------+-+
   |      x4 = Phi(x3, x2) |