| //------------------------------------------------------------------------------------------------------- |
| // 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; |
| } |
| |
| FOREACH_REAL_INSTR_IN_FUNC_EDITING(instr, instrNext, this->func) |
| { |
| if (!instr->IsRealInstr()) |
| { |
| continue; |
| } |
| IR::Opnd *dst = instr->GetDst(); |
| if (dst) |
| { |
| this->EncodeOpnd(instr, dst); |
| } |
| IR::Opnd *src1 = instr->GetSrc1(); |
| if (src1) |
| { |
| this->EncodeOpnd(instr, src1); |
| |
| IR::Opnd *src2 = instr->GetSrc2(); |
| if (src2) |
| { |
| this->EncodeOpnd(instr, src2); |
| } |
| } |
| } NEXT_REAL_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); |
| #else |
| AssertMsg(false, "Unimplemented"); |
| #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::OpndKindHelperCall: |
| // Never encode helper call addresses, as these are always internal constants. |
| return true; |
| } |
| |
| return false; |
| } |
| |
| void |
| Security::EncodeOpnd(IR::Instr *instr, IR::Opnd *opnd) |
| { |
| IR::RegOpnd *newOpnd; |
| bool isSrc2 = false; |
| |
| if (Security::DontEncode(opnd)) |
| { |
| return; |
| } |
| |
| const auto unlinkSrc = [&]() { |
| if (opnd != instr->GetSrc1()) |
| { |
| Assert(opnd == instr->GetSrc2()); |
| isSrc2 = true; |
| instr->UnlinkSrc2(); |
| } |
| else |
| { |
| instr->UnlinkSrc1(); |
| } |
| }; |
| |
| switch(opnd->GetKind()) |
| { |
| case IR::OpndKindInt64Const: |
| { |
| #if TARGET_64 |
| IR::Int64ConstOpnd *intConstOpnd = opnd->AsInt64ConstOpnd(); |
| if (!this->IsLargeConstant(intConstOpnd->GetValue())) |
| { |
| return; |
| } |
| unlinkSrc(); |
| int64 encodedValue = EncodeValue(instr, intConstOpnd, intConstOpnd->GetValue(), &newOpnd); |
| intConstOpnd->SetEncodedValue(encodedValue); |
| #else |
| Assert(UNREACHED); |
| return; |
| #endif |
| } |
| break; |
| |
| case IR::OpndKindIntConst: |
| { |
| IR::IntConstOpnd *intConstOpnd = opnd->AsIntConstOpnd(); |
| |
| if ( |
| #if TARGET_64 |
| IRType_IsInt64(intConstOpnd->GetType()) ? !this->IsLargeConstant(intConstOpnd->GetValue()) : |
| #endif |
| !this->IsLargeConstant(intConstOpnd->AsInt32())) |
| { |
| return; |
| } |
| unlinkSrc(); |
| |
| intConstOpnd->SetEncodedValue(EncodeValue(instr, intConstOpnd, intConstOpnd->GetValue(), &newOpnd)); |
| } |
| break; |
| |
| case IR::OpndKindAddr: |
| { |
| IR::AddrOpnd *addrOpnd = opnd->AsAddrOpnd(); |
| |
| // Only encode large constants. Small ones don't allow control of enough bits |
| if (Js::TaggedInt::Is(addrOpnd->m_address) && !this->IsLargeConstant(Js::TaggedInt::ToInt32(addrOpnd->m_address))) |
| { |
| return; |
| } |
| |
| 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(); |
| |
| if (!this->IsLargeConstant(indirOpnd->GetOffset()) || indirOpnd->m_dontEncode) |
| { |
| return; |
| } |
| AssertMsg(indirOpnd->GetIndexOpnd() == nullptr, "Code currently doesn't support indir with offset and indexOpnd"); |
| |
| IR::IntConstOpnd *indexOpnd = IR::IntConstOpnd::New(indirOpnd->GetOffset(), TyInt32, instr->m_func); |
| |
| indexOpnd->SetEncodedValue(EncodeValue(instr, indexOpnd, indexOpnd->GetValue(), &newOpnd)); |
| indirOpnd->SetOffset(0); |
| indirOpnd->SetIndexOpnd(newOpnd); |
| } |
| return; |
| |
| default: |
| return; |
| } |
| |
| IR::Opnd *dst = instr->GetDst(); |
| |
| if (dst) |
| { |
| #if _M_X64 |
| // 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); |
| } |
| |
| IntConstType |
| Security::EncodeValue(IR::Instr *instr, IR::Opnd *opnd, IntConstType constValue, IR::RegOpnd **pNewOpnd) |
| { |
| if (opnd->GetType() == TyInt32 || opnd->GetType() == TyInt16 || opnd->GetType() == TyInt8 |
| #if _M_IX86 |
| || opnd->GetType() == TyVar |
| #endif |
| ) |
| { |
| int32 cookie = (int32)Math::Rand(); |
| IR::RegOpnd *regOpnd = IR::RegOpnd::New(StackSym::New(TyInt32, instr->m_func), TyInt32, instr->m_func); |
| IR::Instr * instrNew = LowererMD::CreateAssign(regOpnd, opnd, instr); |
| |
| IR::IntConstOpnd * cookieOpnd = IR::IntConstOpnd::New(cookie, TyInt32, instr->m_func); |
| |
| #if DBG_DUMP |
| cookieOpnd->SetName(_u("cookie")); |
| #endif |
| |
| instrNew = IR::Instr::New(Js::OpCode::Xor_I4, regOpnd, regOpnd, cookieOpnd, instr->m_func); |
| instr->InsertBefore(instrNew); |
| |
| LowererMD::EmitInt4Instr(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 ^ cookie; |
| return value; |
| } |
| else if (opnd->GetType() == TyUint32 || opnd->GetType() == TyUint16 || opnd->GetType() == TyUint8) |
| { |
| uint32 cookie = (uint32)Math::Rand(); |
| IR::RegOpnd *regOpnd = IR::RegOpnd::New(StackSym::New(TyUint32, instr->m_func), TyUint32, instr->m_func); |
| IR::Instr * instrNew = LowererMD::CreateAssign(regOpnd, opnd, instr); |
| |
| IR::IntConstOpnd * cookieOpnd = IR::IntConstOpnd::New(cookie, TyUint32, instr->m_func); |
| |
| #if DBG_DUMP |
| cookieOpnd->SetName(_u("cookie")); |
| #endif |
| |
| instrNew = IR::Instr::New(Js::OpCode::Xor_I4, regOpnd, regOpnd, cookieOpnd, instr->m_func); |
| instr->InsertBefore(instrNew); |
| |
| LowererMD::EmitInt4Instr(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 ^ cookie; |
| return (IntConstType)value; |
| } |
| else |
| { |
| #ifdef _M_X64 |
| return this->EncodeAddress(instr, opnd, constValue, pNewOpnd); |
| #else |
| Assert(false); |
| return 0; |
| #endif |
| } |
| } |
| |
| #ifdef _M_X64 |
| size_t |
| Security::EncodeAddress(IR::Instr *instr, IR::Opnd *opnd, size_t value, IR::RegOpnd **pNewOpnd) |
| { |
| IR::Instr *instrNew = nullptr; |
| IR::RegOpnd *regOpnd = IR::RegOpnd::New(TyMachReg, instr->m_func); |
| |
| instrNew = LowererMD::CreateAssign(regOpnd, opnd, instr); |
| |
| size_t cookie = (size_t)Math::Rand(); |
| IR::IntConstOpnd *cookieOpnd = IR::IntConstOpnd::New(cookie, TyMachReg, instr->m_func); |
| instrNew = IR::Instr::New(Js::OpCode::XOR, 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 ^ cookie; |
| } |
| #endif |