| //===- 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<bool> InsertRetpolines("insert-retpolines", | |
| cl::desc("run retpoline insertion pass"), | |
| cl::cat(BoltCategory)); | |
| llvm::cl::opt<bool> | |
| 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<RetpolineInsertion::AvailabilityOptions> 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<std::unique_ptr<BinaryBasicBlock>> 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 << "_"; | |
| TagOS.flush(); | |
| 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); | |
| } | |
| TagOS.flush(); | |
| 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<MCPlusBuilder::X86MemOperand> 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 |