| //------------------------------------------------------------------------------------------------------- |
| // Copyright (C) Microsoft. All rights reserved. |
| // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. |
| //------------------------------------------------------------------------------------------------------- |
| #include "Backend.h" |
| |
| void |
| Security::EncodeLargeConstants() |
| { |
| #pragma prefast(suppress:6236 6285, "logical-or of constants is by design") |
| if (PHASE_OFF(Js::EncodeConstantsPhase, this->func) || CONFIG_ISENABLED(Js::DebugFlag) || !MD_ENCODE_LG_CONSTS) |
| { |
| return; |
| } |
| |
| uint prevInstrConstSize = 0; |
| FOREACH_INSTR_IN_FUNC_EDITING(instr, instrNext, this->func) |
| { |
| if (!instr->IsRealInstr()) |
| { |
| if (instr->IsLabelInstr()) |
| { |
| IR::LabelInstr * label = instr->AsLabelInstr(); |
| |
| if (label->labelRefs.Count() > 1 || (label->labelRefs.Count() == 1 && label->labelRefs.Head() != label->m_prev)) |
| { |
| if (this->cookieOpnd != nullptr) |
| { |
| this->cookieOpnd->Free(this->func); |
| } |
| this->baseOpnd = nullptr; |
| this->cookieOpnd = nullptr; |
| this->basePlusCookieOpnd = nullptr; |
| } |
| } |
| continue; |
| } |
| |
| IR::Opnd *src1 = instr->GetSrc1(); |
| IR::Opnd *src2 = instr->GetSrc2(); |
| IR::Opnd *dst = instr->GetDst(); |
| |
| if (dst && this->baseOpnd && dst->IsEqual(this->baseOpnd)) |
| { |
| if (this->cookieOpnd != nullptr) |
| { |
| this->cookieOpnd->Free(this->func); |
| } |
| this->baseOpnd = nullptr; |
| this->cookieOpnd = nullptr; |
| this->basePlusCookieOpnd = nullptr; |
| } |
| |
| uint currInstrConstSize = 0; |
| uint dstSize = dst ? CalculateConstSize(dst) : 0; |
| uint src1Size = 0; |
| uint src2Size = 0; |
| if (src1) |
| { |
| src1Size = CalculateConstSize(src1); |
| if (src2) |
| { |
| src2Size = CalculateConstSize(src2); |
| } |
| } |
| |
| prevInstrConstSize = currInstrConstSize; |
| currInstrConstSize = dstSize + src1Size + src2Size; |
| |
| // we don't need to blind constants if user controlled byte size < 3 |
| if (currInstrConstSize + prevInstrConstSize <= 2 && !PHASE_FORCE1(Js::EncodeConstantsPhase)) |
| { |
| continue; |
| } |
| |
| bool isSrc1EqualDst = false; |
| if (dstSize >= 2) |
| { |
| // don't count instrs where dst == src1 against size |
| if (src1 && dstSize == src1Size && src1->IsEqual(dst)) |
| { |
| currInstrConstSize -= dstSize; |
| isSrc1EqualDst = true; |
| |
| if (currInstrConstSize + prevInstrConstSize <= 2 && !PHASE_FORCE1(Js::EncodeConstantsPhase)) |
| { |
| continue; |
| } |
| } |
| |
| this->EncodeOpnd(instr, dst); |
| if (isSrc1EqualDst) |
| { |
| instr->ReplaceSrc1(dst); |
| } |
| currInstrConstSize -= dstSize; |
| if (currInstrConstSize + prevInstrConstSize <= 2 && !PHASE_FORCE1(Js::EncodeConstantsPhase)) |
| { |
| continue; |
| } |
| } |
| if (src1Size >= 2 && !isSrc1EqualDst) |
| { |
| this->EncodeOpnd(instr, src1); |
| currInstrConstSize -= src1Size; |
| if (currInstrConstSize + prevInstrConstSize <= 2 && !PHASE_FORCE1(Js::EncodeConstantsPhase)) |
| { |
| continue; |
| } |
| } |
| if (src2Size >= 2) |
| { |
| this->EncodeOpnd(instr, src2); |
| currInstrConstSize -= src2Size; |
| } |
| } NEXT_INSTR_IN_FUNC_EDITING; |
| |
| } |
| |
| int |
| Security::GetNextNOPInsertPoint() |
| { |
| uint frequency = (1 << CONFIG_FLAG(NopFrequency)) - 1; |
| return (Math::Rand() & frequency) + 1; |
| } |
| |
| void |
| Security::InsertRandomFunctionPad(IR::Instr * instrBeforeInstr) |
| { |
| if (PHASE_OFF(Js::InsertNOPsPhase, instrBeforeInstr->m_func->GetTopFunc()) |
| || CONFIG_ISENABLED(Js::DebugFlag) || CONFIG_ISENABLED(Js::BenchmarkFlag)) |
| { |
| return; |
| } |
| DWORD randomPad = Math::Rand() & ((0 - INSTR_ALIGNMENT) & 0xF); |
| #ifndef _M_ARM |
| if (randomPad == 1) |
| { |
| InsertSmallNOP(instrBeforeInstr, 1); |
| return; |
| } |
| if (randomPad & 1) |
| { |
| InsertSmallNOP(instrBeforeInstr, 3); |
| randomPad -= 3; |
| } |
| #endif |
| Assert((randomPad & 1) == 0); |
| while (randomPad >= 4) |
| { |
| InsertSmallNOP(instrBeforeInstr, 4); |
| randomPad -= 4; |
| } |
| Assert(randomPad == 2 || randomPad == 0); |
| if (randomPad == 2) |
| { |
| InsertSmallNOP(instrBeforeInstr, 2); |
| } |
| } |
| |
| |
| void |
| Security::InsertNOPs() |
| { |
| if (PHASE_OFF(Js::InsertNOPsPhase, this->func) || CONFIG_ISENABLED(Js::DebugFlag) || CONFIG_ISENABLED(Js::BenchmarkFlag)) |
| { |
| return; |
| } |
| |
| int count = 0; |
| IR::Instr *instr = this->func->m_headInstr; |
| |
| while (true) |
| { |
| count = this->GetNextNOPInsertPoint(); |
| while (instr && count--) |
| { |
| instr = instr->GetNextRealInstr(); |
| } |
| if (instr == nullptr) |
| { |
| break; |
| } |
| this->InsertNOPBefore(instr); |
| }; |
| } |
| |
| void |
| Security::InsertNOPBefore(IR::Instr *instr) |
| { |
| InsertSmallNOP(instr, (Math::Rand() & 0x3) + 1); |
| } |
| |
| void |
| Security::InsertSmallNOP(IR::Instr * instr, DWORD nopSize) |
| { |
| #if defined(_M_IX86) || defined(_M_X64) |
| #ifdef _M_IX86 |
| if (AutoSystemInfo::Data.SSE2Available()) |
| { // on x86 system that has SSE2, encode fast NOPs as x64 does |
| #endif |
| Assert(nopSize >= 1 || nopSize <= 4); |
| IR::Instr *nop = IR::Instr::New(Js::OpCode::NOP, instr->m_func); |
| |
| // Let the encoder know what the size of the NOP needs to be. |
| if (nopSize > 1) |
| { |
| // 2, 3 or 4 byte NOP. |
| IR::IntConstOpnd *nopSizeOpnd = IR::IntConstOpnd::New(nopSize, TyInt8, instr->m_func); |
| nop->SetSrc1(nopSizeOpnd); |
| } |
| |
| instr->InsertBefore(nop); |
| #ifdef _M_IX86 |
| } |
| else |
| { |
| IR::Instr *nopInstr = nullptr; |
| IR::RegOpnd *regOpnd; |
| IR::IndirOpnd *indirOpnd; |
| switch (nopSize) |
| { |
| case 1: |
| // nop |
| nopInstr = IR::Instr::New(Js::OpCode::NOP, instr->m_func); |
| break; |
| case 2: |
| // mov edi, edi ; 2 bytes |
| regOpnd = IR::RegOpnd::New(nullptr, RegEDI, TyInt32, instr->m_func); |
| nopInstr = IR::Instr::New(Js::OpCode::MOV, regOpnd, regOpnd, instr->m_func); |
| break; |
| case 3: |
| // lea ecx, [ecx+00] ; 3 bytes |
| regOpnd = IR::RegOpnd::New(nullptr, RegECX, TyInt32, instr->m_func); |
| indirOpnd = IR::IndirOpnd::New(regOpnd, (int32)0, TyInt32, instr->m_func); |
| nopInstr = IR::Instr::New(Js::OpCode::LEA, regOpnd, indirOpnd, instr->m_func); |
| break; |
| case 4: |
| // lea esp, [esp+00] ; 4 bytes |
| regOpnd = IR::RegOpnd::New(nullptr, RegESP, TyInt32, instr->m_func); |
| indirOpnd = IR::IndirOpnd::New(regOpnd, (int32)0, TyInt32, instr->m_func); |
| nopInstr = IR::Instr::New(Js::OpCode::LEA, regOpnd, indirOpnd, instr->m_func); |
| break; |
| default: |
| Assert(false); |
| break; |
| } |
| instr->InsertBefore(nopInstr); |
| } |
| #endif |
| #elif defined(_M_ARM) |
| // Can't insert 3 bytes, must choose between 2 and 4. |
| |
| IR::Instr *nopInstr = nullptr; |
| |
| switch (nopSize) |
| { |
| case 1: |
| case 2: |
| nopInstr = IR::Instr::New(Js::OpCode::NOP, instr->m_func); |
| break; |
| case 3: |
| case 4: |
| nopInstr = IR::Instr::New(Js::OpCode::NOP_W, instr->m_func); |
| break; |
| default: |
| Assert(false); |
| break; |
| } |
| |
| instr->InsertBefore(nopInstr); |
| #elif defined(_M_ARM64) |
| |
| // All ARM64 instructions are 4 bytes. |
| IR::Instr *nopInstr = IR::Instr::New(Js::OpCode::NOP, instr->m_func); |
| instr->InsertBefore(nopInstr); |
| |
| #endif |
| } |
| |
| bool |
| Security::DontEncode(IR::Opnd *opnd) |
| { |
| switch (opnd->GetKind()) |
| { |
| case IR::OpndKindIntConst: |
| { |
| IR::IntConstOpnd *intConstOpnd = opnd->AsIntConstOpnd(); |
| return intConstOpnd->m_dontEncode; |
| } |
| |
| case IR::OpndKindAddr: |
| { |
| IR::AddrOpnd *addrOpnd = opnd->AsAddrOpnd(); |
| return (addrOpnd->m_dontEncode || |
| !addrOpnd->IsVar() || |
| addrOpnd->m_address == nullptr || |
| !Js::TaggedNumber::Is(addrOpnd->m_address)); |
| } |
| case IR::OpndKindIndir: |
| { |
| IR::IndirOpnd *indirOpnd = opnd->AsIndirOpnd(); |
| return indirOpnd->m_dontEncode || indirOpnd->GetOffset() == 0; |
| } |
| case IR::OpndKindInt64Const: |
| return false; |
| case IR::OpndKindList: |
| { |
| // We should only have RegOpnd in the ListOpnd therefore, we don't need to encode anything |
| Assert(opnd->AsListOpnd()->All([](IR::ListOpndType* opnd) { return DontEncode(opnd); })); |
| return true; |
| } |
| default: |
| return true; |
| } |
| } |
| |
| uint |
| Security::CalculateConstSize(IR::Opnd *opnd) |
| { |
| if (DontEncode(opnd)) |
| { |
| return 0; |
| } |
| switch (opnd->GetKind()) |
| { |
| #if TARGET_64 |
| case IR::OpndKindInt64Const: |
| { |
| IR::Int64ConstOpnd *intConstOpnd = opnd->AsInt64ConstOpnd(); |
| return GetByteCount(intConstOpnd->GetValue()); |
| } |
| #endif |
| case IR::OpndKindIntConst: |
| { |
| IR::IntConstOpnd *intConstOpnd = opnd->AsIntConstOpnd(); |
| return GetByteCount(intConstOpnd->GetValue()); |
| } |
| case IR::OpndKindAddr: |
| { |
| IR::AddrOpnd *addrOpnd = opnd->AsAddrOpnd(); |
| return Js::TaggedInt::Is(addrOpnd->m_address) ? GetByteCount(Js::TaggedInt::ToInt32(addrOpnd->m_address)) : GetByteCount((intptr_t)addrOpnd->m_address); |
| } |
| case IR::OpndKindIndir: |
| { |
| IR::IndirOpnd * indirOpnd = opnd->AsIndirOpnd(); |
| return GetByteCount(indirOpnd->GetOffset()); |
| } |
| default: |
| Assume(UNREACHED); |
| } |
| return 0; |
| } |
| bool |
| Security::EncodeOpnd(IR::Instr * instr, IR::Opnd *opnd) |
| { |
| IR::RegOpnd *newOpnd; |
| bool isSrc2 = false; |
| |
| const auto unlinkSrc = [&]() { |
| if (opnd != instr->GetSrc1()) |
| { |
| Assert(opnd == instr->GetSrc2()); |
| isSrc2 = true; |
| instr->UnlinkSrc2(); |
| } |
| else |
| { |
| instr->UnlinkSrc1(); |
| } |
| }; |
| |
| switch (opnd->GetKind()) |
| { |
| case IR::OpndKindIntConst: |
| { |
| IR::IntConstOpnd *intConstOpnd = opnd->AsIntConstOpnd(); |
| |
| unlinkSrc(); |
| |
| intConstOpnd->SetEncodedValue(EncodeValue(instr, intConstOpnd, intConstOpnd->GetValue(), &newOpnd)); |
| } |
| break; |
| |
| case IR::OpndKindAddr: |
| { |
| IR::AddrOpnd *addrOpnd = opnd->AsAddrOpnd(); |
| |
| unlinkSrc(); |
| |
| addrOpnd->SetEncodedValue((Js::Var)this->EncodeValue(instr, addrOpnd, (IntConstType)addrOpnd->m_address, &newOpnd), addrOpnd->GetAddrOpndKind()); |
| } |
| break; |
| |
| case IR::OpndKindIndir: |
| { |
| IR::IndirOpnd *indirOpnd = opnd->AsIndirOpnd(); |
| |
| // Using 32 bit cookie causes major perf loss on the subsequent indirs, so only support this path for 16 bit offset |
| // It's relatively rare for base to be null or to have index + offset, so fall back to the more generic xor method for these |
| if (indirOpnd->GetBaseOpnd() && indirOpnd->GetIndexOpnd() == nullptr && Math::FitsInWord(indirOpnd->GetOffset())) |
| { |
| if (!this->baseOpnd || !this->baseOpnd->IsEqual(indirOpnd->GetBaseOpnd())) |
| { |
| if (this->cookieOpnd != nullptr) |
| { |
| this->cookieOpnd->Free(this->func); |
| } |
| this->cookieOpnd = BuildCookieOpnd(TyInt16, instr->m_func); |
| this->basePlusCookieOpnd = IR::RegOpnd::New(TyMachReg, instr->m_func); |
| this->baseOpnd = indirOpnd->GetBaseOpnd(); |
| IR::IndirOpnd * indir = IR::IndirOpnd::New(this->baseOpnd, this->cookieOpnd->AsInt32(), TyMachReg, instr->m_func); |
| Lowerer::InsertLea(this->basePlusCookieOpnd, indir, instr); |
| } |
| int32 diff = indirOpnd->GetOffset() - this->cookieOpnd->AsInt32(); |
| indirOpnd->SetOffset((int32)diff); |
| indirOpnd->SetBaseOpnd(this->basePlusCookieOpnd); |
| return true; |
| } |
| |
| IR::IntConstOpnd *indexOpnd = IR::IntConstOpnd::New(indirOpnd->GetOffset(), TyMachReg, instr->m_func); |
| |
| indexOpnd->SetValue(EncodeValue(instr, indexOpnd, indexOpnd->GetValue(), &newOpnd)); |
| |
| indirOpnd->SetOffset(0); |
| if (indirOpnd->GetIndexOpnd() != nullptr) |
| { |
| // update the base rather than the index, because index might have scale |
| if (indirOpnd->GetBaseOpnd() != nullptr) |
| { |
| IR::RegOpnd * newBaseOpnd = IR::RegOpnd::New(TyMachReg, instr->m_func); |
| Lowerer::InsertAdd(false, newBaseOpnd, newOpnd, indirOpnd->GetBaseOpnd(), instr); |
| indirOpnd->ReplaceBaseOpnd(newBaseOpnd); |
| } |
| else |
| { |
| indirOpnd->SetBaseOpnd(newOpnd); |
| } |
| } |
| else |
| { |
| indirOpnd->SetIndexOpnd(newOpnd); |
| } |
| } |
| return true; |
| |
| default: |
| return false; |
| } |
| |
| IR::Opnd *dst = instr->GetDst(); |
| |
| if (dst) |
| { |
| #if TARGET_64 |
| // Ensure the left and right operand has the same type (that might not be true for constants on x64) |
| newOpnd = (IR::RegOpnd *)newOpnd->UseWithNewType(dst->GetType(), instr->m_func); |
| #endif |
| if (dst->IsRegOpnd()) |
| { |
| IR::RegOpnd *dstRegOpnd = dst->AsRegOpnd(); |
| StackSym *dstSym = dstRegOpnd->m_sym; |
| |
| if (dstSym) |
| { |
| dstSym->m_isConst = false; |
| dstSym->m_isIntConst = false; |
| dstSym->m_isInt64Const = false; |
| dstSym->m_isTaggableIntConst = false; |
| dstSym->m_isFltConst = false; |
| } |
| } |
| } |
| |
| LowererMD::ImmedSrcToReg(instr, newOpnd, isSrc2 ? 2 : 1); |
| return true; |
| } |
| |
| IR::IntConstOpnd * |
| Security::BuildCookieOpnd(IRType type, Func * func) |
| { |
| IntConstType cookie = 0; |
| switch (type) |
| { |
| case TyInt8: |
| cookie = (int8)Math::Rand(); |
| break; |
| case TyUint8: |
| cookie = (uint8)Math::Rand(); |
| break; |
| case TyInt16: |
| cookie = (int16)Math::Rand(); |
| break; |
| case TyUint16: |
| cookie = (uint16)Math::Rand(); |
| break; |
| #if TARGET_32 |
| case TyVar: |
| #endif |
| case TyInt32: |
| cookie = (int32)Math::Rand(); |
| break; |
| case TyUint32: |
| cookie = (uint32)Math::Rand(); |
| break; |
| #if TARGET_64 |
| case TyVar: |
| case TyInt64: |
| case TyUint64: |
| cookie = Math::Rand(); |
| break; |
| #endif |
| default: |
| Assume(UNREACHED); |
| } |
| IR::IntConstOpnd * cookieOpnd = IR::IntConstOpnd::New(cookie, type, func); |
| |
| #if DBG_DUMP |
| cookieOpnd->SetName(_u("cookie")); |
| #endif |
| return cookieOpnd; |
| } |
| |
| IntConstType |
| Security::EncodeValue(IR::Instr * instr, IR::Opnd *opnd, IntConstType constValue, _Out_ IR::RegOpnd **pNewOpnd) |
| { |
| if (opnd->GetType() == TyInt32 || opnd->GetType() == TyInt16 || opnd->GetType() == TyInt8 |
| #if TARGET_32 |
| || opnd->GetType() == TyVar |
| #endif |
| ) |
| { |
| IR::RegOpnd *regOpnd = IR::RegOpnd::New(StackSym::New(opnd->GetType(), instr->m_func), opnd->GetType(), instr->m_func); |
| IR::Instr * instrNew = Lowerer::InsertMove(regOpnd, opnd, instr); |
| IR::IntConstOpnd * cookieOpnd = BuildCookieOpnd(opnd->GetType(), instr->m_func); |
| instrNew = IR::Instr::New(LowererMD::MDXorOpcode, regOpnd, regOpnd, cookieOpnd, instr->m_func); |
| instr->InsertBefore(instrNew); |
| LowererMD::Legalize(instrNew); |
| |
| StackSym * stackSym = regOpnd->m_sym; |
| Assert(!stackSym->m_isSingleDef); |
| Assert(stackSym->m_instrDef == nullptr); |
| stackSym->m_isEncodedConstant = true; |
| stackSym->constantValue = (int32)constValue; |
| |
| *pNewOpnd = regOpnd; |
| |
| int32 value = (int32)constValue; |
| value = value ^ cookieOpnd->AsInt32(); |
| return value; |
| } |
| else if (opnd->GetType() == TyUint32 || opnd->GetType() == TyUint16 || opnd->GetType() == TyUint8) |
| { |
| IR::RegOpnd *regOpnd = IR::RegOpnd::New(StackSym::New(opnd->GetType(), instr->m_func), opnd->GetType(), instr->m_func); |
| IR::Instr * instrNew = Lowerer::InsertMove(regOpnd, opnd, instr); |
| |
| IR::IntConstOpnd * cookieOpnd = BuildCookieOpnd(opnd->GetType(), instr->m_func); |
| |
| instrNew = IR::Instr::New(LowererMD::MDXorOpcode, regOpnd, regOpnd, cookieOpnd, instr->m_func); |
| instr->InsertBefore(instrNew); |
| LowererMD::Legalize(instrNew); |
| |
| StackSym * stackSym = regOpnd->m_sym; |
| Assert(!stackSym->m_isSingleDef); |
| Assert(stackSym->m_instrDef == nullptr); |
| stackSym->m_isEncodedConstant = true; |
| stackSym->constantValue = (uint32)constValue; |
| |
| *pNewOpnd = regOpnd; |
| |
| uint32 value = (uint32)constValue; |
| value = value ^ cookieOpnd->AsUint32(); |
| return (IntConstType)value; |
| } |
| else |
| { |
| #if TARGET_64 |
| return this->EncodeAddress(instr, opnd, constValue, pNewOpnd); |
| #else |
| Assert(false); |
| // (Prefast warning on failure to assign *pNewOpnd.) |
| *pNewOpnd = nullptr; |
| return 0; |
| #endif |
| } |
| } |
| |
| #if TARGET_64 |
| size_t |
| Security::EncodeAddress(IR::Instr * instr, IR::Opnd *opnd, size_t value, _Out_ IR::RegOpnd **pNewOpnd) |
| { |
| IR::Instr *instrNew = nullptr; |
| IR::RegOpnd *regOpnd = IR::RegOpnd::New(TyMachReg, instr->m_func); |
| |
| instrNew = Lowerer::InsertMove(regOpnd, opnd, instr); |
| |
| IR::IntConstOpnd *cookieOpnd = BuildCookieOpnd(TyMachReg, instr->m_func); |
| instrNew = IR::Instr::New(LowererMD::MDXorOpcode, regOpnd, regOpnd, cookieOpnd, instr->m_func); |
| instr->InsertBefore(instrNew); |
| LowererMD::Legalize(instrNew); |
| |
| StackSym * stackSym = regOpnd->m_sym; |
| Assert(!stackSym->m_isSingleDef); |
| Assert(stackSym->m_instrDef == nullptr); |
| stackSym->m_isEncodedConstant = true; |
| stackSym->constantValue = value; |
| |
| *pNewOpnd = regOpnd; |
| return value ^ cookieOpnd->GetValue(); |
| } |
| #endif |