blob: f0449e737d7607d0b8c12d1f74279ea66b9b3e14 [file] [log] [blame]
//-------------------------------------------------------------------------------------------------------
// 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