High severity8.8NVD Advisory· Published Jul 13, 2016· Updated May 6, 2026
CVE-2016-3260
CVE-2016-3260
Description
The Microsoft (1) JScript 9, (2) VBScript, and (3) Chakra JavaScript engines, as used in Microsoft Internet Explorer 11, Microsoft Edge, and other products, allow remote attackers to execute arbitrary code or cause a denial of service (memory corruption) via a crafted web site, aka "Scripting Engine Memory Corruption Vulnerability."
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
Microsoft.ChakraCoreNuGet | < 1.2.0.0 | 1.2.0.0 |
Patches
117f3d4a4852d1607 servicing fixes
11 files changed · +390 −131
lib/Backend/InterpreterThunkEmitter.cpp+26 −12 modified@@ -189,13 +189,13 @@ const BYTE InterpreterThunkEmitter::HeaderSize = sizeof(InterpreterThunk); const BYTE InterpreterThunkEmitter::ThunkSize = sizeof(Call); const uint InterpreterThunkEmitter::ThunksPerBlock = (BlockSize - HeaderSize) / ThunkSize; -InterpreterThunkEmitter::InterpreterThunkEmitter(ArenaAllocator* allocator, CustomHeap::CodePageAllocators * codePageAllocators, void * interpreterThunk) : +InterpreterThunkEmitter::InterpreterThunkEmitter(ArenaAllocator* allocator, CustomHeap::CodePageAllocators * codePageAllocators, bool isAsmInterpreterThunk) : emitBufferManager(allocator, codePageAllocators, /*scriptContext*/ nullptr, _u("Interpreter thunk buffer")), allocation(nullptr), allocator(allocator), thunkCount(0), thunkBuffer(nullptr), - interpreterThunk(interpreterThunk) + isAsmInterpreterThunk(isAsmInterpreterThunk) { } @@ -253,6 +253,20 @@ void InterpreterThunkEmitter::NewThunkBlock() DWORD bufferSize = BlockSize; DWORD thunkCount = 0; + void * interpreterThunk = nullptr; + + // the static interpreter thunk invoked by the dynamic emitted thunk +#ifdef ASMJS_PLAT + if (isAsmInterpreterThunk) + { + interpreterThunk = Js::InterpreterStackFrame::InterpreterAsmThunk; + } + else +#endif + { + interpreterThunk = Js::InterpreterStackFrame::InterpreterThunk; + } + allocation = emitBufferManager.AllocateBuffer(bufferSize, &buffer); if (!emitBufferManager.ProtectBufferWithExecuteReadWriteForInterpreter(allocation)) { @@ -280,7 +294,7 @@ void InterpreterThunkEmitter::NewThunkBlock() // Copy the thunk buffer and modify it. js_memcpy_s(currentBuffer, bytesRemaining, InterpreterThunk, HeaderSize); - EncodeInterpreterThunk(currentBuffer, buffer, HeaderSize, epilogStart, epilogSize); + EncodeInterpreterThunk(currentBuffer, buffer, HeaderSize, epilogStart, epilogSize, interpreterThunk); currentBuffer += HeaderSize; bytesRemaining -= HeaderSize; @@ -359,16 +373,16 @@ void InterpreterThunkEmitter::NewThunkBlock() #if _M_ARM -void InterpreterThunkEmitter::EncodeInterpreterThunk(__in_bcount(thunkSize) BYTE* thunkBuffer, __in_bcount(thunkSize) BYTE* thunkBufferStartAddress, __in const DWORD thunkSize, __in_bcount(epilogSize) BYTE* epilogStart, __in const DWORD epilogSize) +void InterpreterThunkEmitter::EncodeInterpreterThunk(__in_bcount(thunkSize) BYTE* thunkBuffer, __in_bcount(thunkSize) BYTE* thunkBufferStartAddress, __in const DWORD thunkSize, __in_bcount(epilogSize) BYTE* epilogStart, __in const DWORD epilogSize, __in void * const interpreterThunk) { _Analysis_assume_(thunkSize == HeaderSize); // Encode MOVW - DWORD lowerThunkBits = (uint32)this->interpreterThunk & 0x0000FFFF; + DWORD lowerThunkBits = (uint32)interpreterThunk & 0x0000FFFF; DWORD movW = EncodeMove(/*Opcode*/ 0x0000F240, /*register*/1, lowerThunkBits); Emit(thunkBuffer,ThunkAddressOffset, movW); // Encode MOVT - DWORD higherThunkBits = ((uint32)this->interpreterThunk & 0xFFFF0000) >> 16; + DWORD higherThunkBits = ((uint32)interpreterThunk & 0xFFFF0000) >> 16; DWORD movT = EncodeMove(/*Opcode*/ 0x0000F2C0, /*register*/1, higherThunkBits); Emit(thunkBuffer, ThunkAddressOffset + sizeof(movW), movT); @@ -424,7 +438,7 @@ void InterpreterThunkEmitter::GeneratePdata(_In_ const BYTE* entryPoint, _In_ co } #elif _M_ARM64 -void InterpreterThunkEmitter::EncodeInterpreterThunk(__in_bcount(thunkSize) BYTE* thunkBuffer, __in_bcount(thunkSize) BYTE* thunkBufferStartAddress, __in const DWORD thunkSize, __in_bcount(epilogSize) BYTE* epilogStart, __in const DWORD epilogSize) +void InterpreterThunkEmitter::EncodeInterpreterThunk(__in_bcount(thunkSize) BYTE* thunkBuffer, __in_bcount(thunkSize) BYTE* thunkBufferStartAddress, __in const DWORD thunkSize, __in_bcount(epilogSize) BYTE* epilogStart, __in const DWORD epilogSize, __in void * const interpreterThunk) { int addrOffset = ThunkAddressOffset; @@ -434,28 +448,28 @@ void InterpreterThunkEmitter::EncodeInterpreterThunk(__in_bcount(thunkSize) BYTE // Following 4 MOV Instrs are to move the 64-bit address of the InterpreterThunk address into register x1. // Encode MOVZ (movz x1, #<interpreterThunk 16-0 bits>) - DWORD lowerThunkBits = (uint64)this->interpreterThunk & 0x0000FFFF; + DWORD lowerThunkBits = (uint64)interpreterThunk & 0x0000FFFF; DWORD movZ = EncodeMove(/*Opcode*/ 0xD2800000, /*register x1*/1, lowerThunkBits); // no shift; hw = 00 Emit(thunkBuffer,addrOffset, movZ); AssertMsg(sizeof(movZ) == 4, "movZ has to be 32-bit encoded"); addrOffset+= sizeof(movZ); // Encode MOVK (movk x1, #<interpreterThunk 32-16 bits>, lsl #16) - DWORD higherThunkBits = ((uint64)this->interpreterThunk & 0xFFFF0000) >> 16; + DWORD higherThunkBits = ((uint64)interpreterThunk & 0xFFFF0000) >> 16; DWORD movK = EncodeMove(/*Opcode*/ 0xF2A00000, /*register x1*/1, higherThunkBits); // left shift 16 bits; hw = 01 Emit(thunkBuffer, addrOffset, movK); AssertMsg(sizeof(movK) == 4, "movK has to be 32-bit encoded"); addrOffset+= sizeof(movK); // Encode MOVK (movk x1, #<interpreterThunk 48-32 bits>, lsl #16) - higherThunkBits = ((uint64)this->interpreterThunk & 0xFFFF00000000) >> 32; + higherThunkBits = ((uint64)interpreterThunk & 0xFFFF00000000) >> 32; movK = EncodeMove(/*Opcode*/ 0xF2C00000, /*register x1*/1, higherThunkBits); // left shift 32 bits; hw = 02 Emit(thunkBuffer, addrOffset, movK); AssertMsg(sizeof(movK) == 4, "movK has to be 32-bit encoded"); addrOffset += sizeof(movK); // Encode MOVK (movk x1, #<interpreterThunk 64-48 bits>, lsl #16) - higherThunkBits = ((uint64)this->interpreterThunk & 0xFFFF000000000000) >> 48; + higherThunkBits = ((uint64)interpreterThunk & 0xFFFF000000000000) >> 48; movK = EncodeMove(/*Opcode*/ 0xF2E00000, /*register x1*/1, higherThunkBits); // left shift 48 bits; hw = 03 AssertMsg(sizeof(movK) == 4, "movK has to be 32-bit encoded"); Emit(thunkBuffer, addrOffset, movK); @@ -498,7 +512,7 @@ void InterpreterThunkEmitter::GeneratePdata(_In_ const BYTE* entryPoint, _In_ co function->FrameSize = 5; // the number of bytes of stack that is allocated for this function divided by 16 } #else -void InterpreterThunkEmitter::EncodeInterpreterThunk(__in_bcount(thunkSize) BYTE* thunkBuffer, __in_bcount(thunkSize) BYTE* thunkBufferStartAddress, __in const DWORD thunkSize, __in_bcount(epilogSize) BYTE* epilogStart, __in const DWORD epilogSize) +void InterpreterThunkEmitter::EncodeInterpreterThunk(__in_bcount(thunkSize) BYTE* thunkBuffer, __in_bcount(thunkSize) BYTE* thunkBufferStartAddress, __in const DWORD thunkSize, __in_bcount(epilogSize) BYTE* epilogStart, __in const DWORD epilogSize, __in void * const interpreterThunk) { _Analysis_assume_(thunkSize == HeaderSize); Emit(thunkBuffer, ThunkAddressOffset, (uintptr_t)interpreterThunk);
lib/Backend/InterpreterThunkEmitter.h+3 −3 modified@@ -61,7 +61,7 @@ class InterpreterThunkEmitter EmitBufferAllocation *allocation; SListBase<ThunkBlock> thunkBlocks; SListBase<ThunkBlock> freeListedThunkBlocks; - void * interpreterThunk; // the static interpreter thunk invoked by the dynamic emitted thunk + bool isAsmInterpreterThunk; // To emit address of InterpreterAsmThunk or InterpreterThunk BYTE* thunkBuffer; ArenaAllocator* allocator; DWORD thunkCount; // Count of thunks available in the current thunk block @@ -94,7 +94,7 @@ class InterpreterThunkEmitter /* ------private helpers -----------*/ void NewThunkBlock(); - void EncodeInterpreterThunk(__in_bcount(thunkSize) BYTE* thunkBuffer, __in_bcount(thunkSize) BYTE* thunkBufferStartAddress, __in const DWORD thunkSize, __in_bcount(epilogSize) BYTE* epilogStart, __in const DWORD epilogSize); + void EncodeInterpreterThunk(__in_bcount(thunkSize) BYTE* thunkBuffer, __in_bcount(thunkSize) BYTE* thunkBufferStartAddress, __in const DWORD thunkSize, __in_bcount(epilogSize) BYTE* epilogStart, __in const DWORD epilogSize, __in void * const interpreterThunk); #if defined(_M_ARM32_OR_ARM64) DWORD EncodeMove(DWORD opCode, int reg, DWORD imm16); void GeneratePdata(_In_ const BYTE* entryPoint, _In_ const DWORD functionSize, _Out_ RUNTIME_FUNCTION* function); @@ -116,7 +116,7 @@ class InterpreterThunkEmitter static const uint BlockSize; static void* ConvertToEntryPoint(PVOID dynamicInterpreterThunk); - InterpreterThunkEmitter(ArenaAllocator* allocator, CustomHeap::CodePageAllocators * codePageAllocators, void * interpreterThunk); + InterpreterThunkEmitter(ArenaAllocator* allocator, CustomHeap::CodePageAllocators * codePageAllocators, bool isAsmInterpreterThunk = false); BYTE* GetNextThunk(PVOID* ppDynamicInterpreterThunk);
lib/Runtime/Base/ScriptContext.cpp+2 −3 modified@@ -1155,13 +1155,12 @@ if (!sourceList) } #if DYNAMIC_INTERPRETER_THUNK - interpreterThunkEmitter = HeapNew(InterpreterThunkEmitter, SourceCodeAllocator(), this->GetThreadContext()->GetThunkPageAllocators(), - Js::InterpreterStackFrame::InterpreterThunk); + interpreterThunkEmitter = HeapNew(InterpreterThunkEmitter, SourceCodeAllocator(), this->GetThreadContext()->GetThunkPageAllocators()); #endif #ifdef ASMJS_PLAT asmJsInterpreterThunkEmitter = HeapNew(InterpreterThunkEmitter, SourceCodeAllocator(), this->GetThreadContext()->GetThunkPageAllocators(), - Js::InterpreterStackFrame::InterpreterAsmThunk); + true); #endif JS_ETW(EtwTrace::LogScriptContextLoadEvent(this));
lib/Runtime/Library/ArrayBuffer.cpp+26 −3 modified@@ -878,6 +878,29 @@ namespace Js #endif } + // Copy memory from src to dst, truncate if dst smaller, zero extra memory + // if dst larger + static void MemCpyZero(__bcount(dstSize) BYTE* dst, size_t dstSize, + __in_bcount(count) const BYTE* src, size_t count) + { + js_memcpy_s(dst, dstSize, src, min(dstSize, count)); + if (dstSize > count) + { + ZeroMemory(dst + count, dstSize - count); + } + } + + // Same as realloc but zero newly allocated portion if newSize > oldSize + static BYTE* ReallocZero(BYTE* ptr, size_t oldSize, size_t newSize) + { + BYTE* ptrNew = (BYTE*)realloc(ptr, newSize); + if (ptrNew && newSize > oldSize) + { + ZeroMemory(ptrNew + oldSize, newSize - oldSize); + } + return ptrNew; + } + ArrayBuffer * JavascriptArrayBuffer::TransferInternal(uint32 newBufferLength) { ArrayBuffer* newArrayBuffer; @@ -940,7 +963,7 @@ namespace Js recycler->ReportExternalMemoryFailure(newBufferLength); JavascriptError::ThrowOutOfMemoryError(GetScriptContext()); } - js_memcpy_s(newBuffer, newBufferLength, this->buffer, newBufferLength); + MemCpyZero(newBuffer, newBufferLength, this->buffer, this->bufferLength); } } else @@ -949,12 +972,12 @@ namespace Js { // we are transferring from an unoptimized buffer, but new length can be optimized, so move to that newBuffer = (BYTE*)JavascriptArrayBuffer::AllocWrapper(newBufferLength); - js_memcpy_s(newBuffer, newBufferLength, this->buffer, newBufferLength); + MemCpyZero(newBuffer, newBufferLength, this->buffer, this->bufferLength); } else if (newBufferLength != this->bufferLength) { // both sides will just be regular ArrayBuffer, so realloc - newBuffer = (BYTE*)realloc(this->buffer, newBufferLength); + newBuffer = ReallocZero(this->buffer, this->bufferLength, newBufferLength); if (!newBuffer) { recycler->ReportExternalMemoryFailure(newBufferLength);
lib/Runtime/Library/JavascriptArray.cpp+58 −34 modified@@ -2918,7 +2918,8 @@ namespace Js } } - if (pDestArray && JavascriptArray::IsDirectAccessArray(aItem) && JavascriptArray::IsDirectAccessArray(pDestArray)) // Fast path + if (pDestArray && JavascriptArray::IsDirectAccessArray(aItem) && JavascriptArray::IsDirectAccessArray(pDestArray) + && BigIndex(idxDest + JavascriptArray::FromVar(aItem)->length).IsSmallIndex()) // Fast path { if (JavascriptNativeIntArray::Is(aItem)) { @@ -5700,6 +5701,7 @@ namespace Js bool isIntArray = false; bool isFloatArray = false; bool isTypedArrayEntryPoint = typedArrayBase != nullptr; + bool isBuiltinArrayCtor = true; T startT = 0; T newLenT = length; T endT = length; @@ -5752,10 +5754,11 @@ namespace Js if (isTypedArrayEntryPoint) { Var constructor = JavascriptOperators::SpeciesConstructor(typedArrayBase, TypedArrayBase::GetDefaultConstructor(args[0], scriptContext), scriptContext); + isBuiltinArrayCtor = (constructor == library->GetArrayConstructor()); // If we have an array source object, we need to make sure to do the right thing if it's a native array. // The helpers below which do the element copying require the source and destination arrays to have the same native type. - if (pArr && constructor == library->GetArrayConstructor()) + if (pArr && isBuiltinArrayCtor) { if (newLenT > JavascriptArray::MaxArrayLength) { @@ -5789,13 +5792,13 @@ namespace Js else if (pArr != nullptr) { - newObj = ArraySpeciesCreate(pArr, newLenT, scriptContext, &isIntArray, &isFloatArray); + newObj = ArraySpeciesCreate(pArr, newLenT, scriptContext, &isIntArray, &isFloatArray, &isBuiltinArrayCtor); } // skip the typed array and "pure" array case, we still need to handle special arrays like es5array, remote array, and proxy of array. else { - newObj = ArraySpeciesCreate(obj, newLenT, scriptContext); + newObj = ArraySpeciesCreate(obj, newLenT, scriptContext, nullptr, nullptr, &isBuiltinArrayCtor); } // If we didn't create a new object above we will create a new array here. @@ -5839,7 +5842,7 @@ namespace Js if (pArr) { // If we constructed a new Array object, we have some nice helpers here - if (newArr) + if (newArr && isBuiltinArrayCtor) { if (JavascriptArray::IsDirectAccessArray(newArr)) { @@ -5885,7 +5888,7 @@ namespace Js continue; } - newArr->DirectSetItemAt(i, element); + newArr->SetItem(i, element, PropertyOperation_None); } } } @@ -5951,7 +5954,7 @@ namespace Js Var element = JavascriptOperators::GetItem(obj, i + start, scriptContext); if (newArr != nullptr) { - newArr->DirectSetItemAt(i, element); + newArr->SetItem(i, element, PropertyOperation_None); } else { @@ -6238,21 +6241,22 @@ namespace Js { countUndefined++; } - orig[i] = SparseArraySegment<Var>::GetMissingItem(); } } - if (count == 0) + if (count > 0) { - *len = 0; // set the length to zero - return countUndefined; - } + SortElements(elements, 0, count - 1); - SortElements(elements, 0, count - 1); + for (uint32 i = 0; i < count; ++i) + { + orig[i] = elements[i].Value; + } + } - for (uint32 i = 0; i < count; ++i) + for (uint32 i = count + countUndefined; i < *len; ++i) { - orig[i] = elements[i].Value; + orig[i] = SparseArraySegment<Var>::GetMissingItem(); } *len = count; // set the correct length @@ -6583,14 +6587,15 @@ namespace Js bool isIntArray = false; bool isFloatArray = false; + bool isBuiltinArrayCtor = true; JavascriptArray *newArr = nullptr; // Just dump the segment map on splice (before any possible allocation and throw) pArr->ClearSegmentMap(); // If the source object is an Array exotic object (Array.isArray) we should try to load the constructor property // and use it to construct the return object. - newObj = ArraySpeciesCreate(pArr, deleteLen, scriptContext); + newObj = ArraySpeciesCreate(pArr, deleteLen, scriptContext, nullptr, nullptr, &isBuiltinArrayCtor); if (newObj != nullptr) { pArr = EnsureNonNativeArray(pArr); @@ -6608,7 +6613,7 @@ namespace Js } // If return object is a JavascriptArray, we can use all the array splice helpers - if (newArr) + if (newArr && isBuiltinArrayCtor) { // Array has a single segment (need not start at 0) and splice start lies in the range @@ -6687,6 +6692,15 @@ namespace Js pArr->length = newLen; } + if (newArr->length != deleteLen) + { + newArr->SetLength(deleteLen); + } + else + { + newArr->length = deleteLen; + } + newArr->InvalidateLastUsedSegment(); #ifdef VALIDATE_ARRAY @@ -7103,7 +7117,7 @@ namespace Js Var element = JavascriptOperators::GetItem(pObj, start + i, scriptContext); if (pnewArr) { - pnewArr->DirectSetItemAt(i, element); + pnewArr->SetItem(i, element, PropertyOperation_None); } else { @@ -7164,6 +7178,7 @@ namespace Js // Set up new length indexT newLen = indexT(len - deleteLen) + insertLen; h.ThrowTypeErrorOnFailure(JavascriptOperators::SetProperty(pObj, pObj, PropertyIds::length, IndexTrace<indexT>::ToNumber(newLen, scriptContext), scriptContext, PropertyOperation_ThrowIfNotExtensible)); + h.ThrowTypeErrorOnFailure(JavascriptOperators::SetProperty(pNewObj, pNewObj, PropertyIds::length, IndexTrace<indexT>::ToNumber(deleteLen, scriptContext), scriptContext, PropertyOperation_ThrowIfNotExtensible)); #ifdef VALIDATE_ARRAY if (pnewArr) { @@ -8790,6 +8805,7 @@ namespace Js RecyclableObject* newObj = nullptr; JavascriptArray* newArr = nullptr; bool isTypedArrayEntryPoint = typedArrayBase != nullptr; + bool isBuiltinArrayCtor = true; if (args.Info.Count < 2 || !JavascriptConversion::IsCallable(args[1])) { @@ -8827,6 +8843,7 @@ namespace Js { Var constructor = JavascriptOperators::SpeciesConstructor( typedArrayBase, TypedArrayBase::GetDefaultConstructor(args[0], scriptContext), scriptContext); + isBuiltinArrayCtor = (constructor == scriptContext->GetLibrary()->GetArrayConstructor()); if (JavascriptOperators::IsConstructor(constructor)) { @@ -8843,7 +8860,7 @@ namespace Js // skip the typed array and "pure" array case, we still need to handle special arrays like es5array, remote array, and proxy of array. else if (pArr == nullptr || scriptContext->GetConfig()->IsES6SpeciesEnabled()) { - newObj = ArraySpeciesCreate(obj, length, scriptContext); + newObj = ArraySpeciesCreate(obj, length, scriptContext, nullptr, nullptr, &isBuiltinArrayCtor); } if (newObj == nullptr) @@ -8891,7 +8908,7 @@ namespace Js pArr); // If newArr is a valid pointer, then we constructed an array to return. Otherwise we need to do generic object operations - if (newArr) + if (newArr && isBuiltinArrayCtor) { newArr->DirectSetItemAt(k, mappedValue); } @@ -9662,7 +9679,7 @@ namespace Js if (newArr) { - newArr->DirectSetItemAt(k, nextValue); + newArr->SetItem(k, nextValue, PropertyOperation_None); } else { @@ -9731,7 +9748,7 @@ namespace Js if (newArr) { - newArr->DirectSetItemAt(k, kValue); + newArr->SetItem(k, kValue, PropertyOperation_None); } else { @@ -9781,10 +9798,12 @@ namespace Js Var newObj = nullptr; JavascriptArray* newArr = nullptr; TypedArrayBase* newTypedArray = nullptr; + bool isBuiltinArrayCtor = true; if (JavascriptOperators::IsConstructor(args[0])) { RecyclableObject* constructor = RecyclableObject::FromVar(args[0]); + isBuiltinArrayCtor = (constructor == scriptContext->GetLibrary()->GetArrayConstructor()); Js::Var constructorArgs[] = { constructor, JavascriptNumber::ToVar(len, scriptContext) }; Js::CallInfo constructorCallInfo(Js::CallFlags_New, _countof(constructorArgs)); @@ -9818,7 +9837,7 @@ namespace Js // At least we have a new object of some kind Assert(newObj); - if (newArr) + if (isBuiltinArrayCtor) { for (uint32 k = 0; k < len; k++) { @@ -9999,11 +10018,11 @@ namespace Js if (index < startIndex) continue; else if (index >= limitIndex) break; - Var value; - BOOL success = JavascriptOperators::GetOwnItem(es5Array, index, &value, scriptContext); - Assert(success); - - fn(index, value); + Var value = nullptr; + if (JavascriptOperators::GetOwnItem(es5Array, index, &value, scriptContext)) + { + fn(index, value); + } } } } @@ -10066,11 +10085,11 @@ namespace Js T n = destIndex + (index - startIndex); if (destArray == nullptr || !destArray->DirectGetItemAt(n, &oldValue)) { - Var value; - BOOL success = JavascriptOperators::GetOwnItem(es5Array, index, &value, scriptContext); - Assert(success); - - fn(index, value); + Var value = nullptr; + if (JavascriptOperators::GetOwnItem(es5Array, index, &value, scriptContext)) + { + fn(index, value); + } } } } @@ -11353,7 +11372,7 @@ namespace Js template<typename T> RecyclableObject* - JavascriptArray::ArraySpeciesCreate(Var originalArray, T length, ScriptContext* scriptContext, bool* pIsIntArray, bool* pIsFloatArray) + JavascriptArray::ArraySpeciesCreate(Var originalArray, T length, ScriptContext* scriptContext, bool *pIsIntArray, bool *pIsFloatArray, bool *pIsBuiltinArrayCtor) { if (originalArray == nullptr || !scriptContext->GetConfig()->IsES6SpeciesEnabled()) { @@ -11427,6 +11446,11 @@ namespace Js JavascriptError::ThrowTypeError(scriptContext, JSERR_NotAConstructor, _u("constructor[Symbol.species]")); } + if (pIsBuiltinArrayCtor != nullptr) + { + *pIsBuiltinArrayCtor = false; + } + Js::Var constructorArgs[] = { constructor, JavascriptNumber::ToVar(length, scriptContext) }; Js::CallInfo constructorCallInfo(Js::CallFlags_New, _countof(constructorArgs));
lib/Runtime/Library/JavascriptArray.h+1 −1 modified@@ -767,7 +767,7 @@ namespace Js static void ConcatFloatArgs(JavascriptNativeFloatArray* pDestArray, TypeId* remoteTypeIds, Js::Arguments& args, ScriptContext* scriptContext); private: template<typename T=uint32> - static RecyclableObject* ArraySpeciesCreate(Var pThisArray, T length, ScriptContext* scriptContext, bool* pIsIntArray = nullptr, bool* pIsFloatArray = nullptr); + static RecyclableObject* ArraySpeciesCreate(Var pThisArray, T length, ScriptContext* scriptContext, bool *pIsIntArray = nullptr, bool *pIsFloatArray = nullptr, bool *pIsBuiltinArrayCtor = nullptr); template <typename T, typename R> static R ConvertToIndex(T idxDest, ScriptContext* scriptContext) { Throw::InternalError(); return 0; } template <> static Var ConvertToIndex<uint32, Var>(uint32 idxDest, ScriptContext* scriptContext); template <> static uint32 ConvertToIndex<uint32, uint32>(uint32 idxDest, ScriptContext* scriptContext) { return idxDest; }
test/Array/Array_TypeConfusion_bugs.js+239 −0 added@@ -0,0 +1,239 @@ +//------------------------------------------------------------------------------------------------------- +// Copyright (C) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. +//------------------------------------------------------------------------------------------------------- + +//Note: see function ArraySpliceHelper of JavascriptArray.cpp + +if (this.WScript && this.WScript.LoadScriptFile) { // Check for running in ch + this.WScript.LoadScriptFile("..\\UnitTestFramework\\UnitTestFramework.js"); +} + +var tests = [ + { + name: "OS7342663:OOB writes using type confusion in InternalCopyArrayElements", + body: function () + { + function test() { + var arr1 = [0xdead, 0xbabe, 0xdead, 0xbabe]; + + class MyArray extends Uint32Array { } + Object.defineProperty(MyArray, Symbol.species, { value: function() { return arr1; } }); + + var float_val = 0xdaddeadbabe * 4.9406564584124654E-324; + var test = [float_val, float_val, float_val, float_val]; + test.length = 0x1000; + test.__proto__ = new MyArray(0); + + var res = Array.prototype.slice.apply(test, []); // OOB write + assert.areEqual(0x1000, res.length, "res.length == 0x1000"); + assert.areEqual(float_val, res[0], "res[0] == float_val"); + assert.areEqual(float_val, res[1], "res[1] == float_val"); + assert.areEqual(float_val, res[2], "res[2] == float_val"); + assert.areEqual(float_val, res[3], "res[3] == float_val"); + assert.areEqual(undefined, res[4], "res[4] == float_val"); + assert.areEqual(undefined, res[0xfff], "res[0xfff] == undefined"); + } + test(); + test(); + test(); + } + }, + { + name: "OS7342689:OOB writes using type confusion in InternalFillFromPrototypes", + body: function () + { + function test() { + var arr1 = [0xdead, 0xbabe, 0xdead, 0xbabe, 0xdead, 0xbabe, 0xdead, 0xbabe, 0xdead, 0xbabe, 0xdead, + 0xbabe, 0xdead, 0xbabe, 0xdead, 0xbabe, 0xdead, 0xbabe, 0xdead, 0xbabe, 0xdead, 0xbabe, 0xdead, 0xbabe, + 0xdead, 0xbabe, 0xdead, 0xbabe, 0xdead, 0xbabe, 0xdead, 0xbabe, 0xdead, 0xbabe, 0xdead, 0xbabe, 0xdead, + 0xbabe, 0xdead, 0xbabe, 0xdead, 0xbabe, 0xdead, 0xbabe, 0xdead, 0xbabe, 0xdead, 0xbabe]; + + class MyArray extends Uint32Array { } + Object.defineProperty(MyArray, Symbol.species, { value: function() { return arr1; } }); + + var float_val = 0xdaddeadbabe * 4.9406564584124654E-324; + var test = [{}]; + delete test[0]; + test.length = 0x1000; + var src = [float_val, float_val, float_val, float_val, float_val, float_val, float_val, float_val, + float_val, float_val, float_val, float_val, float_val, float_val, float_val, float_val, float_val, float_val, + float_val, float_val, float_val, float_val, float_val, float_val, float_val, float_val, float_val, float_val, + float_val, float_val, float_val, float_val, float_val, float_val, float_val, float_val, float_val, float_val, + float_val, float_val, float_val, float_val, float_val, float_val, float_val, float_val, float_val, float_val, float_val]; + test.__proto__ = src; + test.__proto__.__proto__ = new MyArray(0); + + //this will write 0xfffc0daddeadbabe to [arr1] + 0x1D8 + var res = Array.prototype.slice.apply(test, []) + assert.areEqual(0x1000, res.length, "res.length == 0x1000"); + assert.areEqual(float_val, res[0], "res[0] == float_val"); + assert.areEqual(float_val, res[1], "res[1] == float_val"); + assert.areEqual(float_val, res[2], "res[2] == float_val"); + assert.areEqual(float_val, res[src.length-1], "res[src.length-1] == float_val"); + assert.areEqual(undefined, res[src.length], "res[src] == undefined"); + assert.areEqual(undefined, res[0xfff], "res[0xfff] == undefined"); + } + test(); + test(); + test(); + } + }, + { + name: "OS7307908:type confusion in Array.prototype.slice", + body: function () + { + function test() { + var arr = [1, 2] + + //Our species function will get called during chakra!Js::JavascriptArray::SliceHelper<unsigned int> + Object.defineProperty( + arr.constructor, + Symbol.species, + { + value : function() + { + //change 'arr' from TypeIds_NativeIntArray to TypeIds_Array + arr[0] = WScript; + + //return a TypeIds_NativeIntArray so we can read back out the 64 bit pointer as two 32bit ints. + return []; + } + } + ); + + //trigger the bug and retrieve a TypeIds_NativeIntArray array containing a pointer. + var brr = arr.slice(); + + assert.areEqual(2, brr.length, "brr.length == 2"); + assert.areEqual(WScript, brr[0], "brr[0] == WScript"); + assert.areEqual(2, brr[1], "brr[0] == WScript"); + } + test(); + test(); + test(); + } + }, + { + name: "OS7342791:type confusion in Array.from", + body: function () + { + function test() { + var arr1 = [0xdead, 0xbabe, 0xdead, 0xbabe, 0xdead, 0xbabe, 0xdead, 0xbabe]; + + var float_val = 0xdaddeadbabe * 4.9406564584124654E-324; + var test = [float_val, float_val, float_val, float_val]; + delete test[0]; + delete test[1]; + delete test[2]; + + var res = Array.from.apply(function(){return arr1}, [test]); + assert.areEqual(4, res.length, "res.length == 4"); + assert.areEqual(undefined, res[0], "res[0] == undefined"); + assert.areEqual(undefined, res[1], "res[1] == undefined"); + assert.areEqual(undefined, res[2], "res[2] == undefined"); + assert.areEqual(float_val, res[3], "res[3] == float_val"); + + assert.areEqual(['1','2','3'], Array.from.apply(()=>new Array(), ["123"]), "Array.from on iterable"); + assert.areEqual([1,2,3], Array.from.apply(()=>new Array(), [{"0":1, "1":2, "2":3, "length":3}]), "Array.from on non-iterable"); + } + test(); + test(); + test(); + } + }, + { + name: "OS7342844:type confusion in Array.of", + body: function () + { + function test() { + var brr = Array.of.call(()=>[ 1, 2, 3, 4 ], + WScript, // supply 2 copies of target so the brr array will have a length of 2 and we can read the 64bit pointer. + WScript + ); + + assert.areEqual(2, brr.length, "brr.length == 2"); + assert.areEqual(WScript, brr[0], "res[0] == WScript"); + assert.areEqual(WScript, brr[1], "res[1] == WScript"); + assert.areEqual(undefined, brr[2], "res[2] == undefined"); + assert.areEqual(undefined, brr[3], "res[3] == undefined"); + } + test(); + test(); + test(); + } + }, + { + name: "OS7342907:type confusion in Array.prototype.map", + body: function () + { + function test() { + var arr = [ 1, 2 ]; + + Object.defineProperty( + arr.constructor, + Symbol.species, + { + value : function() + { + return []; + } + } + ); + + //The value returned from our callback is directly set into the array whose type we create via the species. + var brr = arr.map( function( v ) + { + if( v == 1 ) + return WScript; + } + ); + + assert.areEqual(2, brr.length, "brr.length == 2"); + assert.areEqual(WScript, brr[0], "brr[0] == WScript"); + assert.areEqual(undefined, brr[1], "brr[1] == undefined"); + } + test(); + test(); + test(); + } + }, + { + name: "OS7342965:type confusion in Array.prototype.splice", + body: function () + { + function test() { + //create a TypeIds_Array holding two 64 bit values (The same amount of space for four 32 bit values). + var arr = [ WScript, WScript ]; + + //Our species function will get called during chakra!Js::JavascriptArray::EntrySplice + Object.defineProperty( + arr.constructor, + Symbol.species, + { + value : function() + { + //return a TypeIds_NativeIntArray so we can read back out a 64 bit pointer as two 32bit ints. + return [ 1, 2, 3, 4 ]; + } + } + ); + + //trigger the bug and retrieve a TypeIds_NativeIntArray array containing a pointer. The helper + //method ArraySegmentSpliceHelper<Var> will directly copy over the TypeIds_Array segment data + //into the TypeIds_NativeIntArray segment. + var brr = arr.splice( 0, 2 ); + + assert.areEqual(2, brr.length, "brr.length == 2"); + assert.areEqual(WScript, brr[0], "brr[0] == WScript"); + assert.areEqual(WScript, brr[1], "brr[1] == WScript"); + assert.areEqual(undefined, brr[2], "brr[2] == undefined"); + assert.areEqual(undefined, brr[3], "brr[3] == undefined"); + } + test(); + test(); + test(); + } + }, +]; +testRunner.runTests(tests, { verbose: WScript.Arguments[0] != "summary" });
test/Array/concat2.baseline+20 −35 modified@@ -1,41 +1,26 @@ -------concat Small------------- - concat 101, 102, 103, 104, 105 -length: 2147483653 - 2147483647: 100 - 2147483648: 101 - 2147483649: 102 - 2147483650: 103 - 2147483651: 104 - 2147483652: 105 +length: 2147483647 + 2147483641: 100 + 2147483642: 101 + 2147483643: 102 + 2147483644: 103 + 2147483645: 104 + 2147483646: 105 - arr.concat(arr) -length: 4294967295 - 2147483647: 100 - 2147483648: 101 - 2147483649: 102 - 2147483650: 103 - 2147483651: 104 - 2147483652: 105 - 4294967300: 100 - 4294967301: 101 - 4294967302: 102 - 4294967303: 103 - 4294967304: 104 - 4294967305: 105 --------concat Large------------- -- concat 101, 102, 103, 104, 105 -length: 4294967295 - 4294967293: 100 - 4294967294: 101 - 4294967295: 102 - 4294967296: 103 - 4294967297: 104 - 4294967298: 105 -- arr.concat(arr) -length: 4294967295 - 4294967293: 100 - 4294967294: 101 - 8589934588: 100 - 8589934589: 101 +length: 4294967294 + 2147483641: 100 + 2147483642: 101 + 2147483643: 102 + 2147483644: 103 + 2147483645: 104 + 2147483646: 105 + 4294967288: 100 + 4294967289: 101 + 4294967290: 102 + 4294967291: 103 + 4294967292: 104 + 4294967293: 105 -------test prototype lookup------------- a: 200,101,202,203,204,105,106,207 r: 200,101,202,203,204,105,106,207,300,301,302,303,304
test/Array/concat2.js+5 −3 modified@@ -38,10 +38,12 @@ function test_concat(size) { } echo("-------concat Small-------------"); -test_concat(2147483648); +test_concat(2147483642); -echo("-------concat Large-------------"); -test_concat(4294967294); +// Fix for MSRC 33319 changes concat to skip a fast path if the index we're copying into is a BigIndex. +// Disable the large portion of this test since this change makes such a test run for hours. +//echo("-------concat Large-------------"); +//test_concat(4294967294); echo("-------test prototype lookup-------------"); for (var i = 0; i < 10; i++) {
test/Array/rlexe.xml+7 −0 modified@@ -706,4 +706,11 @@ <compile-flags>-mic:1 -off:simplejit</compile-flags> </default> </test> + <test> + <default> + <files>Array_TypeConfusion_bugs.js</files> + <compile-flags>-args summary -endargs</compile-flags> + <tags>BugFix</tags> + </default> + </test> </regress-exe>
test/es6/es6toLength.js+3 −37 modified@@ -12,46 +12,12 @@ var tests = [ { var c = []; c[0] = 1; - c[4294967294] = 2; - var obj = {length : 3, 0 : 3, 1 : 4, 2: 5, [Symbol.isConcatSpreadable] : true} - c = c.concat(obj); - assert.areEqual(1, c[0], "confirm indices of array concated to did not change") - assert.areEqual(2, c[4294967294], "confirm indices of array concated to did not change"); - assert.areEqual(3, c[4294967295], "confirm obj is spread as properties beyond array length bound"); - assert.areEqual(4, c[4294967296], "confirm obj is spread as properties beyond array length bound"); - assert.areEqual(5, c[4294967297], "confirm obj is spread as properties beyond array length bound"); - assert.areEqual(4294967295, c.length, "length maxes out at 4294967295"); - - var c = []; - c[0] = 1; - c[4294967294] = 2; - c = c.concat(3,4,5); - assert.areEqual(1, c[0], "confirm indices of array concated to did not change") - assert.areEqual(2, c[4294967294], "confirm indices of array concated to did not change"); - assert.areEqual(3, c[4294967295], "confirm integers are spread as properties beyond array length bound"); - assert.areEqual(4, c[4294967296], "confirm integers are spread as properties beyond array length bound"); - assert.areEqual(5, c[4294967297], "confirm integers are spread as properties beyond array length bound"); - assert.areEqual(4294967295, c.length, "length maxes out at 4294967295"); - - var c = []; - c[0] = 1; - c[4294967294] = 2; - c = c.concat([3,4,5]); - assert.areEqual(1, c[0], "confirm indices of array concated to did not change") - assert.areEqual(2, c[4294967294], "confirm indices of array concated to did not change"); - assert.areEqual(3, c[4294967295], "confirm array is spread as properties beyond array length bound"); - assert.areEqual(4, c[4294967296], "confirm array is spread as properties beyond array length bound"); - assert.areEqual(5, c[4294967297], "confirm array is spread as properties beyond array length bound"); - assert.areEqual(4294967295, c.length, "length maxes out at 4294967295"); - - var c = []; - c[0] = 1; - c[4294967294] = 2; + c[4294967293] = 2; var oNeg = { length : -1, 0 : 3, 1: 4, [Symbol.isConcatSpreadable] : true}; c = c.concat(oNeg); assert.areEqual(1, c[0], "confirm indices of array concated to did not change") - assert.areEqual(2, c[4294967294], "confirm indices of array concated to did not change"); - assert.areEqual(undefined, c[4294967295], "Length of oNeg is coerced to 0 nothing is concated here"); + assert.areEqual(2, c[4294967293], "confirm indices of array concated to did not change"); + assert.areEqual(undefined, c[4294967294], "Length of oNeg is coerced to 0 nothing is concated here"); } }, {
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
10- github.com/advisories/GHSA-h6g3-73h7-chxpghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2016-3260ghsaADVISORY
- docs.microsoft.com/en-us/security-updates/securitybulletins/2016/ms16-084nvdWEB
- docs.microsoft.com/en-us/security-updates/securitybulletins/2016/ms16-085nvdWEB
- github.com/chakra-core/ChakraCore/commit/17f3d4a4852dcc9e48de7091685b1862afb9f307ghsaWEB
- github.com/chakra-core/ChakraCore/pull/1291ghsaWEB
- web.archive.org/web/20210123150650/http://www.securityfocus.com/bid/91580ghsaWEB
- web.archive.org/web/20211202003833/http://www.securitytracker.com/id/1036283ghsaWEB
- www.securityfocus.com/bid/91580nvd
- www.securitytracker.com/id/1036283nvd
News mentions
0No linked articles in our index yet.