High severity7.5NVD Advisory· Published Oct 14, 2016· Updated May 6, 2026
CVE-2016-3389
CVE-2016-3389
Description
The Chakra JavaScript engine in Microsoft Edge allows 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-3386, CVE-2016-7190, and CVE-2016-7194.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
Microsoft.ChakraCoreNuGet | < 1.2.1 | 1.2.1 |
Patches
1f05c42e64c3bChanges addressing CVE_2016-3382, CVE-2016-3385, CVE-2016-3386, CVE-2016-3389,
24 files changed · +993 −322
jenkins/check_copyright.sh+1 −0 modified@@ -33,6 +33,7 @@ git diff --name-only `git merge-base origin/master HEAD` HEAD | grep -v -E '\.def$' | grep -v -E '\.inc$' | grep -v -E 'test/benchmarks/.*\.js$' | + grep -v -E 'lib/Backend/CRC.h' | xargs -I % sh -c "echo 'Check Copyright > Checking %'; python jenkins/check_copyright.py % > $ERRFILETEMP || cat $ERRFILETEMP >> $ERRFILE" if [ -e $ERRFILE ]; then # if error file exists then there were errors
lib/Backend/amd64/EncoderMD.cpp+72 −9 modified@@ -506,8 +506,8 @@ EncoderMD::EmitImmed(IR::Opnd * opnd, int opSize, int sbit, bool allow64Immediat break; case IR::OpndKindLabel: - value = (size_t)opnd->AsLabelOpnd()->GetLabel(); - AppendRelocEntry(RelocTypeLabelUse, (void*) m_pc, nullptr); + value = 0; + AppendRelocEntry(RelocTypeLabelUse, (void*) m_pc, opnd->AsLabelOpnd()->GetLabel()); break; default: @@ -1473,7 +1473,7 @@ EncoderMD::FixRelocListEntry(uint32 index, int totalBytesSaved, BYTE *buffStart, uint32 count = field & 0xf; AssertMsg(offset < (size_t)(buffEnd - buffStart), "Inlinee entry offset out of range"); - *((size_t*) relocRecord.m_origPtr) = ((offset - totalBytesSaved) << 4) | count; + relocRecord.SetInlineOffset(((offset - totalBytesSaved) << 4) | count); } // adjust the ptr to the buffer itself relocRecord.m_ptr = (BYTE*) relocRecord.m_ptr - totalBytesSaved; @@ -1531,7 +1531,7 @@ EncoderMD::FixMaps(uint32 brOffset, uint32 bytesSaved, uint32 *inlineeFrameRecor ///---------------------------------------------------------------------------- void -EncoderMD::ApplyRelocs(size_t codeBufferAddress_) +EncoderMD::ApplyRelocs(size_t codeBufferAddress_, size_t codeSize, uint * bufferCRC, BOOL isBrShorteningSucceeded, bool isFinalBufferValidation) { for (int32 i = 0; i < m_relocList->Count(); i++) @@ -1561,22 +1561,52 @@ EncoderMD::ApplyRelocs(size_t codeBufferAddress_) // short branch pcrel = (uint32)(labelInstr->GetPC() - ((BYTE*)reloc->m_ptr + 1)); AssertMsg((int32)pcrel >= -128 && (int32)pcrel <= 127, "Offset doesn't fit in imm8."); - *(BYTE*)relocAddress = (BYTE)pcrel; + if (!isFinalBufferValidation) + { + Assert(*(BYTE*)relocAddress == 0); + *(BYTE*)relocAddress = (BYTE)pcrel; + } + else + { + Encoder::EnsureRelocEntryIntegrity(codeBufferAddress_, codeSize, (size_t)m_encoder->m_encodeBuffer, (size_t)relocAddress, sizeof(BYTE), (ptrdiff_t)labelInstr->GetPC() - ((ptrdiff_t)reloc->m_ptr + 1)); + } } else { pcrel = (uint32)(labelInstr->GetPC() - ((BYTE*)reloc->m_ptr + 4)); - *(uint32 *)relocAddress = pcrel; + if (!isFinalBufferValidation) + { + Assert(*(uint32*)relocAddress == 0); + *(uint32 *)relocAddress = pcrel; + } + else + { + Encoder::EnsureRelocEntryIntegrity(codeBufferAddress_, codeSize, (size_t)m_encoder->m_encodeBuffer, (size_t)relocAddress, sizeof(uint32), (ptrdiff_t)labelInstr->GetPC() - ((ptrdiff_t)reloc->m_ptr + 4)); + } } - break; + *bufferCRC = Encoder::CalculateCRC(*bufferCRC, pcrel); + + break; } case RelocTypeLabelUse: { - IR::LabelInstr *labelInstr = *(IR::LabelInstr**)relocAddress; + IR::LabelInstr *labelInstr = reloc->getBrTargetLabel(); AssertMsg(labelInstr->GetPC() != nullptr, "Branch to unemitted label?"); - *(size_t *)relocAddress = (size_t)(labelInstr->GetPC() - m_encoder->m_encodeBuffer + codeBufferAddress_); + + size_t offset = (size_t)(labelInstr->GetPC() - m_encoder->m_encodeBuffer); + size_t targetAddress = (size_t)(offset + codeBufferAddress_); + if (!isFinalBufferValidation) + { + Assert(*(size_t *)relocAddress == 0); + *(size_t *)relocAddress = targetAddress; + } + else + { + Encoder::EnsureRelocEntryIntegrity(codeBufferAddress_, codeSize, (size_t)m_encoder->m_encodeBuffer, (size_t)relocAddress, sizeof(size_t), targetAddress, false); + } + *bufferCRC = Encoder::CalculateCRC(*bufferCRC, offset); break; } case RelocTypeLabel: @@ -1589,6 +1619,39 @@ EncoderMD::ApplyRelocs(size_t codeBufferAddress_) } } +uint +EncoderMD::GetRelocDataSize(EncodeRelocAndLabels *reloc) +{ + switch (reloc->m_type) + { + case RelocTypeBranch: + { + if (reloc->isShortBr()) + { + return sizeof(BYTE); + } + else + { + return sizeof(uint); + } + } + case RelocTypeLabelUse: + { + return sizeof(size_t); + } + default: + { + return 0; + } + } +} + +BYTE * +EncoderMD::GetRelocBufferAddress(EncodeRelocAndLabels * reloc) +{ + return (BYTE*)reloc->m_ptr; +} + #ifdef DBG ///---------------------------------------------------------------------------- ///
lib/Backend/amd64/EncoderMD.h+24 −14 modified@@ -33,7 +33,7 @@ class EncodeRelocAndLabels { IR::LabelInstr* m_labelInstr; // ptr to Br Label BYTE m_nopCount; - uint64 m_origInlineeOffset; + uint64 m_InlineeOffset; }; bool m_isShortBr; @@ -42,6 +42,9 @@ class EncodeRelocAndLabels { m_type = type; m_ptr = ptr; + m_InlineeOffset = 0; + m_isShortBr = false; + if (type == RelocTypeLabel) { // preserve original PC for labels @@ -51,16 +54,17 @@ class EncodeRelocAndLabels else { m_origPtr = ptr; - // in case we have to revert, we need to store original offset in code buffer - if (type == RelocTypeInlineeEntryOffset) + + if (type == RelocTypeBranch) { - m_origInlineeOffset = *((uint64*)m_origPtr); + Assert(labelInstr); + m_labelInstr = labelInstr; + m_isShortBr = false; } - else if (type == RelocTypeBranch) + else if (type == RelocTypeLabelUse) { Assert(labelInstr); m_labelInstr = labelInstr; - m_isShortBr = false; } } } @@ -77,12 +81,6 @@ class EncodeRelocAndLabels return; } - // re-write original inlinee offset to code buffer - if (m_type == RelocTypeInlineeEntryOffset) - { - *(uint64*) m_origPtr = m_origInlineeOffset; - } - if (m_type == RelocTypeBranch) { m_isShortBr = false; @@ -99,7 +97,7 @@ class EncodeRelocAndLabels IR::LabelInstr * getBrTargetLabel() const { - Assert(m_type == RelocTypeBranch && m_labelInstr); + Assert((m_type == RelocTypeBranch || m_type == RelocTypeLabelUse) && m_labelInstr); return m_labelInstr; } IR::LabelInstr * getLabel() const @@ -153,6 +151,16 @@ class EncodeRelocAndLabels getBrTargetLabel()->GetPC() - ((BYTE*)m_ptr + 1) >= -128 && getBrTargetLabel()->GetPC() - ((BYTE*)m_ptr + 1) <= 127; } + + uint64 GetInlineOffset() + { + return m_InlineeOffset; + } + + void SetInlineOffset(uint64 offset) + { + m_InlineeOffset = offset; + } }; @@ -173,7 +181,9 @@ class EncoderMD EncoderMD(Func * func) : m_func(func) {} ptrdiff_t Encode(IR::Instr * instr, BYTE *pc, BYTE* beginCodeAddress = nullptr); void Init(Encoder *encoder); - void ApplyRelocs(size_t codeBufferAddress); + void ApplyRelocs(size_t codeBufferAddress, size_t codeSize, uint* bufferCRC, BOOL isBrShorteningSucceeded, bool isFinalBufferValidation = false); + uint GetRelocDataSize(EncodeRelocAndLabels *reloc); + BYTE * GetRelocBufferAddress(EncodeRelocAndLabels * reloc); void EncodeInlineeCallInfo(IR::Instr *instr, uint32 offset); static bool TryConstFold(IR::Instr *instr, IR::RegOpnd *regOpnd); static bool TryFold(IR::Instr *instr, IR::RegOpnd *regOpnd);
lib/Backend/arm64/EncoderMD.h+1 −1 modified@@ -53,7 +53,7 @@ class EncoderMD EncoderMD(Func * func) { } ptrdiff_t Encode(IR::Instr * instr, BYTE *pc, BYTE* beginCodeAddress = nullptr) { __debugbreak(); return 0; } void Init(Encoder *encoder) { __debugbreak(); } - void ApplyRelocs(size_t codeBufferAddress) { __debugbreak(); } + void ApplyRelocs(size_t codeBufferAddress, size_t codeSize, uint* bufferCRC, BOOL isBrShorteningSucceeded, bool isFinalBufferValidation = false) { __debugbreak(); } static bool TryConstFold(IR::Instr *instr, IR::RegOpnd *regOpnd) { __debugbreak(); return 0; } static bool TryFold(IR::Instr *instr, IR::RegOpnd *regOpnd) { __debugbreak(); return 0; } const BYTE GetRegEncode(IR::RegOpnd *regOpnd) { __debugbreak(); return 0; }
lib/Backend/arm/EncoderMD.cpp+1 −1 modified@@ -2263,7 +2263,7 @@ EncoderMD::BaseAndOffsetFromSym(IR::SymOpnd *symOpnd, RegNum *pBaseReg, int32 *p /// before we copy the contents of the temporary buffer to the target buffer. ///---------------------------------------------------------------------------- void -EncoderMD::ApplyRelocs(uint32 codeBufferAddress) +EncoderMD::ApplyRelocs(uint32 codeBufferAddress, size_t codeSize, uint* bufferCRC, BOOL isBrShorteningSucceeded, bool isFinalBufferValidation) { for (EncodeReloc *reloc = m_relocList; reloc; reloc = reloc->m_next) {
lib/Backend/arm/EncoderMD.h+1 −1 modified@@ -55,7 +55,7 @@ class EncoderMD EncoderMD(Func * func) : m_func(func), consecutiveThumbInstrCount(0) { } ptrdiff_t Encode(IR::Instr * instr, BYTE *pc, BYTE* beginCodeAddress = nullptr); void Init(Encoder *encoder); - void ApplyRelocs(uint32 codeBufferAddress); + void ApplyRelocs(uint32 codeBufferAddress, size_t codeSize, uint* bufferCRC, BOOL isBrShorteningSucceeded, bool isFinalBufferValidation = false); static bool TryConstFold(IR::Instr *instr, IR::RegOpnd *regOpnd); static bool TryFold(IR::Instr *instr, IR::RegOpnd *regOpnd); const BYTE GetRegEncode(IR::RegOpnd *regOpnd);
lib/Backend/Chakra.Backend.vcxproj+6 −1 modified@@ -409,10 +409,15 @@ <None Include="Opnd.inl" /> <None Include="Sym.inl" /> </ItemGroup> + <ItemGroup> + <ClInclude Include="CRC.h"> + <FileType>CppCode</FileType> + </ClInclude> + </ItemGroup> <Import Project="$(BuildConfigPropsPath)Chakra.Build.targets" Condition="exists('$(BuildConfigPropsPath)Chakra.Build.targets')" /> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> <ImportGroup Label="ExtensionTargets"> <Import Project="$(VCTargetsPath)\BuildCustomizations\masm.targets" /> <Import Project="$(BuildConfig_ARMASM_Path)armasm.targets" /> </ImportGroup> -</Project> +</Project> \ No newline at end of file
lib/Backend/Chakra.Backend.vcxproj.filters+1 −0 modified@@ -299,6 +299,7 @@ </ClInclude> <ClInclude Include="IRBaseTypeList.h" /> <ClInclude Include="SwitchIRBuilder.h" /> + <ClInclude Include="CRC.h" /> </ItemGroup> <ItemGroup> <MASM Include="$(MSBuildThisFileDirectory)amd64\LinearScanMdA.asm">
lib/Backend/CRC.h+51 −0 added@@ -0,0 +1,51 @@ +/* +* CRC32 code derived from work by Gary S. Brown. +*/ + +/* +* Pre-populated Table used for calculating CRC32. +*/ +static const unsigned int crc_32_tab[] = +{ + 0x00000000L, 0x77073096L, 0xEE0E612CL, 0x990951BAL, 0x076DC419L, 0x706AF48FL, 0xE963A535L, 0x9E6495A3L, + 0x0EDB8832L, 0x79DCB8A4L, 0xE0D5E91EL, 0x97D2D988L, 0x09B64C2BL, 0x7EB17CBDL, 0xE7B82D07L, 0x90BF1D91L, + 0x1DB71064L, 0x6AB020F2L, 0xF3B97148L, 0x84BE41DEL, 0x1ADAD47DL, 0x6DDDE4EBL, 0xF4D4B551L, 0x83D385C7L, + 0x136C9856L, 0x646BA8C0L, 0xFD62F97AL, 0x8A65C9ECL, 0x14015C4FL, 0x63066CD9L, 0xFA0F3D63L, 0x8D080DF5L, + 0x3B6E20C8L, 0x4C69105EL, 0xD56041E4L, 0xA2677172L, 0x3C03E4D1L, 0x4B04D447L, 0xD20D85FDL, 0xA50AB56BL, + 0x35B5A8FAL, 0x42B2986CL, 0xDBBBC9D6L, 0xACBCF940L, 0x32D86CE3L, 0x45DF5C75L, 0xDCD60DCFL, 0xABD13D59L, + 0x26D930ACL, 0x51DE003AL, 0xC8D75180L, 0xBFD06116L, 0x21B4F4B5L, 0x56B3C423L, 0xCFBA9599L, 0xB8BDA50FL, + 0x2802B89EL, 0x5F058808L, 0xC60CD9B2L, 0xB10BE924L, 0x2F6F7C87L, 0x58684C11L, 0xC1611DABL, 0xB6662D3DL, + 0x76DC4190L, 0x01DB7106L, 0x98D220BCL, 0xEFD5102AL, 0x71B18589L, 0x06B6B51FL, 0x9FBFE4A5L, 0xE8B8D433L, + 0x7807C9A2L, 0x0F00F934L, 0x9609A88EL, 0xE10E9818L, 0x7F6A0DBBL, 0x086D3D2DL, 0x91646C97L, 0xE6635C01L, + 0x6B6B51F4L, 0x1C6C6162L, 0x856530D8L, 0xF262004EL, 0x6C0695EDL, 0x1B01A57BL, 0x8208F4C1L, 0xF50FC457L, + 0x65B0D9C6L, 0x12B7E950L, 0x8BBEB8EAL, 0xFCB9887CL, 0x62DD1DDFL, 0x15DA2D49L, 0x8CD37CF3L, 0xFBD44C65L, + 0x4DB26158L, 0x3AB551CEL, 0xA3BC0074L, 0xD4BB30E2L, 0x4ADFA541L, 0x3DD895D7L, 0xA4D1C46DL, 0xD3D6F4FBL, + 0x4369E96AL, 0x346ED9FCL, 0xAD678846L, 0xDA60B8D0L, 0x44042D73L, 0x33031DE5L, 0xAA0A4C5FL, 0xDD0D7CC9L, + 0x5005713CL, 0x270241AAL, 0xBE0B1010L, 0xC90C2086L, 0x5768B525L, 0x206F85B3L, 0xB966D409L, 0xCE61E49FL, + 0x5EDEF90EL, 0x29D9C998L, 0xB0D09822L, 0xC7D7A8B4L, 0x59B33D17L, 0x2EB40D81L, 0xB7BD5C3BL, 0xC0BA6CADL, + 0xEDB88320L, 0x9ABFB3B6L, 0x03B6E20CL, 0x74B1D29AL, 0xEAD54739L, 0x9DD277AFL, 0x04DB2615L, 0x73DC1683L, + 0xE3630B12L, 0x94643B84L, 0x0D6D6A3EL, 0x7A6A5AA8L, 0xE40ECF0BL, 0x9309FF9DL, 0x0A00AE27L, 0x7D079EB1L, + 0xF00F9344L, 0x8708A3D2L, 0x1E01F268L, 0x6906C2FEL, 0xF762575DL, 0x806567CBL, 0x196C3671L, 0x6E6B06E7L, + 0xFED41B76L, 0x89D32BE0L, 0x10DA7A5AL, 0x67DD4ACCL, 0xF9B9DF6FL, 0x8EBEEFF9L, 0x17B7BE43L, 0x60B08ED5L, + 0xD6D6A3E8L, 0xA1D1937EL, 0x38D8C2C4L, 0x4FDFF252L, 0xD1BB67F1L, 0xA6BC5767L, 0x3FB506DDL, 0x48B2364BL, + 0xD80D2BDAL, 0xAF0A1B4CL, 0x36034AF6L, 0x41047A60L, 0xDF60EFC3L, 0xA867DF55L, 0x316E8EEFL, 0x4669BE79L, + 0xCB61B38CL, 0xBC66831AL, 0x256FD2A0L, 0x5268E236L, 0xCC0C7795L, 0xBB0B4703L, 0x220216B9L, 0x5505262FL, + 0xC5BA3BBEL, 0xB2BD0B28L, 0x2BB45A92L, 0x5CB36A04L, 0xC2D7FFA7L, 0xB5D0CF31L, 0x2CD99E8BL, 0x5BDEAE1DL, + 0x9B64C2B0L, 0xEC63F226L, 0x756AA39CL, 0x026D930AL, 0x9C0906A9L, 0xEB0E363FL, 0x72076785L, 0x05005713L, + 0x95BF4A82L, 0xE2B87A14L, 0x7BB12BAEL, 0x0CB61B38L, 0x92D28E9BL, 0xE5D5BE0DL, 0x7CDCEFB7L, 0x0BDBDF21L, + 0x86D3D2D4L, 0xF1D4E242L, 0x68DDB3F8L, 0x1FDA836EL, 0x81BE16CDL, 0xF6B9265BL, 0x6FB077E1L, 0x18B74777L, + 0x88085AE6L, 0xFF0F6A70L, 0x66063BCAL, 0x11010B5CL, 0x8F659EFFL, 0xF862AE69L, 0x616BFFD3L, 0x166CCF45L, + 0xA00AE278L, 0xD70DD2EEL, 0x4E048354L, 0x3903B3C2L, 0xA7672661L, 0xD06016F7L, 0x4969474DL, 0x3E6E77DBL, + 0xAED16A4AL, 0xD9D65ADCL, 0x40DF0B66L, 0x37D83BF0L, 0xA9BCAE53L, 0xDEBB9EC5L, 0x47B2CF7FL, 0x30B5FFE9L, + 0xBDBDF21CL, 0xCABAC28AL, 0x53B39330L, 0x24B4A3A6L, 0xBAD03605L, 0xCDD70693L, 0x54DE5729L, 0x23D967BFL, + 0xB3667A2EL, 0xC4614AB8L, 0x5D681B02L, 0x2A6F2B94L, 0xB40BBE37L, 0xC30C8EA1L, 0x5A05DF1BL, 0x2D02EF8DL +}; + +static unsigned int CalculateCRC32(unsigned int bufferCRC, size_t data) +{ + /* update running CRC calculation with contents of a buffer */ + + bufferCRC = bufferCRC ^ 0xffffffffL; + bufferCRC = crc_32_tab[(bufferCRC ^ data) & 0xFF] ^ (bufferCRC >> 8); + return (bufferCRC ^ 0xffffffffL); +} \ No newline at end of file
lib/Backend/Encoder.cpp+231 −10 modified@@ -3,6 +3,7 @@ // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. //------------------------------------------------------------------------------------------------------- #include "Backend.h" +#include "CRC.h" ///---------------------------------------------------------------------------- /// @@ -71,6 +72,17 @@ Encoder::Encode() #endif bool isCallInstr = false; + // CRC Check to ensure the integrity of the encoded bytes. + uint initialCRCSeed = 0; + errno_t err = rand_s(&initialCRCSeed); + + if (err != 0) + { + Fatal(); + } + + uint bufferCRC = initialCRCSeed; + FOREACH_INSTR_IN_FUNC(instr, m_func) { Assert(Lowerer::ValidOpcodeAfterLower(instr, m_func)); @@ -140,7 +152,8 @@ Encoder::Encode() #if defined(_M_ARM32_OR_ARM64) encoderMD->AddLabelReloc((byte*) offset); #else - encoderMD->AppendRelocEntry(RelocTypeLabelUse, (void*) (offset)); + encoderMD->AppendRelocEntry(RelocTypeLabelUse, (void*) (offset), *(IR::LabelInstr**)(offset)); + *((size_t*)offset) = 0; #endif }); } @@ -191,6 +204,9 @@ Encoder::Encode() } count = m_encoderMD.Encode(instr, m_pc, m_encodeBuffer); +#if defined(_M_IX86) || defined(_M_X64) + bufferCRC = CalculateCRC(bufferCRC, count, m_pc); +#endif #if DBG_DUMP if (PHASE_TRACE(Js::EncoderPhase, this->m_func)) @@ -231,15 +247,21 @@ Encoder::Encode() Fatal(); } } NEXT_INSTR_IN_FUNC; - + ptrdiff_t codeSize = m_pc - m_encodeBuffer + totalJmpTableSizeInBytes; -#if defined(_M_IX86) || defined(_M_X64) BOOL isSuccessBrShortAndLoopAlign = false; + +#if defined(_M_IX86) || defined(_M_X64) // Shorten branches. ON by default if (!PHASE_OFF(Js::BrShortenPhase, m_func)) { - isSuccessBrShortAndLoopAlign = ShortenBranchesAndLabelAlign(&m_encodeBuffer, &codeSize); + uint brShortenedbufferCRC = initialCRCSeed; + isSuccessBrShortAndLoopAlign = ShortenBranchesAndLabelAlign(&m_encodeBuffer, &codeSize, &brShortenedbufferCRC, bufferCRC, totalJmpTableSizeInBytes); + if (isSuccessBrShortAndLoopAlign) + { + bufferCRC = brShortenedbufferCRC; + } } #endif #if DBG_DUMP | defined(VTUNE_PROFILING) @@ -291,10 +313,14 @@ Encoder::Encode() }); // Relocs - m_encoderMD.ApplyRelocs((size_t) workItem->GetCodeAddress()); + m_encoderMD.ApplyRelocs((size_t) workItem->GetCodeAddress(), codeSize, &bufferCRC, isSuccessBrShortAndLoopAlign); workItem->RecordNativeCode(m_func, m_encodeBuffer); +#if defined(_M_IX86) || defined(_M_X64) + ValidateCRCOnFinalBuffer((BYTE*)workItem->GetCodeAddress(), codeSize, totalJmpTableSizeInBytes, m_encodeBuffer, initialCRCSeed, bufferCRC, isSuccessBrShortAndLoopAlign); +#endif + m_func->GetScriptContext()->GetThreadContext()->SetValidCallTargetForCFG((PVOID) workItem->GetCodeAddress()); #ifdef _M_X64 @@ -575,7 +601,8 @@ void Encoder::TryCopyAndAddRelocRecordsForSwitchJumpTableEntries(BYTE *codeStart #if defined(_M_ARM32_OR_ARM64) encoderMD->AddLabelReloc((byte*) addressOfJmpTableEntry); #else - encoderMD->AppendRelocEntry(RelocTypeLabelUse, addressOfJmpTableEntry); + encoderMD->AppendRelocEntry(RelocTypeLabelUse, addressOfJmpTableEntry, *(IR::LabelInstr**)addressOfJmpTableEntry); + *((size_t*)addressOfJmpTableEntry) = 0; #endif } @@ -619,6 +646,156 @@ void Encoder::RecordInlineeFrame(Func* inlinee, uint32 currentOffset) } } +#if defined(_M_IX86) || defined(_M_X64) +/* +* ValidateCRCOnFinalBuffer +* - Validates the CRC that is last computed (could be either the one after BranchShortening or after encoding itself) +* - We calculate the CRC for jump table and dictionary after computing the code section. +* - Also, all reloc data are computed towards the end - after computing the code section - because we don't have to deal with the changes relocs while operating on the code section. +* - The version of CRC that we are validating with, doesn't have Relocs applied but the final buffer does - So we have to make adjustments while calculating the final buffer's CRC. +*/ +void Encoder::ValidateCRCOnFinalBuffer(_In_reads_bytes_(finalCodeSize) BYTE * finalCodeBufferStart, size_t finalCodeSize, size_t jumpTableSize, _In_reads_bytes_(finalCodeSize) BYTE * oldCodeBufferStart, uint initialCrcSeed, uint bufferCrcToValidate, BOOL isSuccessBrShortAndLoopAlign) +{ + RelocList * relocList = m_encoderMD.GetRelocList(); + + BYTE * currentStartAddress = finalCodeBufferStart; + BYTE * currentEndAddress = nullptr; + size_t crcSizeToCompute = 0; + + size_t finalCodeSizeWithoutJumpTable = finalCodeSize - jumpTableSize; + + uint finalBufferCRC = initialCrcSeed; + + BYTE * oldPtr = nullptr; + + if (relocList != nullptr) + { + for (int index = 0; index < relocList->Count(); index++) + { + EncodeRelocAndLabels * relocTuple = &relocList->Item(index); + + //We will deal with the jump table and dictionary entries along with other reloc records in ApplyRelocs() + if ((BYTE*)m_encoderMD.GetRelocBufferAddress(relocTuple) >= oldCodeBufferStart && (BYTE*)m_encoderMD.GetRelocBufferAddress(relocTuple) < (oldCodeBufferStart + finalCodeSizeWithoutJumpTable)) + { + BYTE* finalBufferRelocTuplePtr = (BYTE*)m_encoderMD.GetRelocBufferAddress(relocTuple) - oldCodeBufferStart + finalCodeBufferStart; + Assert(finalBufferRelocTuplePtr >= finalCodeBufferStart && finalBufferRelocTuplePtr < (finalCodeBufferStart + finalCodeSizeWithoutJumpTable)); + uint relocDataSize = m_encoderMD.GetRelocDataSize(relocTuple); + if (relocDataSize != 0) + { + AssertMsg(oldPtr == nullptr || oldPtr < finalBufferRelocTuplePtr, "Assumption here is that the reloc list is strictly increasing in terms of bufferAddress"); + oldPtr = finalBufferRelocTuplePtr; + + currentEndAddress = finalBufferRelocTuplePtr; + crcSizeToCompute = currentEndAddress - currentStartAddress; + + Assert(currentEndAddress >= currentStartAddress); + + finalBufferCRC = CalculateCRC(finalBufferCRC, crcSizeToCompute, currentStartAddress); + for (uint i = 0; i < relocDataSize; i++) + { + finalBufferCRC = CalculateCRC(finalBufferCRC, 0); + } + currentStartAddress = currentEndAddress + relocDataSize; + } + } + } + } + + currentEndAddress = finalCodeBufferStart + finalCodeSizeWithoutJumpTable; + crcSizeToCompute = currentEndAddress - currentStartAddress; + + Assert(currentEndAddress >= currentStartAddress); + + finalBufferCRC = CalculateCRC(finalBufferCRC, crcSizeToCompute, currentStartAddress); + + //Include all offsets from the reloc records to the CRC. + m_encoderMD.ApplyRelocs((size_t)finalCodeBufferStart, finalCodeSize, &finalBufferCRC, isSuccessBrShortAndLoopAlign, true); + + if (finalBufferCRC != bufferCrcToValidate) + { + Assert(false); + Fatal(); + } +} +#endif + +/* +* EnsureRelocEntryIntegrity +* - We compute the target address as the processor would compute it and check if the target is within the final buffer's bounds. +* - For relative addressing, Target = current m_pc + offset +* - For absolute addressing, Target = direct address +*/ +void Encoder::EnsureRelocEntryIntegrity(size_t newBufferStartAddress, size_t codeSize, size_t oldBufferAddress, size_t relocAddress, uint offsetBytes, ptrdiff_t opndData, bool isRelativeAddr) +{ + size_t targetBrAddress = 0; + size_t newBufferEndAddress = newBufferStartAddress + codeSize; + + //Handle Dictionary addresses here - The target address will be in the dictionary. + if (relocAddress < oldBufferAddress || relocAddress >= (oldBufferAddress + codeSize)) + { + targetBrAddress = (size_t)(*(size_t*)relocAddress); + } + else + { + size_t newBufferRelocAddr = relocAddress - oldBufferAddress + newBufferStartAddress; + + if (isRelativeAddr) + { + targetBrAddress = (size_t)newBufferRelocAddr + offsetBytes + opndData; + } + else // Absolute Address + { + targetBrAddress = (size_t)opndData; + } + } + + if (targetBrAddress < newBufferStartAddress || targetBrAddress >= newBufferEndAddress) + { + Assert(false); + Fatal(); + } +} + +uint Encoder::CalculateCRC(uint bufferCRC, size_t data) +{ +#if defined(_M_IX86) + if (AutoSystemInfo::Data.SSE4_2Available()) + { + return _mm_crc32_u32(bufferCRC, data); + } +#elif defined(_M_X64) + if (AutoSystemInfo::Data.SSE4_2Available()) + { + //CRC32 always returns a 32-bit result + return (uint)_mm_crc32_u64(bufferCRC, data); + } +#endif + + return CalculateCRC32(bufferCRC, data); +} + +uint Encoder::CalculateCRC(uint bufferCRC, size_t count, _In_reads_bytes_(count) void * buffer) +{ + for (uint index = 0; index < count; index++) + { + bufferCRC = CalculateCRC(bufferCRC, *((BYTE*)buffer + index)); + } + return bufferCRC; +} + +void Encoder::ValidateCRC(uint bufferCRC, uint initialCRCSeed, _In_reads_bytes_(count) void* buffer, size_t count) +{ + uint validationCRC = initialCRCSeed; + + validationCRC = CalculateCRC(validationCRC, count, buffer); + + if (validationCRC != bufferCRC) + { + //TODO: This throws internal error. Is this error type, Fine? + Fatal(); + } +} + #if defined(_M_IX86) || defined(_M_X64) ///---------------------------------------------------------------------------- /// @@ -628,7 +805,7 @@ void Encoder::RecordInlineeFrame(Func* inlinee, uint32 currentOffset) /// Also align LoopTop Label and TryCatchLabel ///---------------------------------------------------------------------------- BOOL -Encoder::ShortenBranchesAndLabelAlign(BYTE **codeStart, ptrdiff_t *codeSize) +Encoder::ShortenBranchesAndLabelAlign(BYTE **codeStart, ptrdiff_t *codeSize, uint * pShortenedBufferCRC, uint bufferCrcToValidate, size_t jumpTableSize) { #ifdef ENABLE_DEBUG_CONFIG_OPTIONS static uint32 globalTotalBytesSaved = 0, globalTotalBytesWithoutShortening = 0; @@ -791,6 +968,8 @@ Encoder::ShortenBranchesAndLabelAlign(BYTE **codeStart, ptrdiff_t *codeSize) // Next, we re-write the code to shorten the BRs and adjust relocList offsets to point to new buffer. // We also write NOPs for aligned loops. BYTE* tmpBuffer = AnewArray(m_tempAlloc, BYTE, newCodeSize); + + uint srcBufferCrc = *pShortenedBufferCRC; //This has the intial Random CRC seed to start with. // start copying to new buffer // this can possibly be done during fixing, but there is no evidence it is an overhead to justify the complexity. @@ -849,17 +1028,40 @@ Encoder::ShortenBranchesAndLabelAlign(BYTE **codeStart, ptrdiff_t *codeSize) AnalysisAssert(dst_size >= src_size); memcpy_s(dst_p, dst_size, from, src_size); + + srcBufferCrc = CalculateCRC(srcBufferCrc, (BYTE*)reloc.m_origPtr - from + 4, from); + *pShortenedBufferCRC = CalculateCRC(*pShortenedBufferCRC, src_size, dst_p); + dst_p += src_size; dst_size -= src_size; // fix the BR // write new opcode AnalysisAssert(dst_p < tmpBuffer + newCodeSize); *dst_p = (*opcodeByte == 0xe9) ? (BYTE)0xeb : (BYTE)(*opcodeByte - 0x10); + *(dst_p + 1) = 0; // imm8 + + *pShortenedBufferCRC = CalculateCRC(*pShortenedBufferCRC, 2, dst_p); dst_p += 2; // 1 byte for opcode + 1 byte for imm8 dst_size -= 2; from = (BYTE*)reloc.m_origPtr + 4; } + else if (reloc.m_type == RelocTypeInlineeEntryOffset) + { + to = (BYTE*)reloc.m_origPtr - 1; + CopyPartialBufferAndCalculateCRC(&dst_p, dst_size, from, to, pShortenedBufferCRC); + + *(size_t*)dst_p = reloc.GetInlineOffset(); + + *pShortenedBufferCRC = CalculateCRC(*pShortenedBufferCRC, sizeof(size_t), dst_p); + + dst_p += sizeof(size_t); + dst_size -= sizeof(size_t); + + srcBufferCrc = CalculateCRC(srcBufferCrc, (BYTE*)reloc.m_origPtr + sizeof(size_t) - from , from); + + from = (BYTE*)reloc.m_origPtr + sizeof(size_t); + } // insert NOPs for aligned labels else if ((!PHASE_OFF(Js::LoopAlignPhase, m_func) && reloc.isAlignedLabel()) && reloc.getLabelNopCount() > 0) { @@ -870,7 +1072,9 @@ Encoder::ShortenBranchesAndLabelAlign(BYTE **codeStart, ptrdiff_t *codeSize) AssertMsg((((uint32)(label->GetPC() - buffStart)) & 0xf) == 0, "Misaligned Label"); to = reloc.getLabelOrigPC() - 1; - CopyPartialBuffer(&dst_p, dst_size, from, to); + + CopyPartialBufferAndCalculateCRC(&dst_p, dst_size, from, to, pShortenedBufferCRC); + srcBufferCrc = CalculateCRC(srcBufferCrc, to - from + 1, from); #ifdef ENABLE_DEBUG_CONFIG_OPTIONS if (PHASE_TRACE(Js::LoopAlignPhase, this->m_func)) @@ -882,17 +1086,28 @@ Encoder::ShortenBranchesAndLabelAlign(BYTE **codeStart, ptrdiff_t *codeSize) Output::Flush(); } #endif + BYTE * tmpDst_p = dst_p; InsertNopsForLabelAlignment(nop_count, &dst_p); + *pShortenedBufferCRC = CalculateCRC(*pShortenedBufferCRC, nop_count, tmpDst_p); dst_size -= nop_count; from = to + 1; } } // copy last chunk - CopyPartialBuffer(&dst_p, dst_size, from, buffStart + *codeSize - 1); + //Exclude jumpTable content from CRC calculation. + //Though jumpTable is not part of the encoded bytes, codeSize has jumpTableSize included in it. + CopyPartialBufferAndCalculateCRC(&dst_p, dst_size, from, buffStart + *codeSize - 1, pShortenedBufferCRC, jumpTableSize); + srcBufferCrc = CalculateCRC(srcBufferCrc, buffStart + *codeSize - from - jumpTableSize, from); m_encoderMD.UpdateRelocListWithNewBuffer(relocList, tmpBuffer, buffStart, buffEnd); + if (srcBufferCrc != bufferCrcToValidate) + { + Assert(false); + Fatal(); + } + // switch buffers *codeStart = tmpBuffer; *codeSize = newCodeSize; @@ -905,13 +1120,19 @@ BYTE Encoder::FindNopCountFor16byteAlignment(size_t address) return (16 - (BYTE) (address & 0xf)) % 16; } -void Encoder::CopyPartialBuffer(BYTE ** ptrDstBuffer, size_t &dstSize, BYTE * srcStart, BYTE * srcEnd) +void Encoder::CopyPartialBufferAndCalculateCRC(BYTE ** ptrDstBuffer, size_t &dstSize, BYTE * srcStart, BYTE * srcEnd, uint* pBufferCRC, size_t jumpTableSize) { BYTE * destBuffer = *ptrDstBuffer; size_t srcSize = srcEnd - srcStart + 1; Assert(dstSize >= srcSize); memcpy_s(destBuffer, dstSize, srcStart, srcSize); + + Assert(srcSize >= jumpTableSize); + + //Exclude the jump table content (which is at the end of the buffer) for calculating CRC - at this point. + *pBufferCRC = CalculateCRC(*pBufferCRC, srcSize - jumpTableSize, destBuffer); + *ptrDstBuffer += srcSize; dstSize -= srcSize; }
lib/Backend/Encoder.h+10 −2 modified@@ -49,15 +49,23 @@ class Encoder #if defined(_M_IX86) || defined(_M_X64) InlineeFrameRecords *m_inlineeFrameRecords; - BOOL ShortenBranchesAndLabelAlign(BYTE **codeStart, ptrdiff_t *codeSize); + BOOL ShortenBranchesAndLabelAlign(BYTE **codeStart, ptrdiff_t *codeSize, uint * brShortenedBufferCRC, uint bufferCrcToValidate, size_t jumpTableSize); void revertRelocList(); template <bool restore> void CopyMaps(OffsetList **m_origInlineeFrameRecords, OffsetList **m_origInlineeFrameMap, OffsetList **m_origPragmaInstrToRecordOffset, OffsetList **m_origOffsetBuffer); #endif void InsertNopsForLabelAlignment(int nopCount, BYTE ** pDstBuffer); - void CopyPartialBuffer(BYTE ** ptrDstBuffer, size_t &dstSize, BYTE * srcStart, BYTE * srcEnd); + void CopyPartialBufferAndCalculateCRC(BYTE ** ptrDstBuffer, size_t &dstSize, BYTE * srcStart, BYTE * srcEnd, uint* pBufferCRC, size_t jumpTableSize = 0); BYTE FindNopCountFor16byteAlignment(size_t address); uint32 GetCurrentOffset() const; void TryCopyAndAddRelocRecordsForSwitchJumpTableEntries(BYTE *codeStart, size_t codeSize, JmpTableList * jumpTableListForSwitchStatement, size_t totalJmpTableSizeInBytes); + + void ValidateCRC(uint bufferCRC, uint initialCRCSeed, _In_reads_bytes_(count) void* buffer, size_t count); + static uint CalculateCRC(uint bufferCRC, size_t count, _In_reads_bytes_(count) void * buffer); + static uint CalculateCRC(uint bufferCRC, size_t data); + static void EnsureRelocEntryIntegrity(size_t newBufferStartAddress, size_t codeSize, size_t oldBufferAddress, size_t relocAddress, uint offsetBytes, ptrdiff_t opndData, bool isRelativeAddr = true); +#if defined(_M_IX86) || defined(_M_X64) + void ValidateCRCOnFinalBuffer(_In_reads_bytes_(finalCodeSize) BYTE * finalCodeBufferStart, size_t finalCodeSize, size_t jumpTableSize, _In_reads_bytes_(finalCodeSize) BYTE * oldCodeBufferStart, uint initialCrcSeed, uint bufferCrcToValidate, BOOL isSuccessBrShortAndLoopAlign); +#endif };
lib/Backend/GlobOpt.cpp+2 −1 modified@@ -926,9 +926,10 @@ GlobOpt::TryTailDup(IR::BranchInstr *tailBranch) { branchEntry->InsertBefore(instr->Copy()); } - branchEntry->ReplaceTarget(mergeLabel, tailBranch->GetTarget()); instr = branchEntry; + branchEntry->ReplaceTarget(mergeLabel, tailBranch->GetTarget()); + while(!instr->IsLabelInstr()) { instr = instr->m_prev;
lib/Backend/i386/EncoderMD.cpp+90 −28 modified@@ -481,7 +481,6 @@ void EncoderMD::EmitCondBranch(IR::BranchInstr * branchInstr) { IR::LabelInstr * labelInstr; - // TODO: Make this more table-driven by mapping opcodes to condcodes. // (Will become more useful when we're emitting short branches as well.) @@ -556,12 +555,10 @@ EncoderMD::EmitCondBranch(IR::BranchInstr * branchInstr) break; } - AppendRelocEntry(RelocTypeBranch, (void*) m_pc); - - // Save the target LabelInstr's address in the encoder buffer itself, using the 4-byte - // pcrel field of the branch instruction. This only works for long branches, obviously. labelInstr = branchInstr->GetTarget(); - this->EmitConst((uint32)labelInstr, MachInt); + AppendRelocEntry(RelocTypeBranch, (void*) m_pc, labelInstr); + + this->EmitConst(0, MachInt); } ///---------------------------------------------------------------------------- @@ -764,8 +761,8 @@ EncoderMD::Encode(IR::Instr *instr, BYTE *pc, BYTE* beginCodeAddress) this->EmitModRM(instr, opr1, this->GetOpcodeByte2(instr) >> 3); if (opr2->IsLabelOpnd()) { - AppendRelocEntry( RelocTypeLabelUse, (void*) m_pc); - this->EmitConst((uint32)opr2->AsLabelOpnd()->GetLabel(), 4); + AppendRelocEntry( RelocTypeLabelUse, (void*) m_pc, opr2->AsLabelOpnd()->GetLabel()); + this->EmitConst(0, 4); } else { @@ -874,8 +871,8 @@ EncoderMD::Encode(IR::Instr *instr, BYTE *pc, BYTE* beginCodeAddress) { continue; } - AppendRelocEntry(RelocTypeLabelUse, (void*) m_pc); - this->EmitConst((uint32)opr1->AsLabelOpnd()->GetLabel(), 4); + AppendRelocEntry(RelocTypeLabelUse, (void*) m_pc, opr1->AsLabelOpnd()->GetLabel()); + this->EmitConst(0, 4); } else { @@ -918,24 +915,21 @@ EncoderMD::Encode(IR::Instr *instr, BYTE *pc, BYTE* beginCodeAddress) // Unconditional branch AssertMsg(instr->IsBranchInstr(), "Invalid LABREL2 form"); - AppendRelocEntry(RelocTypeBranch, (void*)m_pc); - - // Save the target LabelInstr's address in the encoder buffer itself, using the 4-byte - // pcrel field of the branch instruction. This only works for long branches, obviously. - this->EmitConst((uint32)instr->AsBranchInstr()->GetTarget(), 4); + AppendRelocEntry(RelocTypeBranch, (void*)m_pc, instr->AsBranchInstr()->GetTarget()); + this->EmitConst(0, 4); } else if (opr1->IsIntConstOpnd()) { - AppendRelocEntry(RelocTypeCallPcrel, (void*)m_pc); - this->EmitConst(opr1->AsIntConstOpnd()->GetValue(), 4); + AppendRelocEntry(RelocTypeCallPcrel, (void*)m_pc, nullptr, (void*)opr1->AsIntConstOpnd()->GetValue()); + this->EmitConst(0, 4); AssertMsg( ( ((BYTE*)opr1->AsIntConstOpnd()->GetValue()) < m_encoder->m_encodeBuffer || ((BYTE *)opr1->AsIntConstOpnd()->GetValue()) >= m_encoder->m_encodeBuffer + m_encoder->m_encodeBufferSize), "Call Target within buffer."); } else if (opr1->IsHelperCallOpnd()) { - AppendRelocEntry(RelocTypeCallPcrel, (void*)m_pc); const void* fnAddress = IR::GetMethodAddress(opr1->AsHelperCallOpnd()); + AppendRelocEntry(RelocTypeCallPcrel, (void*)m_pc, nullptr, fnAddress); AssertMsg(sizeof(uint32) == sizeof(void*), "Sizes of void* assumed to be 32-bits"); - this->EmitConst((uint32)fnAddress, 4); + this->EmitConst(0, 4); AssertMsg( (((BYTE*)fnAddress) < m_encoder->m_encodeBuffer || ((BYTE *)fnAddress) >= m_encoder->m_encodeBuffer + m_encoder->m_encodeBufferSize), "Call Target within buffer."); } else @@ -1281,13 +1275,13 @@ EncoderMD::Encode(IR::Instr *instr, BYTE *pc, BYTE* beginCodeAddress) } int -EncoderMD::AppendRelocEntry(RelocType type, void *ptr) +EncoderMD::AppendRelocEntry(RelocType type, void *ptr, IR::LabelInstr* labelInstr, const void * fnAddress) { if (m_relocList == nullptr) m_relocList = Anew(m_encoder->m_tempAlloc, RelocList, m_encoder->m_tempAlloc); EncodeRelocAndLabels reloc; - reloc.init(type, ptr); + reloc.init(type, ptr, labelInstr, fnAddress); return m_relocList->Add(reloc); } @@ -1341,7 +1335,7 @@ EncoderMD::FixRelocListEntry(uint32 index, int32 totalBytesSaved, BYTE *buffStar uint32 count = field & 0xf; AssertMsg(offset < (uint32)(buffEnd - buffStart), "Inlinee entry offset out of range"); - *((uint32*) relocRecord.m_origPtr) = ((offset - totalBytesSaved) << 4) | count; + relocRecord.SetInlineOffset(((offset - totalBytesSaved) << 4) | count); } // adjust the ptr to the buffer itself relocRecord.m_ptr = (BYTE*) relocRecord.m_ptr - totalBytesSaved; @@ -1396,7 +1390,7 @@ EncoderMD::FixMaps(uint32 brOffset, int32 bytesSaved, uint32 *inlineeFrameRecord /// before we copy the contents of the temporary buffer to the target buffer. ///---------------------------------------------------------------------------- void -EncoderMD::ApplyRelocs(uint32 codeBufferAddress) +EncoderMD::ApplyRelocs(uint32 codeBufferAddress, size_t codeSize, uint * bufferCRC, BOOL isBrShorteningSucceeded, bool isFinalBufferValidation) { for (int32 i = 0; i < m_relocList->Count(); i++) { @@ -1409,7 +1403,13 @@ EncoderMD::ApplyRelocs(uint32 codeBufferAddress) case RelocTypeCallPcrel: { pcrel = (uint32)(codeBufferAddress + (BYTE*)reloc->m_ptr - m_encoder->m_encodeBuffer + 4); - *(uint32 *)relocAddress -= pcrel; + uint32 offset = (uint32)reloc->GetFnAddress() - pcrel; + if (!isFinalBufferValidation) + { + Assert(*(uint32 *)relocAddress == 0); + *(uint32 *)relocAddress = offset; + } + *bufferCRC = Encoder::CalculateCRC(*bufferCRC, offset); break; } case RelocTypeBranch: @@ -1421,20 +1421,48 @@ EncoderMD::ApplyRelocs(uint32 codeBufferAddress) // short branch pcrel = (uint32)(labelInstr->GetPC() - ((BYTE*)reloc->m_ptr + 1)); AssertMsg((int32)pcrel >= -128 && (int32)pcrel <= 127, "Offset doesn't fit in imm8."); - *(BYTE*)relocAddress = (BYTE)pcrel; + if (!isFinalBufferValidation) + { + Assert(*(BYTE*)relocAddress == 0); + *(BYTE*)relocAddress = (BYTE)pcrel; + } + else + { + Encoder::EnsureRelocEntryIntegrity(codeBufferAddress, codeSize, (size_t)m_encoder->m_encodeBuffer, (size_t)relocAddress, sizeof(BYTE), (ptrdiff_t)labelInstr->GetPC() - ((ptrdiff_t)reloc->m_ptr + 1)); + } } else { pcrel = (uint32)(labelInstr->GetPC() - ((BYTE*)reloc->m_ptr + 4)); - *(uint32 *)relocAddress = pcrel; + if (!isFinalBufferValidation) + { + Assert(*(uint32 *)relocAddress == 0); + *(uint32 *)relocAddress = pcrel; + } + else + { + Encoder::EnsureRelocEntryIntegrity(codeBufferAddress, codeSize, (size_t)m_encoder->m_encodeBuffer, (size_t)relocAddress, sizeof(uint32), (ptrdiff_t)labelInstr->GetPC() - ((ptrdiff_t)reloc->m_ptr + 4)); + } } + *bufferCRC = Encoder::CalculateCRC(*bufferCRC, pcrel); break; } case RelocTypeLabelUse: { - IR::LabelInstr * labelInstr = *(IR::LabelInstr**)relocAddress; + IR::LabelInstr * labelInstr = reloc->GetLabelInstrForRelocTypeLabelUse(); AssertMsg(labelInstr->GetPC() != nullptr, "Branch to unemitted label?"); - *(uint32 *)relocAddress = (uint32)(labelInstr->GetPC() - m_encoder->m_encodeBuffer + codeBufferAddress); + uint32 offset = uint32(labelInstr->GetPC() - m_encoder->m_encodeBuffer); + size_t targetAddress = (uint32)(offset + codeBufferAddress); + if (!isFinalBufferValidation) + { + Assert(*(uint32 *)relocAddress == 0); + *(uint32 *)relocAddress = targetAddress; + } + else + { + Encoder::EnsureRelocEntryIntegrity(codeBufferAddress, codeSize, (size_t)m_encoder->m_encodeBuffer, (size_t)relocAddress, sizeof(size_t), targetAddress, false); + } + *bufferCRC = Encoder::CalculateCRC(*bufferCRC, offset); break; } case RelocTypeLabel: @@ -1447,6 +1475,40 @@ EncoderMD::ApplyRelocs(uint32 codeBufferAddress) } } +uint +EncoderMD::GetRelocDataSize(EncodeRelocAndLabels *reloc) +{ + switch (reloc->m_type) + { + case RelocTypeCallPcrel: + case RelocTypeLabelUse: + { + return sizeof(uint32); + } + case RelocTypeBranch: + { + if (reloc->isShortBr()) + { + return sizeof(BYTE); + } + else + { + return sizeof(uint32); + } + } + default: + { + return 0; + } + } +} + +BYTE * +EncoderMD::GetRelocBufferAddress(EncodeRelocAndLabels * reloc) +{ + return (BYTE*)reloc->m_ptr; +} + ///---------------------------------------------------------------------------- /// /// EncodeRelocAndLabels::VerifyRelocList
lib/Backend/i386/EncoderMD.h+48 −19 modified@@ -31,15 +31,23 @@ class EncodeRelocAndLabels union { IR::LabelInstr * m_shortBrLabel; // NULL if not a short branch - uint32 m_origInlineeOffset; + uint32 m_InlineeOffset; BYTE m_nopCount; // for AlignedLabel, how many nops do we need to be 16-byte aligned }; + union + { + IR::LabelInstr * m_labelInstr; + const void * m_fnAddress; + }; + public: - void init(RelocType type, void* ptr) + void init(RelocType type, void* ptr, IR::LabelInstr* labelInstr, const void * fnAddress) { m_type = type; m_ptr = ptr; + m_InlineeOffset = 0; + m_labelInstr = nullptr; if (type == RelocTypeLabel) { @@ -50,16 +58,21 @@ class EncodeRelocAndLabels else { m_origPtr = ptr; - // in case we have to revert, we need to store original offset in code buffer - if (type == RelocTypeInlineeEntryOffset) + if (type == RelocTypeBranch) { - m_origInlineeOffset = *((uint32*)m_origPtr); + m_shortBrLabel = NULL; + m_labelInstr = labelInstr; } - else if (type == RelocTypeBranch) + else if (type == RelocTypeLabelUse) { - m_shortBrLabel = NULL; + Assert(labelInstr); + m_labelInstr = labelInstr; + } + else if (type == RelocTypeCallPcrel) + { + Assert(fnAddress); + m_fnAddress = fnAddress; } - } } @@ -76,12 +89,6 @@ class EncodeRelocAndLabels return; } - // re-write original inlinee offset to code buffer - if (m_type == RelocTypeInlineeEntryOffset) - { - *((uint32*)m_origPtr) = m_origInlineeOffset; - } - if (m_type == RelocTypeBranch) { m_shortBrLabel = NULL; @@ -104,16 +111,27 @@ class EncodeRelocAndLabels IR::LabelInstr * getBrTargetLabel() const { Assert(m_type == RelocTypeBranch); - return m_shortBrLabel == NULL ? *(IR::LabelInstr**)m_origPtr : m_shortBrLabel; + return m_shortBrLabel == NULL ? m_labelInstr : m_shortBrLabel; } - IR::LabelInstr * getLabel() const { Assert(isLabel()); return (IR::LabelInstr*) m_ptr; } + IR::LabelInstr * GetLabelInstrForRelocTypeLabelUse() + { + Assert(m_type == RelocTypeLabelUse && m_labelInstr); + return m_labelInstr; + } + + const void * GetFnAddress() + { + Assert(m_type == RelocTypeCallPcrel && m_fnAddress); + return m_fnAddress; + } + // get label original PC without shortening/alignment BYTE * getLabelOrigPC() const { @@ -160,6 +178,16 @@ class EncodeRelocAndLabels m_shortBrLabel->GetPC() - ((BYTE*)m_ptr + 1) >= -128 && m_shortBrLabel->GetPC() - ((BYTE*)m_ptr + 1) <= 127; } + + uint32 GetInlineOffset() + { + return m_InlineeOffset; + } + + void SetInlineOffset(uint32 offset) + { + m_InlineeOffset = offset; + } }; @@ -181,23 +209,24 @@ class EncoderMD ptrdiff_t Encode(IR::Instr * instr, BYTE *pc, BYTE* beginCodeAddress = nullptr); void Init(Encoder *encoder); - void ApplyRelocs(uint32 codeBufferAddress); - + void ApplyRelocs(uint32 codeBufferAddress, size_t codeSize, uint * bufferCRC, BOOL isBrShorteningSucceeded, bool isFinalBufferValidation = false); + uint GetRelocDataSize(EncodeRelocAndLabels *reloc); void EncodeInlineeCallInfo(IR::Instr *instr, uint32 offset); static bool TryConstFold(IR::Instr *instr, IR::RegOpnd *regOpnd); static bool TryFold(IR::Instr *instr, IR::RegOpnd *regOpnd); static bool SetsConditionCode(IR::Instr *instr); static bool UsesConditionCode(IR::Instr *instr); static bool IsOPEQ(IR::Instr *instr); RelocList* GetRelocList() const { return m_relocList; } - int AppendRelocEntry(RelocType type, void *ptr); + int AppendRelocEntry(RelocType type, void *ptr, IR::LabelInstr * labelInstr = nullptr, const void * fnAddress = nullptr); int FixRelocListEntry(uint32 index, int32 totalBytesSaved, BYTE *buffStart, BYTE* buffEnd); void FixMaps(uint32 brOffset, int32 bytesSaved, uint32 *inlineeFrameRecordsIndex, uint32 *inlineeFrameMapIndex, uint32 *pragmaInstToRecordOffsetIndex, uint32 *offsetBuffIndex); void UpdateRelocListWithNewBuffer(RelocList * relocList, BYTE * newBuffer, BYTE * oldBufferStart, BYTE * oldBufferEnd); #ifdef DBG void VerifyRelocList(BYTE *buffStart, BYTE *buffEnd); #endif void AddLabelReloc(BYTE* relocAddress); + BYTE * GetRelocBufferAddress(EncodeRelocAndLabels * reloc); private: const BYTE GetOpcodeByte2(IR::Instr *instr);
lib/Backend/LowerMDShared.cpp+1 −1 modified@@ -8871,7 +8871,7 @@ void LowererMD::GenerateFastInlineBuiltInCall(IR::Instr* instr, IR::JnHelperMeth Assert(src->IsFloat32()); zero = IR::MemRefOpnd::New((float*)&Js::JavascriptNumber::k_Float32Zero, TyFloat32, this->m_func, IR::AddrOpndKindDynamicFloatRef); } - + IR::AutoReuseOpnd autoReuseZero(zero, this->m_func); IR::LabelInstr * skipRoundSd = IR::LabelInstr::New(Js::OpCode::Label, this->m_func); if(instr->m_opcode == Js::OpCode::InlineMathRound)
lib/Common/Core/SysInfo.cpp+7 −0 modified@@ -197,6 +197,13 @@ AutoSystemInfo::SSE4_1Available() const return VirtualSseAvailable(4) && (CPUInfo[2] & (0x1 << 19)); } +BOOL +AutoSystemInfo::SSE4_2Available() const +{ + Assert(initialized); + return VirtualSseAvailable(4) && (CPUInfo[2] & (0x1 << 20)); +} + BOOL AutoSystemInfo::PopCntAvailable() const {
lib/Common/Core/SysInfo.h+1 −0 modified@@ -28,6 +28,7 @@ class AutoSystemInfo : public SYSTEM_INFO #if defined(_M_IX86) || defined(_M_X64) BOOL SSE3Available() const; BOOL SSE4_1Available() const; + BOOL SSE4_2Available() const; BOOL PopCntAvailable() const; BOOL LZCntAvailable() const; bool IsAtomPlatform() const;
lib/Runtime/Language/JavascriptOperators.cpp+15 −2 modified@@ -1362,10 +1362,23 @@ namespace Js Var JavascriptOperators::OP_LdCustomSpreadIteratorList(Var aRight, ScriptContext* scriptContext) { +#if ENABLE_COPYONACCESS_ARRAY + // We know we're going to read from this array. Do the conversion before we try to perform checks on the head segment. + JavascriptLibrary::CheckAndConvertCopyOnAccessNativeIntArray(aRight); +#endif RecyclableObject* function = GetIteratorFunction(aRight, scriptContext); JavascriptMethod method = function->GetEntryPoint(); - if (((JavascriptArray::Is(aRight) && method == JavascriptArray::EntryInfo::Values.GetOriginalEntryPoint()) - || (TypedArrayBase::Is(aRight) && method == TypedArrayBase::EntryInfo::Values.GetOriginalEntryPoint())) + if (((JavascriptArray::Is(aRight) && + ( + method == JavascriptArray::EntryInfo::Values.GetOriginalEntryPoint() + // Verify that the head segment of the array covers all elements with no gaps. + // Accessing an element on the prototype could have side-effects that would invalidate the optimization. + && JavascriptArray::FromVar(aRight)->GetHead()->next == nullptr + && JavascriptArray::FromVar(aRight)->GetHead()->left == 0 + && JavascriptArray::FromVar(aRight)->GetHead()->length == JavascriptArray::FromVar(aRight)->GetLength() + && JavascriptArray::FromVar(aRight)->HasNoMissingValues() + )) || + (TypedArrayBase::Is(aRight) && method == TypedArrayBase::EntryInfo::Values.GetOriginalEntryPoint())) // We can't optimize away the iterator if the array iterator prototype is user defined. && !JavascriptLibrary::ArrayIteratorPrototypeHasUserDefinedNext(scriptContext)) {
lib/Runtime/Library/JavascriptArray.cpp+10 −6 modified@@ -3857,7 +3857,6 @@ namespace Js Throw::InternalError(); } - template <bool includesAlgorithm, typename T, typename P> Var JavascriptArray::TemplatedIndexOfHelper(T * pArr, Var search, P fromIndex, P toIndex, ScriptContext * scriptContext) { @@ -3871,7 +3870,7 @@ namespace Js //Consider: enumerating instead of walking all indices for (P i = fromIndex; i < toIndex; i++) { - if (!TemplatedGetItem(pArr, i, &element, scriptContext)) + if (!TryTemplatedGetItem(pArr, i, &element, scriptContext)) { if (doUndefinedSearch) { @@ -4236,7 +4235,8 @@ namespace Js { cs->Append(separator); } - if (TemplatedGetItem(arr, i, &item, scriptContext)) + + if (TryTemplatedGetItem(arr, i, &item, scriptContext)) { cs->Append(JavascriptArray::JoinToString(item, scriptContext)); } @@ -4254,19 +4254,23 @@ namespace Js JavascriptString *res = nullptr; Var item; + if (TemplatedGetItem(arr, 0u, &item, scriptContext)) { res = JavascriptArray::JoinToString(item, scriptContext); } - if (TemplatedGetItem(arr, 1u, &item, scriptContext)) + + if (TryTemplatedGetItem(arr, 1u, &item, scriptContext)) { JavascriptString *const itemString = JavascriptArray::JoinToString(item, scriptContext); return res ? ConcatString::New(res, itemString) : itemString; } + if(res) { return res; } + goto Case0; } @@ -4503,7 +4507,7 @@ namespace Js { uint32 index = end - i; - if (!TemplatedGetItem(pArr, index, &element, scriptContext)) + if (!TryTemplatedGetItem(pArr, index, &element, scriptContext)) { continue; } @@ -9017,7 +9021,7 @@ namespace Js JavascriptNumber::ToVar(k, scriptContext), obj); - if (newArr) + if (newArr && isBuiltinArrayCtor) { newArr->SetItem(k, mappedValue, PropertyOperation_None); }
lib/Runtime/Library/JavascriptArray.h+9 −2 modified@@ -567,13 +567,20 @@ namespace Js template <> static bool MayChangeType<JavascriptNativeIntArray>() { return true; } template <> static bool MayChangeType<JavascriptNativeFloatArray>() { return true; } + template<typename T, typename P> + static BOOL TryTemplatedGetItem(T *arr, P index, Var *element, ScriptContext *scriptContext) + { + return T::Is(arr) ? JavascriptArray::TemplatedGetItem(arr, index, element, scriptContext) : + JavascriptOperators::GetItem(arr, index, element, scriptContext); + } + template <bool hasSideEffect, typename T, typename Fn> static void TemplatedForEachItemInRange(T * arr, uint32 startIndex, uint32 limitIndex, Var missingItem, ScriptContext * scriptContext, Fn fn) { for (uint32 i = startIndex; i < limitIndex; i++) { Var element; - fn(i, TemplatedGetItem(arr, i, &element, scriptContext) ? element : missingItem); + fn(i, TryTemplatedGetItem(arr, i, &element, scriptContext) ? element : missingItem); if (hasSideEffect && MayChangeType<T>() && !T::Is(arr)) { @@ -590,7 +597,7 @@ namespace Js for (P i = startIndex; i < limitIndex; i++) { Var element; - if (TemplatedGetItem(arr, i, &element, scriptContext)) + if (TryTemplatedGetItem(arr, i, &element, scriptContext)) { fn(i, element);
lib/Runtime/Library/JavascriptExternalFunction.cpp+3 −3 modified@@ -250,7 +250,7 @@ namespace Js Var JavascriptExternalFunction::ExternalFunctionThunk(RecyclableObject* function, CallInfo callInfo, ...) { - RUNTIME_ARGUMENTS(args, callInfo); + ARGUMENTS(args, callInfo); JavascriptExternalFunction* externalFunction = static_cast<JavascriptExternalFunction*>(function); // Deferred constructors which are not callable fall back to using the RecyclableObject::DefaultEntryPoint. In order to call @@ -281,7 +281,7 @@ namespace Js Var JavascriptExternalFunction::WrappedFunctionThunk(RecyclableObject* function, CallInfo callInfo, ...) { - RUNTIME_ARGUMENTS(args, callInfo); + ARGUMENTS(args, callInfo); JavascriptExternalFunction* externalFunction = static_cast<JavascriptExternalFunction*>(function); ScriptContext* scriptContext = externalFunction->type->GetScriptContext(); Assert(!scriptContext->GetThreadContext()->IsDisableImplicitException()); @@ -298,7 +298,7 @@ namespace Js Var JavascriptExternalFunction::StdCallExternalFunctionThunk(RecyclableObject* function, CallInfo callInfo, ...) { - RUNTIME_ARGUMENTS(args, callInfo); + ARGUMENTS(args, callInfo); JavascriptExternalFunction* externalFunction = static_cast<JavascriptExternalFunction*>(function); externalFunction->PrepareExternalCall(&args);
test/Array/Array_TypeConfusion_bugs.js+382 −221 modified@@ -14,226 +14,387 @@ 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(); - } - }, + 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: "type confusion in Array.prototype.map with Proxy", + body: function () + { + function test() { + var d = [1,2,3]; + class dummy { + constructor() { + return d; + } + } + + var handler = { + get: function(target, name) { + if(name == "length") { + return 0x100; + } + + return {[Symbol.species] : dummy}; + }, + + has: function(target, name) { + return true; + } + }; + + var p = new Proxy([], handler); + var a = new Array(1,2,3); + + function test(){ + return 0x777777777777; + } + + var o = a.map.call(p, test); + assert.areEqual(Array(0x100).fill(0x777777777777), o); + } + 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(); + } + }, + { + name: "type confusion in Array.prototype.join", + body: function () + { + function test() { + var a = [0, 1, 2, 3]; + var b = []; + delete a[0]; + Object.setPrototypeOf(a, b) + Object.defineProperty(b, "0", + { + get: function() { + a[2] = "abc"; + return -1; + } + }); + + assert.areEqual("-1,1,abc,3", a.join()); + } + test(); + test(); + test(); + } + }, + { + name: "type confusion in Array.prototype.indexOf", + body: function () + { + function test() { + var float_val = 0xdaddeadbabe * 4.9406564584124654E-324; + var a = [0, 1, 2, 3]; + var b = []; + delete a[1]; + Object.setPrototypeOf(a, b); + Object.defineProperty(b, "1", + { + get: function() { + a[2] = float_val; //"abc"; + return -1; + } + }); + + assert.areEqual(3, a.indexOf(3)); + } + test(); + test(); + test(); + } + }, + { + name: "type confusion in Array.prototype.lastIndexOf", + body: function () + { + function test() { + var float_val = 0xdaddeadbabe * 4.9406564584124654E-324; + var a = [3, 2, 1, 0]; + var b = []; + delete a[3]; + Object.setPrototypeOf(a, b); + Object.defineProperty(b, "3", + { + get: function() { + a[1] = float_val; //"abc"; + return -1; + } + }); + + assert.areEqual(0, a.lastIndexOf(3)); + } + test(); + test(); + test(); + } + }, + { + name: "type confusion in Function.prototype.apply", + body: function () + { + function test() { + var t = [1,2,3]; + + function f(){ + var h = []; + var a = [...arguments] + + for(item in a){ + var n = new Number(a[item]); + + if( n < 0) { + n = n + 0x100000000; + } + + h.push(n.toString(16)); + } + + return h; + } + + var q = f; + + t.length = 20; + var o = {}; + Object.defineProperty(o, '3', { + get: function() { + var ta = []; + ta.fill.call(t, "natalie"); + return 5; + } + }); + + t.__proto__ = o; + + var j = []; + assert.areEqual("1,2,3,5,NaN,NaN,NaN,NaN,NaN,NaN,NaN,NaN,NaN,NaN,NaN,NaN,NaN,NaN,NaN,NaN", f.apply(null, t).toString()); + } + test(); + test(); + test(); + } + }, ]; testRunner.runTests(tests, { verbose: WScript.Arguments[0] != "summary" });
test/es6/spread.js+21 −0 modified@@ -231,6 +231,8 @@ var tests = [ assert.throws(function() { eval('a(...x)--'); }, ReferenceError, "Spread with CallIPut throws a ReferenceError"); } }, +/* + A fix for an unsafe optimization makes this portion of the test time out. { name: "BLUE 596934, 597412: Incorrect spread argument length handling", body: function () { @@ -247,6 +249,25 @@ var tests = [ assert.throws(function () { a(...new Array(3), ...new Array(1 << 32 - 2)); }, RangeError, "Total spread size greater than max call arg count throws RangeError"); } }, +*/ + { + name: "MSRC 34309: Guard against getter in prototype", + body: function () { + var x = [0x40]; + x.length = 0x9; + + Object.defineProperty(Array.prototype, 1, { + get: function() { + x.length = 0; + } + }); + + var f = function(){ + assert.areEqual(arguments.length, 2, "Changing length of x during spreading should truncate the spread."); + } + f(...x); + } + }, { name: "BLUE 611774: Spread with a prefix operator is allowed anywhere", body: function () {
THIRD-PARTY-NOTICES.txt+5 −0 modified@@ -199,6 +199,11 @@ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +--------------------------------------------- +CRC.h + +COPYRIGHT (C) 1986 Gary S. Brown. You may use this program, or code or tables +extracted from it, as desired without restriction.
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
9- github.com/advisories/GHSA-294j-r53x-w786ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2016-3389ghsaADVISORY
- docs.microsoft.com/en-us/security-updates/securitybulletins/2016/ms16-119nvdWEB
- github.com/chakra-core/ChakraCore/commit/f05c42e64c3b2d057ae1a52fe1917af26c9f2737ghsaWEB
- github.com/chakra-core/ChakraCore/pull/1737ghsaWEB
- web.archive.org/web/20210123174706/http://www.securityfocus.com/bid/93398ghsaWEB
- web.archive.org/web/20211208062350/http://www.securitytracker.com/id/1036993ghsaWEB
- www.securityfocus.com/bid/93398nvd
- www.securitytracker.com/id/1036993nvd
News mentions
0No linked articles in our index yet.