//===- bolt/Passes/RetpolineInsertion.cpp ---------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // // This file implements RetpolineInsertion class, which replaces indirect // branches (calls and jumps) with calls to retpolines to protect against branch // target injection attacks. // A unique retpoline is created for each register holding the address of the // callee, if the callee address is in memory %r11 is used if available to // hold the address of the callee before calling the retpoline, otherwise an // address pattern specific retpoline is called where the callee address is // loaded inside the retpoline. // The user can determine when to assume %r11 available using r11-availability // option, by default %r11 is assumed not available. // Adding lfence instruction to the body of the speculate code is enabled by // default and can be controlled by the user using retpoline-lfence option. // //===----------------------------------------------------------------------===// #include "bolt/Passes/RetpolineInsertion.h" #include "llvm/MC/MCInstPrinter.h" #include "llvm/Support/raw_ostream.h" #define DEBUG_TYPE "bolt-retpoline" using namespace llvm; using namespace bolt; namespace opts { extern cl::OptionCategory BoltCategory; llvm::cl::opt InsertRetpolines("insert-retpolines", cl::desc("run retpoline insertion pass"), cl::cat(BoltCategory)); llvm::cl::opt RetpolineLfence("retpoline-lfence", cl::desc("determine if lfence instruction should exist in the retpoline"), cl::init(true), cl::ZeroOrMore, cl::Hidden, cl::cat(BoltCategory)); cl::opt R11Availability( "r11-availability", cl::desc("determine the availability of r11 before indirect branches"), cl::init(RetpolineInsertion::AvailabilityOptions::NEVER), cl::values(clEnumValN(RetpolineInsertion::AvailabilityOptions::NEVER, "never", "r11 not available"), clEnumValN(RetpolineInsertion::AvailabilityOptions::ALWAYS, "always", "r11 available before calls and jumps"), clEnumValN(RetpolineInsertion::AvailabilityOptions::ABI, "abi", "r11 available before calls but not before jumps")), cl::ZeroOrMore, cl::cat(BoltCategory)); } // namespace opts namespace llvm { namespace bolt { // Retpoline function structure: // BB0: call BB2 // BB1: pause // lfence // jmp BB1 // BB2: mov %reg, (%rsp) // ret // or // BB2: push %r11 // mov Address, %r11 // mov %r11, 8(%rsp) // pop %r11 // ret BinaryFunction *createNewRetpoline(BinaryContext &BC, const std::string &RetpolineTag, const IndirectBranchInfo &BrInfo, bool R11Available) { auto &MIB = *BC.MIB; MCContext &Ctx = *BC.Ctx.get(); LLVM_DEBUG(dbgs() << "BOLT-DEBUG: Creating a new retpoline function[" << RetpolineTag << "]\n"); BinaryFunction *NewRetpoline = BC.createInjectedBinaryFunction(RetpolineTag, true); std::vector> NewBlocks(3); for (int I = 0; I < 3; I++) { MCSymbol *Symbol = Ctx.createNamedTempSymbol(Twine(RetpolineTag + "_BB" + to_string(I))); NewBlocks[I] = NewRetpoline->createBasicBlock(Symbol); NewBlocks[I].get()->setCFIState(0); } BinaryBasicBlock &BB0 = *NewBlocks[0].get(); BinaryBasicBlock &BB1 = *NewBlocks[1].get(); BinaryBasicBlock &BB2 = *NewBlocks[2].get(); BB0.addSuccessor(&BB2, 0, 0); BB1.addSuccessor(&BB1, 0, 0); // Build BB0 MCInst DirectCall; MIB.createDirectCall(DirectCall, BB2.getLabel(), &Ctx, /*IsTailCall*/ false); BB0.addInstruction(DirectCall); // Build BB1 MCInst Pause; MIB.createPause(Pause); BB1.addInstruction(Pause); if (opts::RetpolineLfence) { MCInst Lfence; MIB.createLfence(Lfence); BB1.addInstruction(Lfence); } InstructionListType Seq; MIB.createShortJmp(Seq, BB1.getLabel(), &Ctx); BB1.addInstructions(Seq.begin(), Seq.end()); // Build BB2 if (BrInfo.isMem()) { if (R11Available) { MCInst StoreToStack; MIB.createSaveToStack(StoreToStack, MIB.getStackPointer(), 0, MIB.getX86R11(), 8); BB2.addInstruction(StoreToStack); } else { MCInst PushR11; MIB.createPushRegister(PushR11, MIB.getX86R11(), 8); BB2.addInstruction(PushR11); MCInst LoadCalleeAddrs; const IndirectBranchInfo::MemOpInfo &MemRef = BrInfo.Memory; MIB.createLoad(LoadCalleeAddrs, MemRef.BaseRegNum, MemRef.ScaleImm, MemRef.IndexRegNum, MemRef.DispImm, MemRef.DispExpr, MemRef.SegRegNum, MIB.getX86R11(), 8); BB2.addInstruction(LoadCalleeAddrs); MCInst StoreToStack; MIB.createSaveToStack(StoreToStack, MIB.getStackPointer(), 8, MIB.getX86R11(), 8); BB2.addInstruction(StoreToStack); MCInst PopR11; MIB.createPopRegister(PopR11, MIB.getX86R11(), 8); BB2.addInstruction(PopR11); } } else if (BrInfo.isReg()) { MCInst StoreToStack; MIB.createSaveToStack(StoreToStack, MIB.getStackPointer(), 0, BrInfo.BranchReg, 8); BB2.addInstruction(StoreToStack); } else { llvm_unreachable("not expected"); } // return MCInst Return; MIB.createReturn(Return); BB2.addInstruction(Return); NewRetpoline->insertBasicBlocks(nullptr, std::move(NewBlocks), /* UpdateLayout */ true, /* UpdateCFIState */ false); NewRetpoline->updateState(BinaryFunction::State::CFG_Finalized); return NewRetpoline; } std::string createRetpolineFunctionTag(BinaryContext &BC, const IndirectBranchInfo &BrInfo, bool R11Available) { std::string Tag; llvm::raw_string_ostream TagOS(Tag); TagOS << "__retpoline_"; if (BrInfo.isReg()) { BC.InstPrinter->printRegName(TagOS, BrInfo.BranchReg); TagOS << "_"; return Tag; } // Memory Branch if (R11Available) return "__retpoline_r11"; const IndirectBranchInfo::MemOpInfo &MemRef = BrInfo.Memory; TagOS << "mem_"; if (MemRef.BaseRegNum != BC.MIB->getNoRegister()) BC.InstPrinter->printRegName(TagOS, MemRef.BaseRegNum); TagOS << "+"; if (MemRef.DispExpr) MemRef.DispExpr->print(TagOS, BC.AsmInfo.get()); else TagOS << MemRef.DispImm; if (MemRef.IndexRegNum != BC.MIB->getNoRegister()) { TagOS << "+" << MemRef.ScaleImm << "*"; BC.InstPrinter->printRegName(TagOS, MemRef.IndexRegNum); } if (MemRef.SegRegNum != BC.MIB->getNoRegister()) { TagOS << "_seg_"; BC.InstPrinter->printRegName(TagOS, MemRef.SegRegNum); } return Tag; } BinaryFunction *RetpolineInsertion::getOrCreateRetpoline( BinaryContext &BC, const IndirectBranchInfo &BrInfo, bool R11Available) { const std::string RetpolineTag = createRetpolineFunctionTag(BC, BrInfo, R11Available); if (CreatedRetpolines.count(RetpolineTag)) return CreatedRetpolines[RetpolineTag]; return CreatedRetpolines[RetpolineTag] = createNewRetpoline(BC, RetpolineTag, BrInfo, R11Available); } void createBranchReplacement(BinaryContext &BC, const IndirectBranchInfo &BrInfo, bool R11Available, InstructionListType &Replacement, const MCSymbol *RetpolineSymbol) { auto &MIB = *BC.MIB; // Load the branch address in r11 if available if (BrInfo.isMem() && R11Available) { const IndirectBranchInfo::MemOpInfo &MemRef = BrInfo.Memory; MCInst LoadCalleeAddrs; MIB.createLoad(LoadCalleeAddrs, MemRef.BaseRegNum, MemRef.ScaleImm, MemRef.IndexRegNum, MemRef.DispImm, MemRef.DispExpr, MemRef.SegRegNum, MIB.getX86R11(), 8); Replacement.push_back(LoadCalleeAddrs); } // Call the retpoline MCInst RetpolineCall; MIB.createDirectCall(RetpolineCall, RetpolineSymbol, BC.Ctx.get(), BrInfo.isJump() || BrInfo.isTailCall()); Replacement.push_back(RetpolineCall); } IndirectBranchInfo::IndirectBranchInfo(MCInst &Inst, MCPlusBuilder &MIB) { IsCall = MIB.isCall(Inst); IsTailCall = MIB.isTailCall(Inst); if (MIB.isBranchOnMem(Inst)) { IsMem = true; std::optional MO = MIB.evaluateX86MemoryOperand(Inst); if (!MO) llvm_unreachable("not expected"); Memory = MO.value(); } else if (MIB.isBranchOnReg(Inst)) { assert(MCPlus::getNumPrimeOperands(Inst) == 1 && "expect 1 operand"); BranchReg = Inst.getOperand(0).getReg(); } else { llvm_unreachable("unexpected instruction"); } } Error RetpolineInsertion::runOnFunctions(BinaryContext &BC) { if (!opts::InsertRetpolines) return Error::success(); assert(BC.isX86() && "retpoline insertion not supported for target architecture"); assert(BC.HasRelocations && "retpoline mode not supported in non-reloc"); auto &MIB = *BC.MIB; uint32_t RetpolinedBranches = 0; for (auto &It : BC.getBinaryFunctions()) { BinaryFunction &Function = It.second; for (BinaryBasicBlock &BB : Function) { for (auto It = BB.begin(); It != BB.end(); ++It) { MCInst &Inst = *It; if (!MIB.isIndirectCall(Inst) && !MIB.isIndirectBranch(Inst)) continue; IndirectBranchInfo BrInfo(Inst, MIB); bool R11Available = false; BinaryFunction *TargetRetpoline; InstructionListType Replacement; // Determine if r11 is available before this instruction if (BrInfo.isMem()) { if (MIB.hasAnnotation(Inst, "PLTCall")) R11Available = true; else if (opts::R11Availability == AvailabilityOptions::ALWAYS) R11Available = true; else if (opts::R11Availability == AvailabilityOptions::ABI) R11Available = BrInfo.isCall(); } // If the instruction addressing pattern uses rsp and the retpoline // loads the callee address then displacement needs to be updated if (BrInfo.isMem() && !R11Available) { IndirectBranchInfo::MemOpInfo &MemRef = BrInfo.Memory; int Addend = (BrInfo.isJump() || BrInfo.isTailCall()) ? 8 : 16; if (MemRef.BaseRegNum == MIB.getStackPointer()) MemRef.DispImm += Addend; if (MemRef.IndexRegNum == MIB.getStackPointer()) MemRef.DispImm += Addend * MemRef.ScaleImm; } TargetRetpoline = getOrCreateRetpoline(BC, BrInfo, R11Available); createBranchReplacement(BC, BrInfo, R11Available, Replacement, TargetRetpoline->getSymbol()); It = BB.replaceInstruction(It, Replacement.begin(), Replacement.end()); RetpolinedBranches++; } } } BC.outs() << "BOLT-INFO: The number of created retpoline functions is : " << CreatedRetpolines.size() << "\nBOLT-INFO: The number of retpolined branches is : " << RetpolinedBranches << "\n"; return Error::success(); } } // namespace bolt } // namespace llvm