| //===- subzero/src/IceASanInstrumentation.cpp - ASan ------------*- C++ -*-===// | |
| // | |
| // The Subzero Code Generator | |
| // | |
| // This file is distributed under the University of Illinois Open Source | |
| // License. See LICENSE.TXT for details. | |
| // | |
| //===----------------------------------------------------------------------===// | |
| /// | |
| /// \file | |
| /// \brief Implements the AddressSanitizer instrumentation class. | |
| /// | |
| //===----------------------------------------------------------------------===// | |
| #include "IceASanInstrumentation.h" | |
| #include "IceBuildDefs.h" | |
| #include "IceCfg.h" | |
| #include "IceCfgNode.h" | |
| #include "IceGlobalInits.h" | |
| #include "IceInst.h" | |
| #include "IceTargetLowering.h" | |
| #include "IceTypes.h" | |
| #include <sstream> | |
| #include <unordered_map> | |
| #include <unordered_set> | |
| #include <vector> | |
| namespace Ice { | |
| namespace { | |
| constexpr SizeT BytesPerWord = sizeof(uint32_t); | |
| constexpr SizeT RzSize = 32; | |
| constexpr SizeT ShadowScaleLog2 = 3; | |
| constexpr SizeT ShadowScale = 1 << ShadowScaleLog2; | |
| constexpr SizeT ShadowLength32 = 1 << (32 - ShadowScaleLog2); | |
| constexpr int32_t StackPoisonVal = -1; | |
| constexpr const char *ASanPrefix = "__asan"; | |
| constexpr const char *RzPrefix = "__$rz"; | |
| constexpr const char *RzArrayName = "__$rz_array"; | |
| constexpr const char *RzSizesName = "__$rz_sizes"; | |
| const llvm::NaClBitcodeRecord::RecordVector RzContents = | |
| llvm::NaClBitcodeRecord::RecordVector(RzSize, 'R'); | |
| // In order to instrument the code correctly, the .pexe must not have had its | |
| // symbols stripped. | |
| using StringMap = std::unordered_map<std::string, std::string>; | |
| using StringSet = std::unordered_set<std::string>; | |
| // TODO(tlively): Handle all allocation functions | |
| const StringMap FuncSubstitutions = {{"malloc", "__asan_malloc"}, | |
| {"free", "__asan_free"}, | |
| {"calloc", "__asan_calloc"}, | |
| {"__asan_dummy_calloc", "__asan_calloc"}, | |
| {"realloc", "__asan_realloc"}}; | |
| const StringSet FuncIgnoreList = {"_Balloc"}; | |
| llvm::NaClBitcodeRecord::RecordVector sizeToByteVec(SizeT Size) { | |
| llvm::NaClBitcodeRecord::RecordVector SizeContents; | |
| for (unsigned i = 0; i < sizeof(Size); ++i) { | |
| SizeContents.emplace_back(Size % (1 << CHAR_BIT)); | |
| Size >>= CHAR_BIT; | |
| } | |
| return SizeContents; | |
| } | |
| } // end of anonymous namespace | |
| ICE_TLS_DEFINE_FIELD(VarSizeMap *, ASanInstrumentation, LocalVars); | |
| ICE_TLS_DEFINE_FIELD(std::vector<InstStore *> *, ASanInstrumentation, | |
| LocalDtors); | |
| ICE_TLS_DEFINE_FIELD(CfgNode *, ASanInstrumentation, CurNode); | |
| ICE_TLS_DEFINE_FIELD(VarSizeMap *, ASanInstrumentation, CheckedVars); | |
| bool ASanInstrumentation::isInstrumentable(Cfg *Func) { | |
| std::string FuncName = Func->getFunctionName().toStringOrEmpty(); | |
| return FuncName == "" || (FuncIgnoreList.count(FuncName) == 0 && | |
| FuncName.find(ASanPrefix) != 0); | |
| } | |
| // Create redzones around all global variables, ensuring that the initializer | |
| // types of the redzones and their associated globals match so that they are | |
| // laid out together in memory. | |
| void ASanInstrumentation::instrumentGlobals(VariableDeclarationList &Globals) { | |
| std::unique_lock<std::mutex> _(GlobalsMutex); | |
| if (DidProcessGlobals) | |
| return; | |
| VariableDeclarationList NewGlobals; | |
| // Global holding pointers to all redzones | |
| auto *RzArray = VariableDeclaration::create(&NewGlobals); | |
| // Global holding sizes of all redzones | |
| auto *RzSizes = VariableDeclaration::create(&NewGlobals); | |
| RzArray->setName(Ctx, RzArrayName); | |
| RzSizes->setName(Ctx, RzSizesName); | |
| RzArray->setIsConstant(true); | |
| RzSizes->setIsConstant(true); | |
| NewGlobals.push_back(RzArray); | |
| NewGlobals.push_back(RzSizes); | |
| using PrototypeMap = std::unordered_map<std::string, FunctionDeclaration *>; | |
| PrototypeMap ProtoSubstitutions; | |
| for (VariableDeclaration *Global : Globals) { | |
| assert(Global->getAlignment() <= RzSize); | |
| VariableDeclaration *RzLeft = VariableDeclaration::create(&NewGlobals); | |
| VariableDeclaration *NewGlobal = Global; | |
| VariableDeclaration *RzRight = VariableDeclaration::create(&NewGlobals); | |
| RzLeft->setName(Ctx, nextRzName()); | |
| RzRight->setName(Ctx, nextRzName()); | |
| SizeT Alignment = std::max(RzSize, Global->getAlignment()); | |
| SizeT RzLeftSize = Alignment; | |
| SizeT RzRightSize = | |
| RzSize + Utils::OffsetToAlignment(Global->getNumBytes(), Alignment); | |
| if (!Global->hasNonzeroInitializer()) { | |
| RzLeft->addInitializer(VariableDeclaration::ZeroInitializer::create( | |
| &NewGlobals, RzLeftSize)); | |
| RzRight->addInitializer(VariableDeclaration::ZeroInitializer::create( | |
| &NewGlobals, RzRightSize)); | |
| } else { | |
| RzLeft->addInitializer(VariableDeclaration::DataInitializer::create( | |
| &NewGlobals, llvm::NaClBitcodeRecord::RecordVector(RzLeftSize, 'R'))); | |
| RzRight->addInitializer(VariableDeclaration::DataInitializer::create( | |
| &NewGlobals, | |
| llvm::NaClBitcodeRecord::RecordVector(RzRightSize, 'R'))); | |
| // replace any pointers to allocator functions | |
| NewGlobal = VariableDeclaration::create(&NewGlobals); | |
| NewGlobal->setName(Global->getName()); | |
| std::vector<VariableDeclaration::Initializer *> GlobalInits = | |
| Global->getInitializers(); | |
| for (VariableDeclaration::Initializer *Init : GlobalInits) { | |
| auto *RelocInit = | |
| llvm::dyn_cast<VariableDeclaration::RelocInitializer>(Init); | |
| if (RelocInit == nullptr) { | |
| NewGlobal->addInitializer(Init); | |
| continue; | |
| } | |
| const GlobalDeclaration *TargetDecl = RelocInit->getDeclaration(); | |
| const auto *TargetFunc = | |
| llvm::dyn_cast<FunctionDeclaration>(TargetDecl); | |
| if (TargetFunc == nullptr) { | |
| NewGlobal->addInitializer(Init); | |
| continue; | |
| } | |
| std::string TargetName = TargetDecl->getName().toStringOrEmpty(); | |
| StringMap::const_iterator Subst = FuncSubstitutions.find(TargetName); | |
| if (Subst == FuncSubstitutions.end()) { | |
| NewGlobal->addInitializer(Init); | |
| continue; | |
| } | |
| std::string SubstName = Subst->second; | |
| PrototypeMap::iterator SubstProtoEntry = | |
| ProtoSubstitutions.find(SubstName); | |
| FunctionDeclaration *SubstProto; | |
| if (SubstProtoEntry != ProtoSubstitutions.end()) | |
| SubstProto = SubstProtoEntry->second; | |
| else { | |
| constexpr bool IsProto = true; | |
| SubstProto = FunctionDeclaration::create( | |
| Ctx, TargetFunc->getSignature(), TargetFunc->getCallingConv(), | |
| llvm::GlobalValue::ExternalLinkage, IsProto); | |
| SubstProto->setName(Ctx, SubstName); | |
| ProtoSubstitutions.insert({SubstName, SubstProto}); | |
| } | |
| NewGlobal->addInitializer(VariableDeclaration::RelocInitializer::create( | |
| &NewGlobals, SubstProto, RelocOffsetArray(0))); | |
| } | |
| } | |
| RzLeft->setIsConstant(Global->getIsConstant()); | |
| NewGlobal->setIsConstant(Global->getIsConstant()); | |
| RzRight->setIsConstant(Global->getIsConstant()); | |
| RzLeft->setAlignment(Alignment); | |
| NewGlobal->setAlignment(Alignment); | |
| RzRight->setAlignment(1); | |
| RzArray->addInitializer(VariableDeclaration::RelocInitializer::create( | |
| &NewGlobals, RzLeft, RelocOffsetArray(0))); | |
| RzArray->addInitializer(VariableDeclaration::RelocInitializer::create( | |
| &NewGlobals, RzRight, RelocOffsetArray(0))); | |
| RzSizes->addInitializer(VariableDeclaration::DataInitializer::create( | |
| &NewGlobals, sizeToByteVec(RzLeftSize))); | |
| RzSizes->addInitializer(VariableDeclaration::DataInitializer::create( | |
| &NewGlobals, sizeToByteVec(RzRightSize))); | |
| NewGlobals.push_back(RzLeft); | |
| NewGlobals.push_back(NewGlobal); | |
| NewGlobals.push_back(RzRight); | |
| RzGlobalsNum += 2; | |
| GlobalSizes.insert({NewGlobal->getName(), NewGlobal->getNumBytes()}); | |
| } | |
| // Replace old list of globals, without messing up arena allocators | |
| Globals.clear(); | |
| Globals.merge(&NewGlobals); | |
| DidProcessGlobals = true; | |
| // Log the new set of globals | |
| if (BuildDefs::dump() && (getFlags().getVerbose() & IceV_GlobalInit)) { | |
| OstreamLocker _(Ctx); | |
| Ctx->getStrDump() << "========= Instrumented Globals =========\n"; | |
| for (VariableDeclaration *Global : Globals) { | |
| Global->dump(Ctx->getStrDump()); | |
| } | |
| } | |
| } | |
| std::string ASanInstrumentation::nextRzName() { | |
| std::stringstream Name; | |
| Name << RzPrefix << RzNum++; | |
| return Name.str(); | |
| } | |
| // Check for an alloca signaling the presence of local variables and add a | |
| // redzone if it is found | |
| void ASanInstrumentation::instrumentFuncStart(LoweringContext &Context) { | |
| if (ICE_TLS_GET_FIELD(LocalDtors) == nullptr) { | |
| ICE_TLS_SET_FIELD(LocalDtors, new std::vector<InstStore *>()); | |
| ICE_TLS_SET_FIELD(LocalVars, new VarSizeMap()); | |
| } | |
| Cfg *Func = Context.getNode()->getCfg(); | |
| using Entry = std::pair<SizeT, int32_t>; | |
| std::vector<InstAlloca *> NewAllocas; | |
| std::vector<Entry> PoisonVals; | |
| Variable *FirstShadowLocVar; | |
| InstArithmetic *ShadowIndexCalc; | |
| InstArithmetic *ShadowLocCalc; | |
| InstAlloca *Cur; | |
| ConstantInteger32 *VarSizeOp; | |
| while (!Context.atEnd()) { | |
| Cur = llvm::dyn_cast<InstAlloca>(iteratorToInst(Context.getCur())); | |
| VarSizeOp = (Cur == nullptr) | |
| ? nullptr | |
| : llvm::dyn_cast<ConstantInteger32>(Cur->getSizeInBytes()); | |
| if (Cur == nullptr || VarSizeOp == nullptr) { | |
| Context.advanceCur(); | |
| Context.advanceNext(); | |
| continue; | |
| } | |
| Cur->setDeleted(); | |
| if (PoisonVals.empty()) { | |
| // insert leftmost redzone | |
| auto *LastRzVar = Func->makeVariable(IceType_i32); | |
| LastRzVar->setName(Func, nextRzName()); | |
| auto *ByteCount = ConstantInteger32::create(Ctx, IceType_i32, RzSize); | |
| constexpr SizeT Alignment = 8; | |
| NewAllocas.emplace_back( | |
| InstAlloca::create(Func, LastRzVar, ByteCount, Alignment)); | |
| PoisonVals.emplace_back(Entry{RzSize >> ShadowScaleLog2, StackPoisonVal}); | |
| // Calculate starting address for poisoning | |
| FirstShadowLocVar = Func->makeVariable(IceType_i32); | |
| FirstShadowLocVar->setName(Func, "firstShadowLoc"); | |
| auto *ShadowIndexVar = Func->makeVariable(IceType_i32); | |
| ShadowIndexVar->setName(Func, "shadowIndex"); | |
| auto *ShadowScaleLog2Const = | |
| ConstantInteger32::create(Ctx, IceType_i32, ShadowScaleLog2); | |
| auto *ShadowMemLocConst = | |
| ConstantInteger32::create(Ctx, IceType_i32, ShadowLength32); | |
| ShadowIndexCalc = | |
| InstArithmetic::create(Func, InstArithmetic::Lshr, ShadowIndexVar, | |
| LastRzVar, ShadowScaleLog2Const); | |
| ShadowLocCalc = | |
| InstArithmetic::create(Func, InstArithmetic::Add, FirstShadowLocVar, | |
| ShadowIndexVar, ShadowMemLocConst); | |
| } | |
| // create the new alloca that includes a redzone | |
| SizeT VarSize = VarSizeOp->getValue(); | |
| Variable *Dest = Cur->getDest(); | |
| ICE_TLS_GET_FIELD(LocalVars)->insert({Dest, VarSize}); | |
| SizeT RzPadding = RzSize + Utils::OffsetToAlignment(VarSize, RzSize); | |
| auto *ByteCount = | |
| ConstantInteger32::create(Ctx, IceType_i32, VarSize + RzPadding); | |
| constexpr SizeT Alignment = 8; | |
| NewAllocas.emplace_back( | |
| InstAlloca::create(Func, Dest, ByteCount, Alignment)); | |
| const SizeT Zeros = VarSize >> ShadowScaleLog2; | |
| const SizeT Offset = VarSize % ShadowScale; | |
| const SizeT PoisonBytes = | |
| ((VarSize + RzPadding) >> ShadowScaleLog2) - Zeros - 1; | |
| if (Zeros > 0) | |
| PoisonVals.emplace_back(Entry{Zeros, 0}); | |
| PoisonVals.emplace_back(Entry{1, (Offset == 0) ? StackPoisonVal : Offset}); | |
| PoisonVals.emplace_back(Entry{PoisonBytes, StackPoisonVal}); | |
| Context.advanceCur(); | |
| Context.advanceNext(); | |
| } | |
| Context.rewind(); | |
| if (PoisonVals.empty()) { | |
| Context.advanceNext(); | |
| return; | |
| } | |
| for (InstAlloca *RzAlloca : NewAllocas) { | |
| Context.insert(RzAlloca); | |
| } | |
| Context.insert(ShadowIndexCalc); | |
| Context.insert(ShadowLocCalc); | |
| // Poison redzones | |
| std::vector<Entry>::iterator Iter = PoisonVals.begin(); | |
| for (SizeT Offset = 0; Iter != PoisonVals.end(); Offset += BytesPerWord) { | |
| int32_t CurVals[BytesPerWord] = {0}; | |
| for (uint32_t i = 0; i < BytesPerWord; ++i) { | |
| if (Iter == PoisonVals.end()) | |
| break; | |
| Entry Val = *Iter; | |
| CurVals[i] = Val.second; | |
| --Val.first; | |
| if (Val.first > 0) | |
| *Iter = Val; | |
| else | |
| ++Iter; | |
| } | |
| int32_t Poison = ((CurVals[3] & 0xff) << 24) | ((CurVals[2] & 0xff) << 16) | | |
| ((CurVals[1] & 0xff) << 8) | (CurVals[0] & 0xff); | |
| if (Poison == 0) | |
| continue; | |
| auto *PoisonConst = ConstantInteger32::create(Ctx, IceType_i32, Poison); | |
| auto *ZeroConst = ConstantInteger32::create(Ctx, IceType_i32, 0); | |
| auto *OffsetConst = ConstantInteger32::create(Ctx, IceType_i32, Offset); | |
| auto *PoisonAddrVar = Func->makeVariable(IceType_i32); | |
| Context.insert(InstArithmetic::create(Func, InstArithmetic::Add, | |
| PoisonAddrVar, FirstShadowLocVar, | |
| OffsetConst)); | |
| Context.insert(InstStore::create(Func, PoisonConst, PoisonAddrVar)); | |
| ICE_TLS_GET_FIELD(LocalDtors) | |
| ->emplace_back(InstStore::create(Func, ZeroConst, PoisonAddrVar)); | |
| } | |
| Context.advanceNext(); | |
| } | |
| void ASanInstrumentation::instrumentCall(LoweringContext &Context, | |
| InstCall *Instr) { | |
| auto *CallTarget = | |
| llvm::dyn_cast<ConstantRelocatable>(Instr->getCallTarget()); | |
| if (CallTarget == nullptr) | |
| return; | |
| std::string TargetName = CallTarget->getName().toStringOrEmpty(); | |
| auto Subst = FuncSubstitutions.find(TargetName); | |
| if (Subst == FuncSubstitutions.end()) | |
| return; | |
| std::string SubName = Subst->second; | |
| Constant *NewFunc = Ctx->getConstantExternSym(Ctx->getGlobalString(SubName)); | |
| auto *NewCall = | |
| InstCall::create(Context.getNode()->getCfg(), Instr->getNumArgs(), | |
| Instr->getDest(), NewFunc, Instr->isTailcall()); | |
| for (SizeT I = 0, Args = Instr->getNumArgs(); I < Args; ++I) | |
| NewCall->addArg(Instr->getArg(I)); | |
| Context.insert(NewCall); | |
| Instr->setDeleted(); | |
| } | |
| void ASanInstrumentation::instrumentLoad(LoweringContext &Context, | |
| InstLoad *Instr) { | |
| Operand *Src = Instr->getLoadAddress(); | |
| if (auto *Reloc = llvm::dyn_cast<ConstantRelocatable>(Src)) { | |
| auto *NewLoad = InstLoad::create(Context.getNode()->getCfg(), | |
| Instr->getDest(), instrumentReloc(Reloc)); | |
| Instr->setDeleted(); | |
| Context.insert(NewLoad); | |
| Instr = NewLoad; | |
| } | |
| Constant *Func = | |
| Ctx->getConstantExternSym(Ctx->getGlobalString("__asan_check_load")); | |
| instrumentAccess(Context, Instr->getLoadAddress(), | |
| typeWidthInBytes(Instr->getDest()->getType()), Func); | |
| } | |
| void ASanInstrumentation::instrumentStore(LoweringContext &Context, | |
| InstStore *Instr) { | |
| Operand *Data = Instr->getData(); | |
| if (auto *Reloc = llvm::dyn_cast<ConstantRelocatable>(Data)) { | |
| auto *NewStore = | |
| InstStore::create(Context.getNode()->getCfg(), instrumentReloc(Reloc), | |
| Instr->getStoreAddress()); | |
| Instr->setDeleted(); | |
| Context.insert(NewStore); | |
| Instr = NewStore; | |
| } | |
| Constant *Func = | |
| Ctx->getConstantExternSym(Ctx->getGlobalString("__asan_check_store")); | |
| instrumentAccess(Context, Instr->getStoreAddress(), | |
| typeWidthInBytes(Instr->getData()->getType()), Func); | |
| } | |
| ConstantRelocatable * | |
| ASanInstrumentation::instrumentReloc(ConstantRelocatable *Reloc) { | |
| std::string DataName = Reloc->getName().toString(); | |
| StringMap::const_iterator DataSub = FuncSubstitutions.find(DataName); | |
| if (DataSub != FuncSubstitutions.end()) { | |
| return ConstantRelocatable::create( | |
| Ctx, Reloc->getType(), | |
| RelocatableTuple(Reloc->getOffset(), RelocOffsetArray(0), | |
| Ctx->getGlobalString(DataSub->second), | |
| Reloc->getEmitString())); | |
| } | |
| return Reloc; | |
| } | |
| void ASanInstrumentation::instrumentAccess(LoweringContext &Context, | |
| Operand *Op, SizeT Size, | |
| Constant *CheckFunc) { | |
| // Skip redundant checks within basic blocks | |
| VarSizeMap *Checked = ICE_TLS_GET_FIELD(CheckedVars); | |
| if (ICE_TLS_GET_FIELD(CurNode) != Context.getNode()) { | |
| ICE_TLS_SET_FIELD(CurNode, Context.getNode()); | |
| if (Checked == NULL) { | |
| Checked = new VarSizeMap(); | |
| ICE_TLS_SET_FIELD(CheckedVars, Checked); | |
| } | |
| Checked->clear(); | |
| } | |
| VarSizeMap::iterator PrevCheck = Checked->find(Op); | |
| if (PrevCheck != Checked->end() && PrevCheck->second >= Size) | |
| return; | |
| else | |
| Checked->insert({Op, Size}); | |
| // check for known good local access | |
| VarSizeMap::iterator LocalSize = ICE_TLS_GET_FIELD(LocalVars)->find(Op); | |
| if (LocalSize != ICE_TLS_GET_FIELD(LocalVars)->end() && | |
| LocalSize->second >= Size) | |
| return; | |
| if (isOkGlobalAccess(Op, Size)) | |
| return; | |
| constexpr SizeT NumArgs = 2; | |
| constexpr Variable *Void = nullptr; | |
| constexpr bool NoTailCall = false; | |
| auto *Call = InstCall::create(Context.getNode()->getCfg(), NumArgs, Void, | |
| CheckFunc, NoTailCall); | |
| Call->addArg(Op); | |
| Call->addArg(ConstantInteger32::create(Ctx, IceType_i32, Size)); | |
| // play games to insert the call before the access instruction | |
| InstList::iterator Next = Context.getNext(); | |
| Context.setInsertPoint(Context.getCur()); | |
| Context.insert(Call); | |
| Context.setNext(Next); | |
| } | |
| // TODO(tlively): Trace back load and store addresses to find their real offsets | |
| bool ASanInstrumentation::isOkGlobalAccess(Operand *Op, SizeT Size) { | |
| auto *Reloc = llvm::dyn_cast<ConstantRelocatable>(Op); | |
| if (Reloc == nullptr) | |
| return false; | |
| RelocOffsetT Offset = Reloc->getOffset(); | |
| GlobalSizeMap::iterator GlobalSize = GlobalSizes.find(Reloc->getName()); | |
| return GlobalSize != GlobalSizes.end() && GlobalSize->second - Offset >= Size; | |
| } | |
| void ASanInstrumentation::instrumentRet(LoweringContext &Context, InstRet *) { | |
| Cfg *Func = Context.getNode()->getCfg(); | |
| Context.setInsertPoint(Context.getCur()); | |
| for (InstStore *RzUnpoison : *ICE_TLS_GET_FIELD(LocalDtors)) { | |
| Context.insert(InstStore::create(Func, RzUnpoison->getData(), | |
| RzUnpoison->getStoreAddress())); | |
| } | |
| Context.advanceCur(); | |
| Context.advanceNext(); | |
| } | |
| void ASanInstrumentation::instrumentStart(Cfg *Func) { | |
| Constant *ShadowMemInit = | |
| Ctx->getConstantExternSym(Ctx->getGlobalString("__asan_init")); | |
| constexpr SizeT NumArgs = 3; | |
| constexpr Variable *Void = nullptr; | |
| constexpr bool NoTailCall = false; | |
| auto *Call = InstCall::create(Func, NumArgs, Void, ShadowMemInit, NoTailCall); | |
| Func->getEntryNode()->getInsts().push_front(Call); | |
| instrumentGlobals(*getGlobals()); | |
| Call->addArg(ConstantInteger32::create(Ctx, IceType_i32, RzGlobalsNum)); | |
| Call->addArg(Ctx->getConstantSym(0, Ctx->getGlobalString(RzArrayName))); | |
| Call->addArg(Ctx->getConstantSym(0, Ctx->getGlobalString(RzSizesName))); | |
| } | |
| // TODO(tlively): make this more efficient with swap idiom | |
| void ASanInstrumentation::finishFunc(Cfg *) { | |
| ICE_TLS_GET_FIELD(LocalVars)->clear(); | |
| ICE_TLS_GET_FIELD(LocalDtors)->clear(); | |
| } | |
| } // end of namespace Ice |