LLVM 20.0.0git
ARMSLSHardening.cpp
Go to the documentation of this file.
1//===- ARMSLSHardening.cpp - Harden Straight Line Missspeculation ---------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// This file contains a pass to insert code to mitigate against side channel
10// vulnerabilities that may happen under straight line miss-speculation.
11//
12//===----------------------------------------------------------------------===//
13
14#include "ARM.h"
15#include "ARMInstrInfo.h"
16#include "ARMSubtarget.h"
24#include "llvm/IR/DebugLoc.h"
25#include <cassert>
26
27using namespace llvm;
28
29#define DEBUG_TYPE "arm-sls-hardening"
30
31#define ARM_SLS_HARDENING_NAME "ARM sls hardening pass"
32
33namespace {
34
35class ARMSLSHardening : public MachineFunctionPass {
36public:
37 const TargetInstrInfo *TII;
38 const ARMSubtarget *ST;
39
40 static char ID;
41
42 ARMSLSHardening() : MachineFunctionPass(ID) {
44 }
45
46 bool runOnMachineFunction(MachineFunction &Fn) override;
47
48 StringRef getPassName() const override { return ARM_SLS_HARDENING_NAME; }
49
50 void getAnalysisUsage(AnalysisUsage &AU) const override {
51 AU.setPreservesCFG();
53 }
54
55private:
56 bool hardenReturnsAndBRs(MachineBasicBlock &MBB) const;
57 bool hardenIndirectCalls(MachineBasicBlock &MBB) const;
59 ConvertIndirectCallToIndirectJump(MachineBasicBlock &MBB,
61};
62
63} // end anonymous namespace
64
65char ARMSLSHardening::ID = 0;
66
67INITIALIZE_PASS(ARMSLSHardening, "arm-sls-hardening",
68 ARM_SLS_HARDENING_NAME, false, false)
69
75 assert(MBBI != MBB.begin() &&
76 "Must not insert SpeculationBarrierEndBB as only instruction in MBB.");
77 assert(std::prev(MBBI)->isBarrier() &&
78 "SpeculationBarrierEndBB must only follow unconditional control flow "
79 "instructions.");
80 assert(std::prev(MBBI)->isTerminator() &&
81 "SpeculationBarrierEndBB must only follow terminators.");
82 const TargetInstrInfo *TII = ST->getInstrInfo();
83 assert(ST->hasDataBarrier() || ST->hasSB());
84 bool ProduceSB = ST->hasSB() && !AlwaysUseISBDSB;
85 unsigned BarrierOpc =
86 ProduceSB ? (ST->isThumb() ? ARM::t2SpeculationBarrierSBEndBB
87 : ARM::SpeculationBarrierSBEndBB)
88 : (ST->isThumb() ? ARM::t2SpeculationBarrierISBDSBEndBB
89 : ARM::SpeculationBarrierISBDSBEndBB);
90 if (MBBI == MBB.end() || !isSpeculationBarrierEndBBOpcode(MBBI->getOpcode()))
91 BuildMI(MBB, MBBI, DL, TII->get(BarrierOpc));
92}
93
94bool ARMSLSHardening::runOnMachineFunction(MachineFunction &MF) {
97
98 bool Modified = false;
99 for (auto &MBB : MF) {
100 Modified |= hardenReturnsAndBRs(MBB);
101 Modified |= hardenIndirectCalls(MBB);
102 }
103
104 return Modified;
105}
106
107bool ARMSLSHardening::hardenReturnsAndBRs(MachineBasicBlock &MBB) const {
108 if (!ST->hardenSlsRetBr())
109 return false;
110 assert(!ST->isThumb1Only());
111 bool Modified = false;
114 for (; MBBI != E; MBBI = NextMBBI) {
115 MachineInstr &MI = *MBBI;
116 NextMBBI = std::next(MBBI);
118 assert(MI.isTerminator());
120 insertSpeculationBarrier(ST, MBB, std::next(MBBI), MI.getDebugLoc());
121 Modified = true;
122 }
123 }
124 return Modified;
125}
126
127static const char SLSBLRNamePrefix[] = "__llvm_slsblr_thunk_";
128
129static const struct ThunkNameRegMode {
130 const char* Name;
133} SLSBLRThunks[] = {
134 {"__llvm_slsblr_thunk_arm_r0", ARM::R0, false},
135 {"__llvm_slsblr_thunk_arm_r1", ARM::R1, false},
136 {"__llvm_slsblr_thunk_arm_r2", ARM::R2, false},
137 {"__llvm_slsblr_thunk_arm_r3", ARM::R3, false},
138 {"__llvm_slsblr_thunk_arm_r4", ARM::R4, false},
139 {"__llvm_slsblr_thunk_arm_r5", ARM::R5, false},
140 {"__llvm_slsblr_thunk_arm_r6", ARM::R6, false},
141 {"__llvm_slsblr_thunk_arm_r7", ARM::R7, false},
142 {"__llvm_slsblr_thunk_arm_r8", ARM::R8, false},
143 {"__llvm_slsblr_thunk_arm_r9", ARM::R9, false},
144 {"__llvm_slsblr_thunk_arm_r10", ARM::R10, false},
145 {"__llvm_slsblr_thunk_arm_r11", ARM::R11, false},
146 {"__llvm_slsblr_thunk_arm_sp", ARM::SP, false},
147 {"__llvm_slsblr_thunk_arm_pc", ARM::PC, false},
148 {"__llvm_slsblr_thunk_thumb_r0", ARM::R0, true},
149 {"__llvm_slsblr_thunk_thumb_r1", ARM::R1, true},
150 {"__llvm_slsblr_thunk_thumb_r2", ARM::R2, true},
151 {"__llvm_slsblr_thunk_thumb_r3", ARM::R3, true},
152 {"__llvm_slsblr_thunk_thumb_r4", ARM::R4, true},
153 {"__llvm_slsblr_thunk_thumb_r5", ARM::R5, true},
154 {"__llvm_slsblr_thunk_thumb_r6", ARM::R6, true},
155 {"__llvm_slsblr_thunk_thumb_r7", ARM::R7, true},
156 {"__llvm_slsblr_thunk_thumb_r8", ARM::R8, true},
157 {"__llvm_slsblr_thunk_thumb_r9", ARM::R9, true},
158 {"__llvm_slsblr_thunk_thumb_r10", ARM::R10, true},
159 {"__llvm_slsblr_thunk_thumb_r11", ARM::R11, true},
160 {"__llvm_slsblr_thunk_thumb_sp", ARM::SP, true},
161 {"__llvm_slsblr_thunk_thumb_pc", ARM::PC, true},
163
164// An enum for tracking whether Arm and Thumb thunks have been inserted into the
165// current module so far.
167
170 return X = static_cast<ArmInsertedThunks>(X | Y);
171}
172
173namespace {
174struct SLSBLRThunkInserter
175 : ThunkInserter<SLSBLRThunkInserter, ArmInsertedThunks> {
176 const char *getThunkPrefix() { return SLSBLRNamePrefix; }
177 bool mayUseThunk(const MachineFunction &MF) {
178 ComdatThunks &= !MF.getSubtarget<ARMSubtarget>().hardenSlsNoComdat();
179 return MF.getSubtarget<ARMSubtarget>().hardenSlsBlr();
180 }
182 ArmInsertedThunks InsertedThunks);
184
185private:
186 bool ComdatThunks = true;
187};
188} // namespace
189
191SLSBLRThunkInserter::insertThunks(MachineModuleInfo &MMI, MachineFunction &MF,
192 ArmInsertedThunks InsertedThunks) {
193 if ((InsertedThunks & ArmThunk &&
194 !MF.getSubtarget<ARMSubtarget>().isThumb()) ||
195 (InsertedThunks & ThumbThunk &&
196 MF.getSubtarget<ARMSubtarget>().isThumb()))
197 return NoThunk;
198 // FIXME: It probably would be possible to filter which thunks to produce
199 // based on which registers are actually used in indirect calls in this
200 // function. But would that be a worthwhile optimization?
202 for (auto T : SLSBLRThunks)
203 if (ST->isThumb() == T.isThumb)
204 createThunkFunction(MMI, T.Name, ComdatThunks,
205 T.isThumb ? "+thumb-mode" : "");
206 return ST->isThumb() ? ThumbThunk : ArmThunk;
207}
208
209void SLSBLRThunkInserter::populateThunk(MachineFunction &MF) {
210 assert(MF.getFunction().hasComdat() == ComdatThunks &&
211 "ComdatThunks value changed since MF creation");
212 // FIXME: How to better communicate Register number, rather than through
213 // name and lookup table?
214 assert(MF.getName().starts_with(getThunkPrefix()));
215 auto ThunkIt = llvm::find_if(
216 SLSBLRThunks, [&MF](auto T) { return T.Name == MF.getName(); });
217 assert(ThunkIt != std::end(SLSBLRThunks));
218 Register ThunkReg = ThunkIt->Reg;
219 bool isThumb = ThunkIt->isThumb;
220
221 const TargetInstrInfo *TII = MF.getSubtarget<ARMSubtarget>().getInstrInfo();
223 Entry->clear();
224
225 // These thunks need to consist of the following instructions:
226 // __llvm_slsblr_thunk_(arm/thumb)_rN:
227 // bx rN
228 // barrierInsts
229 Entry->addLiveIn(ThunkReg);
230 if (isThumb)
231 BuildMI(Entry, DebugLoc(), TII->get(ARM::tBX))
232 .addReg(ThunkReg)
234 else
235 BuildMI(Entry, DebugLoc(), TII->get(ARM::BX))
236 .addReg(ThunkReg);
237
238 // Make sure the thunks do not make use of the SB extension in case there is
239 // a function somewhere that will call to it that for some reason disabled
240 // the SB extension locally on that function, even though it's enabled for
241 // the module otherwise. Therefore set AlwaysUseISBSDB to true.
243 Entry->end(), DebugLoc(), true /*AlwaysUseISBDSB*/);
244}
245
246MachineBasicBlock &ARMSLSHardening::ConvertIndirectCallToIndirectJump(
248 // Transform an indirect call to an indirect jump as follows:
249 // Before:
250 // |-----------------------------|
251 // | ... |
252 // | instI |
253 // | BLX rN |
254 // | instJ |
255 // | ... |
256 // |-----------------------------|
257 //
258 // After:
259 // |---------- -------------------------|
260 // | ... |
261 // | instI |
262 // | *call* __llvm_slsblr_thunk_mode_xN |
263 // | instJ |
264 // | ... |
265 // |--------------------------------------|
266 //
267 // __llvm_slsblr_thunk_mode_xN:
268 // |-----------------------------|
269 // | BX rN |
270 // | barrierInsts |
271 // |-----------------------------|
272 //
273 // The __llvm_slsblr_thunk_mode_xN thunks are created by the
274 // SLSBLRThunkInserter.
275 // This function merely needs to transform an indirect call to a direct call
276 // to __llvm_slsblr_thunk_xN.
279 int RegOpIdxOnIndirectCall = -1;
280 bool isThumb;
281 switch (IndirectCall.getOpcode()) {
282 case ARM::BLX: // !isThumb2
283 case ARM::BLX_noip: // !isThumb2
284 isThumb = false;
285 RegOpIdxOnIndirectCall = 0;
286 break;
287 case ARM::tBLXr: // isThumb2
288 case ARM::tBLXr_noip: // isThumb2
289 isThumb = true;
290 RegOpIdxOnIndirectCall = 2;
291 break;
292 default:
293 llvm_unreachable("unhandled Indirect Call");
294 }
295
296 Register Reg = IndirectCall.getOperand(RegOpIdxOnIndirectCall).getReg();
297 // Since linkers are allowed to clobber R12 on function calls, the above
298 // mitigation only works if the original indirect call instruction was not
299 // using R12. Code generation before must make sure that no indirect call
300 // using R12 was produced if the mitigation is enabled.
301 // Also, the transformation is incorrect if the indirect call uses LR, so
302 // also have to avoid that.
303 assert(Reg != ARM::R12 && Reg != ARM::LR);
304 bool RegIsKilled = IndirectCall.getOperand(RegOpIdxOnIndirectCall).isKill();
305
306 DebugLoc DL = IndirectCall.getDebugLoc();
307
308 MachineFunction &MF = *MBBI->getMF();
309 auto ThunkIt = llvm::find_if(SLSBLRThunks, [Reg, isThumb](auto T) {
310 return T.Reg == Reg && T.isThumb == isThumb;
311 });
312 assert(ThunkIt != std::end(SLSBLRThunks));
313 Module *M = MF.getFunction().getParent();
314 const GlobalValue *GV = cast<GlobalValue>(M->getNamedValue(ThunkIt->Name));
315
317 isThumb ? BuildMI(MBB, MBBI, DL, TII->get(ARM::tBL))
318 .addImm(IndirectCall.getOperand(0).getImm())
319 .addReg(IndirectCall.getOperand(1).getReg())
321 : BuildMI(MBB, MBBI, DL, TII->get(ARM::BL)).addGlobalAddress(GV);
322
323 // Now copy the implicit operands from IndirectCall to BL and copy other
324 // necessary info.
325 // However, both IndirectCall and BL instructions implictly use SP and
326 // implicitly define LR. Blindly copying implicit operands would result in SP
327 // and LR operands to be present multiple times. While this may not be too
328 // much of an issue, let's avoid that for cleanliness, by removing those
329 // implicit operands from the BL created above before we copy over all
330 // implicit operands from the IndirectCall.
331 int ImpLROpIdx = -1;
332 int ImpSPOpIdx = -1;
333 for (unsigned OpIdx = BL->getNumExplicitOperands();
334 OpIdx < BL->getNumOperands(); OpIdx++) {
335 MachineOperand Op = BL->getOperand(OpIdx);
336 if (!Op.isReg())
337 continue;
338 if (Op.getReg() == ARM::LR && Op.isDef())
339 ImpLROpIdx = OpIdx;
340 if (Op.getReg() == ARM::SP && !Op.isDef())
341 ImpSPOpIdx = OpIdx;
342 }
343 assert(ImpLROpIdx != -1);
344 assert(ImpSPOpIdx != -1);
345 int FirstOpIdxToRemove = std::max(ImpLROpIdx, ImpSPOpIdx);
346 int SecondOpIdxToRemove = std::min(ImpLROpIdx, ImpSPOpIdx);
347 BL->removeOperand(FirstOpIdxToRemove);
348 BL->removeOperand(SecondOpIdxToRemove);
349 // Now copy over the implicit operands from the original IndirectCall
350 BL->copyImplicitOps(MF, IndirectCall);
352 // Also add the register called in the IndirectCall as being used in the
353 // called thunk.
354 BL->addOperand(MachineOperand::CreateReg(Reg, false /*isDef*/, true /*isImp*/,
355 RegIsKilled /*isKill*/));
356 // Remove IndirectCallinstruction
357 MBB.erase(MBBI);
358 return MBB;
359}
360
361bool ARMSLSHardening::hardenIndirectCalls(MachineBasicBlock &MBB) const {
362 if (!ST->hardenSlsBlr())
363 return false;
364 bool Modified = false;
367 for (; MBBI != E; MBBI = NextMBBI) {
368 MachineInstr &MI = *MBBI;
369 NextMBBI = std::next(MBBI);
370 // Tail calls are both indirect calls and "returns".
371 // They are also indirect jumps, so should be handled by sls-harden-retbr,
372 // rather than sls-harden-blr.
373 if (isIndirectCall(MI) && !MI.isReturn()) {
374 ConvertIndirectCallToIndirectJump(MBB, MBBI);
375 Modified = true;
376 }
377 }
378 return Modified;
379}
380
381
382
384 return new ARMSLSHardening();
385}
386
387namespace {
388class ARMIndirectThunks : public ThunkInserterPass<SLSBLRThunkInserter> {
389public:
390 static char ID;
391
392 ARMIndirectThunks() : ThunkInserterPass(ID) {}
393
394 StringRef getPassName() const override { return "ARM Indirect Thunks"; }
395};
396} // end anonymous namespace
397
398char ARMIndirectThunks::ID = 0;
399
401 return new ARMIndirectThunks();
402}
aarch64 promote const
static void insertSpeculationBarrier(const AArch64Subtarget *ST, MachineBasicBlock &MBB, MachineBasicBlock::iterator MBBI, DebugLoc DL, bool AlwaysUseISBDSB=false)
static bool isThumb(const MCSubtargetInfo &STI)
MachineBasicBlock & MBB
ArmInsertedThunks & operator|=(ArmInsertedThunks &X, ArmInsertedThunks Y)
MachineBasicBlock MachineBasicBlock::iterator DebugLoc bool AlwaysUseISBDSB
#define ARM_SLS_HARDENING_NAME
ArmInsertedThunks
@ NoThunk
@ ArmThunk
@ ThumbThunk
MachineBasicBlock MachineBasicBlock::iterator DebugLoc DL
MachineBasicBlock MachineBasicBlock::iterator MBBI
static const struct ThunkNameRegMode SLSBLRThunks[]
static GCMetadataPrinterRegistry::Add< ErlangGCPrinter > X("erlang", "erlang-compatible garbage collector")
const HexagonInstrInfo * TII
IRTranslator LLVM IR MI
Contains a base ThunkInserter class that simplifies injection of MI thunks as well as a default imple...
static GCMetadataPrinterRegistry::Add< OcamlGCMetadataPrinter > Y("ocaml", "ocaml 3.10-compatible collector")
#define INITIALIZE_PASS(passName, arg, name, cfg, analysis)
Definition: PassSupport.h:38
assert(ImpDefSCC.getReg()==AMDGPU::SCC &&ImpDefSCC.isDef())
Represent the analysis usage information of a pass.
void setPreservesCFG()
This function should be called by the pass, iff they do not:
Definition: Pass.cpp:256
This class represents an Operation in the Expression.
A debug info location.
Definition: DebugLoc.h:33
FunctionPass class - This class is used to implement most global optimizations.
Definition: Pass.h:310
bool hasComdat() const
Definition: GlobalObject.h:127
Module * getParent()
Get the module that this global value is contained inside of...
Definition: GlobalValue.h:656
bool isPredicated(const MachineInstr &MI) const override
Returns true if the instruction is already predicated.
iterator getFirstTerminator()
Returns an iterator to the first terminator instruction of this basic block.
instr_iterator erase(instr_iterator I)
Remove an instruction from the instruction list and delete it.
MachineFunctionPass - This class adapts the FunctionPass interface to allow convenient creation of pa...
void getAnalysisUsage(AnalysisUsage &AU) const override
getAnalysisUsage - Subclasses that override getAnalysisUsage must call this.
virtual bool runOnMachineFunction(MachineFunction &MF)=0
runOnMachineFunction - This method must be overloaded to perform the desired machine code transformat...
const TargetSubtargetInfo & getSubtarget() const
getSubtarget - Return the subtarget for which this machine code is being compiled.
StringRef getName() const
getName - Return the name of the corresponding LLVM function.
Function & getFunction()
Return the LLVM function that this machine code represents.
const MachineBasicBlock & front() const
void moveCallSiteInfo(const MachineInstr *Old, const MachineInstr *New)
Move the call site info from Old to \New call site info.
const MachineInstrBuilder & addImm(int64_t Val) const
Add a new immediate operand.
const MachineInstrBuilder & add(const MachineOperand &MO) const
const MachineInstrBuilder & addGlobalAddress(const GlobalValue *GV, int64_t Offset=0, unsigned TargetFlags=0) const
const MachineInstrBuilder & addReg(Register RegNo, unsigned flags=0, unsigned SubReg=0) const
Add a new virtual register operand.
Representation of each machine instruction.
Definition: MachineInstr.h:69
This class contains meta information specific to a module.
MachineOperand class - Representation of each machine instruction operand.
static MachineOperand CreateReg(Register Reg, bool isDef, bool isImp=false, bool isKill=false, bool isDead=false, bool isUndef=false, bool isEarlyClobber=false, unsigned SubReg=0, bool isDebug=false, bool isInternalRead=false, bool isRenamable=false)
A Module instance is used to store all the information related to an LLVM module.
Definition: Module.h:65
static PassRegistry * getPassRegistry()
getPassRegistry - Access the global registry object, which is automatically initialized at applicatio...
virtual StringRef getPassName() const
getPassName - Return a nice clean name for a pass.
Definition: Pass.cpp:81
Wrapper class representing virtual and physical registers.
Definition: Register.h:19
StringRef - Represent a constant reference to a string, i.e.
Definition: StringRef.h:51
bool starts_with(StringRef Prefix) const
Check if this string starts with the given Prefix.
Definition: StringRef.h:265
TargetInstrInfo - Interface to description of machine instruction set.
virtual const TargetInstrInfo * getInstrInfo() const
Basic implementation of MachineFunctionPass wrapping one or more ThunkInserters passed as type parame...
This class assists in inserting MI thunk functions into the module and rewriting the existing machine...
bool mayUseThunk(const MachineFunction &MF)
Checks if MF may use thunks (true - maybe, false - definitely not).
InsertedThunksTy insertThunks(MachineModuleInfo &MMI, MachineFunction &MF, InsertedThunksTy ExistingThunks)
Rewrites the function if necessary, returns the set of thunks added.
const char * getThunkPrefix()
Returns common prefix for thunk function's names.
void populateThunk(MachineFunction &MF)
Populate the thunk function with instructions.
#define llvm_unreachable(msg)
Marks that the current location is not supposed to be reachable.
@ Entry
Definition: COFF.h:844
unsigned ID
LLVM IR allows to use arbitrary numbers as calling convention identifiers.
Definition: CallingConv.h:24
Reg
All possible values of the reg field in the ModR/M byte.
This is an optimization pass for GlobalISel generic memory operations.
Definition: AddressRanges.h:18
static bool isIndirectCall(const MachineInstr &MI)
MachineInstrBuilder BuildMI(MachineFunction &MF, const MIMetadata &MIMD, const MCInstrDesc &MCID)
Builder interface. Specify how to create the initial instruction itself.
static std::array< MachineOperand, 2 > predOps(ARMCC::CondCodes Pred, unsigned PredReg=0)
Get the operands corresponding to the given Pred value.
void initializeARMSLSHardeningPass(PassRegistry &)
FunctionPass * createARMSLSHardeningPass()
static bool isIndirectControlFlowNotComingBack(const MachineInstr &MI)
auto find_if(R &&Range, UnaryPredicate P)
Provide wrappers to std::find_if which take ranges instead of having to pass begin/end explicitly.
Definition: STLExtras.h:1766
FunctionPass * createARMIndirectThunks()
static bool isSpeculationBarrierEndBBOpcode(int Opc)