High severity8.8NVD Advisory· Published Jul 13, 2016· Updated May 6, 2026
CVE-2016-3259
CVE-2016-3259
Description
The Microsoft (1) JScript 9, (2) VBScript, and (3) Chakra JavaScript engines, as used in Microsoft Internet Explorer 9 through 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," a different vulnerability than CVE-2016-3248.
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-wm27-49x2-mg9qghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2016-3259ghsaADVISORY
- 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/20210125172712/http://www.securityfocus.com/bid/91581ghsaWEB
- web.archive.org/web/20211202003833/http://www.securitytracker.com/id/1036283ghsaWEB
- www.securityfocus.com/bid/91581nvd
- www.securitytracker.com/id/1036283nvd
News mentions
0No linked articles in our index yet.