diff --git a/THIRD-PARTY-NOTICES.txt b/THIRD-PARTY-NOTICES.txt index 8228d28c62a..b55f7848858 100644 --- a/THIRD-PARTY-NOTICES.txt +++ b/THIRD-PARTY-NOTICES.txt @@ -229,3 +229,11 @@ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------------------------- +--------------------------------------------- + +CRC.h + +COPYRIGHT (C) 1986 Gary S. Brown. You may use this program, or code or tables +extracted from it, as desired without restriction. + + diff --git a/jenkins/check_copyright.sh b/jenkins/check_copyright.sh index c457db0f5d0..6cf8bcaa08e 100755 --- a/jenkins/check_copyright.sh +++ b/jenkins/check_copyright.sh @@ -37,6 +37,7 @@ git diff --name-only `git merge-base origin/master HEAD` HEAD | grep -v -E 'bin/NativeTests/Scripts/splay.js$' | grep -v -E 'pal/.*' | grep -v -E 'libChakraCoreLib.version|ch.version' | + 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 diff --git a/lib/Backend/CRC.h b/lib/Backend/CRC.h new file mode 100644 index 00000000000..6d3e400d3ab --- /dev/null +++ b/lib/Backend/CRC.h @@ -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 diff --git a/lib/Backend/Chakra.Backend.vcxproj b/lib/Backend/Chakra.Backend.vcxproj index 4ee35033f8d..62ce388bf58 100644 --- a/lib/Backend/Chakra.Backend.vcxproj +++ b/lib/Backend/Chakra.Backend.vcxproj @@ -410,10 +410,15 @@ + + + CppCode + + - + \ No newline at end of file diff --git a/lib/Backend/Chakra.Backend.vcxproj.filters b/lib/Backend/Chakra.Backend.vcxproj.filters index 0e824bfa3a9..36cbf9f542d 100644 --- a/lib/Backend/Chakra.Backend.vcxproj.filters +++ b/lib/Backend/Chakra.Backend.vcxproj.filters @@ -300,6 +300,7 @@ + diff --git a/lib/Backend/Encoder.cpp b/lib/Backend/Encoder.cpp index a4077b8f4c7..52605f44c6d 100644 --- a/lib/Backend/Encoder.cpp +++ b/lib/Backend/Encoder.cpp @@ -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; @@ -795,6 +972,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. @@ -853,6 +1032,10 @@ 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; @@ -860,10 +1043,29 @@ Encoder::ShortenBranchesAndLabelAlign(BYTE **codeStart, ptrdiff_t *codeSize) // 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) { @@ -874,7 +1076,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)) @@ -886,17 +1090,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; @@ -909,13 +1124,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; } diff --git a/lib/Backend/Encoder.h b/lib/Backend/Encoder.h index 6b99854ad4f..e62651641aa 100644 --- a/lib/Backend/Encoder.h +++ b/lib/Backend/Encoder.h @@ -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 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 }; diff --git a/lib/Backend/GlobOpt.cpp b/lib/Backend/GlobOpt.cpp index 4f51f7c63e1..38311438a50 100644 --- a/lib/Backend/GlobOpt.cpp +++ b/lib/Backend/GlobOpt.cpp @@ -940,9 +940,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; diff --git a/lib/Backend/LowerMDShared.cpp b/lib/Backend/LowerMDShared.cpp index 12d6955a904..b9c6bfa294e 100644 --- a/lib/Backend/LowerMDShared.cpp +++ b/lib/Backend/LowerMDShared.cpp @@ -8898,7 +8898,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) diff --git a/lib/Backend/amd64/EncoderMD.cpp b/lib/Backend/amd64/EncoderMD.cpp index 0db53b3dc47..2499a637772 100644 --- a/lib/Backend/amd64/EncoderMD.cpp +++ b/lib/Backend/amd64/EncoderMD.cpp @@ -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) { if (m_relocList == nullptr) { @@ -1565,22 +1565,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: @@ -1593,6 +1623,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 ///---------------------------------------------------------------------------- /// diff --git a/lib/Backend/amd64/EncoderMD.h b/lib/Backend/amd64/EncoderMD.h index 42dac6f90c8..061e52a1a95 100644 --- a/lib/Backend/amd64/EncoderMD.h +++ b/lib/Backend/amd64/EncoderMD.h @@ -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); diff --git a/lib/Backend/arm/EncoderMD.cpp b/lib/Backend/arm/EncoderMD.cpp index ab77dcb7473..6c1f69f7de0 100644 --- a/lib/Backend/arm/EncoderMD.cpp +++ b/lib/Backend/arm/EncoderMD.cpp @@ -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) { diff --git a/lib/Backend/arm/EncoderMD.h b/lib/Backend/arm/EncoderMD.h index 3243788330f..eaed327c271 100644 --- a/lib/Backend/arm/EncoderMD.h +++ b/lib/Backend/arm/EncoderMD.h @@ -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); diff --git a/lib/Backend/arm64/EncoderMD.h b/lib/Backend/arm64/EncoderMD.h index 962ed027371..4fc3bdf9da3 100644 --- a/lib/Backend/arm64/EncoderMD.h +++ b/lib/Backend/arm64/EncoderMD.h @@ -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; } diff --git a/lib/Backend/i386/EncoderMD.cpp b/lib/Backend/i386/EncoderMD.cpp index 09167972633..3149942b74a 100644 --- a/lib/Backend/i386/EncoderMD.cpp +++ b/lib/Backend/i386/EncoderMD.cpp @@ -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 { @@ -917,24 +914,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 @@ -1280,13 +1274,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); } @@ -1340,7 +1334,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; @@ -1395,7 +1389,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++) { @@ -1408,7 +1402,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: @@ -1420,20 +1420,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: @@ -1446,6 +1474,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 diff --git a/lib/Backend/i386/EncoderMD.h b/lib/Backend/i386/EncoderMD.h index 4f22fb9a7be..1919d44505b 100644 --- a/lib/Backend/i386/EncoderMD.h +++ b/lib/Backend/i386/EncoderMD.h @@ -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,8 +209,8 @@ 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); @@ -190,7 +218,7 @@ class EncoderMD 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); @@ -198,6 +226,7 @@ class EncoderMD void VerifyRelocList(BYTE *buffStart, BYTE *buffEnd); #endif void AddLabelReloc(BYTE* relocAddress); + BYTE * GetRelocBufferAddress(EncodeRelocAndLabels * reloc); private: const BYTE GetOpcodeByte2(IR::Instr *instr); diff --git a/lib/Common/Core/SysInfo.cpp b/lib/Common/Core/SysInfo.cpp index d5aa453f0af..2ee2f72babc 100644 --- a/lib/Common/Core/SysInfo.cpp +++ b/lib/Common/Core/SysInfo.cpp @@ -241,6 +241,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 { diff --git a/lib/Common/Core/SysInfo.h b/lib/Common/Core/SysInfo.h index 9158388bc59..4e84244b88b 100644 --- a/lib/Common/Core/SysInfo.h +++ b/lib/Common/Core/SysInfo.h @@ -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; diff --git a/lib/Runtime/Language/JavascriptOperators.cpp b/lib/Runtime/Language/JavascriptOperators.cpp index d27fde1669a..c6f2fa569ff 100644 --- a/lib/Runtime/Language/JavascriptOperators.cpp +++ b/lib/Runtime/Language/JavascriptOperators.cpp @@ -1400,10 +1400,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)) { diff --git a/lib/Runtime/Library/JavascriptArray.cpp b/lib/Runtime/Library/JavascriptArray.cpp index d737dec961b..1fb9934f40b 100644 --- a/lib/Runtime/Library/JavascriptArray.cpp +++ b/lib/Runtime/Library/JavascriptArray.cpp @@ -3865,7 +3865,6 @@ namespace Js Throw::InternalError(); } - template Var JavascriptArray::TemplatedIndexOfHelper(T * pArr, Var search, P fromIndex, P toIndex, ScriptContext * scriptContext) { @@ -3879,7 +3878,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) { @@ -4300,7 +4299,8 @@ namespace Js { cs->Append(separator); } - if (TemplatedGetItem(arr, i, &item, scriptContext)) + + if (TryTemplatedGetItem(arr, i, &item, scriptContext)) { cs->Append(JavascriptArray::JoinToString(item, scriptContext)); } @@ -4318,19 +4318,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; } @@ -4566,7 +4570,7 @@ namespace Js { uint32 index = end - i; - if (!TemplatedGetItem(pArr, index, &element, scriptContext)) + if (!TryTemplatedGetItem(pArr, index, &element, scriptContext)) { continue; } @@ -9027,7 +9031,7 @@ namespace Js JavascriptNumber::ToVar(k, scriptContext), obj); - if (newArr) + if (newArr && isBuiltinArrayCtor) { newArr->SetItem(k, mappedValue, PropertyOperation_None); } diff --git a/lib/Runtime/Library/JavascriptArray.h b/lib/Runtime/Library/JavascriptArray.h index cce2deecee7..c109a134d25 100644 --- a/lib/Runtime/Library/JavascriptArray.h +++ b/lib/Runtime/Library/JavascriptArray.h @@ -567,13 +567,20 @@ namespace Js // NativeArrays may change it's content type, but not others template static bool MayChangeType() { return false; } + template + 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 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::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); diff --git a/lib/Runtime/Library/JavascriptExternalFunction.cpp b/lib/Runtime/Library/JavascriptExternalFunction.cpp index db77ebfbe19..27eeda76b80 100644 --- a/lib/Runtime/Library/JavascriptExternalFunction.cpp +++ b/lib/Runtime/Library/JavascriptExternalFunction.cpp @@ -176,7 +176,7 @@ namespace Js Var JavascriptExternalFunction::ExternalFunctionThunk(RecyclableObject* function, CallInfo callInfo, ...) { - RUNTIME_ARGUMENTS(args, callInfo); + ARGUMENTS(args, callInfo); JavascriptExternalFunction* externalFunction = static_cast(function); ScriptContext * scriptContext = externalFunction->type->GetScriptContext(); @@ -260,7 +260,7 @@ namespace Js Var JavascriptExternalFunction::WrappedFunctionThunk(RecyclableObject* function, CallInfo callInfo, ...) { - RUNTIME_ARGUMENTS(args, callInfo); + ARGUMENTS(args, callInfo); JavascriptExternalFunction* externalFunction = static_cast(function); ScriptContext* scriptContext = externalFunction->type->GetScriptContext(); Assert(!scriptContext->GetThreadContext()->IsDisableImplicitException()); @@ -284,7 +284,7 @@ namespace Js Var JavascriptExternalFunction::StdCallExternalFunctionThunk(RecyclableObject* function, CallInfo callInfo, ...) { - RUNTIME_ARGUMENTS(args, callInfo); + ARGUMENTS(args, callInfo); JavascriptExternalFunction* externalFunction = static_cast(function); externalFunction->PrepareExternalCall(&args); diff --git a/test/Array/Array_TypeConfusion_bugs.js b/test/Array/Array_TypeConfusion_bugs.js index 1d1ce51f959..c3661c8bf2a 100644 --- a/test/Array/Array_TypeConfusion_bugs.js +++ b/test/Array/Array_TypeConfusion_bugs.js @@ -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 - 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 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 + 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 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" }); diff --git a/test/es6/spread.js b/test/es6/spread.js index 9c079ee0cc0..40de318c179 100644 --- a/test/es6/spread.js +++ b/test/es6/spread.js @@ -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 () {