1 //===-- AArch64PointerAuth.cpp -- Harden code using PAuth ------------------==//
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 #include "AArch64PointerAuth.h"
10
11 #include "AArch64.h"
12 #include "AArch64InstrInfo.h"
13 #include "AArch64MachineFunctionInfo.h"
14 #include "AArch64Subtarget.h"
15 #include "llvm/CodeGen/MachineBasicBlock.h"
16 #include "llvm/CodeGen/MachineInstrBuilder.h"
17 #include "llvm/CodeGen/MachineModuleInfo.h"
18
19 using namespace llvm;
20 using namespace llvm::AArch64PAuth;
21
22 #define AARCH64_POINTER_AUTH_NAME "AArch64 Pointer Authentication"
23
24 namespace {
25
26 class AArch64PointerAuth : public MachineFunctionPass {
27 public:
28 static char ID;
29
AArch64PointerAuth()30 AArch64PointerAuth() : MachineFunctionPass(ID) {}
31
32 bool runOnMachineFunction(MachineFunction &MF) override;
33
getPassName() const34 StringRef getPassName() const override { return AARCH64_POINTER_AUTH_NAME; }
35
36 private:
37 /// An immediate operand passed to BRK instruction, if it is ever emitted.
38 const unsigned BrkOperand = 0xc471;
39
40 const AArch64Subtarget *Subtarget = nullptr;
41 const AArch64InstrInfo *TII = nullptr;
42 const AArch64RegisterInfo *TRI = nullptr;
43
44 void signLR(MachineFunction &MF, MachineBasicBlock::iterator MBBI) const;
45
46 void authenticateLR(MachineFunction &MF,
47 MachineBasicBlock::iterator MBBI) const;
48
49 bool checkAuthenticatedLR(MachineBasicBlock::iterator TI) const;
50 };
51
52 } // end anonymous namespace
53
54 INITIALIZE_PASS(AArch64PointerAuth, "aarch64-ptrauth",
55 AARCH64_POINTER_AUTH_NAME, false, false)
56
createAArch64PointerAuthPass()57 FunctionPass *llvm::createAArch64PointerAuthPass() {
58 return new AArch64PointerAuth();
59 }
60
61 char AArch64PointerAuth::ID = 0;
62
63 // Where PAuthLR support is not known at compile time, it is supported using
64 // PACM. PACM is in the hint space so has no effect when PAuthLR is not
65 // supported by the hardware, but will alter the behaviour of PACI*SP, AUTI*SP
66 // and RETAA/RETAB if the hardware supports PAuthLR.
BuildPACM(const AArch64Subtarget & Subtarget,MachineBasicBlock & MBB,MachineBasicBlock::iterator MBBI,DebugLoc DL,MachineInstr::MIFlag Flags,MCSymbol * PACSym=nullptr)67 static void BuildPACM(const AArch64Subtarget &Subtarget, MachineBasicBlock &MBB,
68 MachineBasicBlock::iterator MBBI, DebugLoc DL,
69 MachineInstr::MIFlag Flags, MCSymbol *PACSym = nullptr) {
70 const TargetInstrInfo *TII = Subtarget.getInstrInfo();
71 auto &MFnI = *MBB.getParent()->getInfo<AArch64FunctionInfo>();
72
73 // ADR X16,<address_of_PACIASP>
74 if (PACSym) {
75 assert(Flags == MachineInstr::FrameDestroy);
76 BuildMI(MBB, MBBI, DL, TII->get(AArch64::ADR))
77 .addReg(AArch64::X16, RegState::Define)
78 .addSym(PACSym);
79 }
80
81 // Only emit PACM if -mbranch-protection has +pc and the target does not
82 // have feature +pauth-lr.
83 if (MFnI.branchProtectionPAuthLR() && !Subtarget.hasPAuthLR())
84 BuildMI(MBB, MBBI, DL, TII->get(AArch64::PACM)).setMIFlag(Flags);
85 }
86
signLR(MachineFunction & MF,MachineBasicBlock::iterator MBBI) const87 void AArch64PointerAuth::signLR(MachineFunction &MF,
88 MachineBasicBlock::iterator MBBI) const {
89 auto &MFnI = *MF.getInfo<AArch64FunctionInfo>();
90 bool UseBKey = MFnI.shouldSignWithBKey();
91 bool EmitCFI = MFnI.needsDwarfUnwindInfo(MF);
92 bool NeedsWinCFI = MF.hasWinCFI();
93
94 MachineBasicBlock &MBB = *MBBI->getParent();
95
96 // Debug location must be unknown, see AArch64FrameLowering::emitPrologue.
97 DebugLoc DL;
98
99 if (UseBKey) {
100 BuildMI(MBB, MBBI, DL, TII->get(AArch64::EMITBKEY))
101 .setMIFlag(MachineInstr::FrameSetup);
102 }
103
104 // PAuthLR authentication instructions need to know the value of PC at the
105 // point of signing (PACI*).
106 if (MFnI.branchProtectionPAuthLR()) {
107 MCSymbol *PACSym = MF.getMMI().getContext().createTempSymbol();
108 MFnI.setSigningInstrLabel(PACSym);
109 }
110
111 // No SEH opcode for this one; it doesn't materialize into an
112 // instruction on Windows.
113 if (MFnI.branchProtectionPAuthLR() && Subtarget->hasPAuthLR()) {
114 BuildMI(MBB, MBBI, DL,
115 TII->get(MFnI.shouldSignWithBKey() ? AArch64::PACIBSPPC
116 : AArch64::PACIASPPC))
117 .setMIFlag(MachineInstr::FrameSetup)
118 ->setPreInstrSymbol(MF, MFnI.getSigningInstrLabel());
119 } else {
120 BuildPACM(*Subtarget, MBB, MBBI, DL, MachineInstr::FrameSetup);
121 BuildMI(MBB, MBBI, DL,
122 TII->get(MFnI.shouldSignWithBKey() ? AArch64::PACIBSP
123 : AArch64::PACIASP))
124 .setMIFlag(MachineInstr::FrameSetup)
125 ->setPreInstrSymbol(MF, MFnI.getSigningInstrLabel());
126 }
127
128 if (EmitCFI) {
129 unsigned CFIIndex =
130 MF.addFrameInst(MCCFIInstruction::createNegateRAState(nullptr));
131 BuildMI(MBB, MBBI, DL, TII->get(TargetOpcode::CFI_INSTRUCTION))
132 .addCFIIndex(CFIIndex)
133 .setMIFlags(MachineInstr::FrameSetup);
134 } else if (NeedsWinCFI) {
135 BuildMI(MBB, MBBI, DL, TII->get(AArch64::SEH_PACSignLR))
136 .setMIFlag(MachineInstr::FrameSetup);
137 }
138 }
139
authenticateLR(MachineFunction & MF,MachineBasicBlock::iterator MBBI) const140 void AArch64PointerAuth::authenticateLR(
141 MachineFunction &MF, MachineBasicBlock::iterator MBBI) const {
142 const AArch64FunctionInfo *MFnI = MF.getInfo<AArch64FunctionInfo>();
143 bool UseBKey = MFnI->shouldSignWithBKey();
144 bool EmitAsyncCFI = MFnI->needsAsyncDwarfUnwindInfo(MF);
145 bool NeedsWinCFI = MF.hasWinCFI();
146
147 MachineBasicBlock &MBB = *MBBI->getParent();
148 DebugLoc DL = MBBI->getDebugLoc();
149 // MBBI points to a PAUTH_EPILOGUE instruction to be replaced and
150 // TI points to a terminator instruction that may or may not be combined.
151 // Note that inserting new instructions "before MBBI" and "before TI" is
152 // not the same because if ShadowCallStack is enabled, its instructions
153 // are placed between MBBI and TI.
154 MachineBasicBlock::iterator TI = MBB.getFirstInstrTerminator();
155
156 // The AUTIASP instruction assembles to a hint instruction before v8.3a so
157 // this instruction can safely used for any v8a architecture.
158 // From v8.3a onwards there are optimised authenticate LR and return
159 // instructions, namely RETA{A,B}, that can be used instead. In this case the
160 // DW_CFA_AARCH64_negate_ra_state can't be emitted.
161 bool TerminatorIsCombinable =
162 TI != MBB.end() && TI->getOpcode() == AArch64::RET;
163 MCSymbol *PACSym = MFnI->getSigningInstrLabel();
164
165 if (Subtarget->hasPAuth() && TerminatorIsCombinable && !NeedsWinCFI &&
166 !MF.getFunction().hasFnAttribute(Attribute::ShadowCallStack)) {
167 if (MFnI->branchProtectionPAuthLR() && Subtarget->hasPAuthLR()) {
168 assert(PACSym && "No PAC instruction to refer to");
169 BuildMI(MBB, TI, DL,
170 TII->get(UseBKey ? AArch64::RETABSPPCi : AArch64::RETAASPPCi))
171 .addSym(PACSym)
172 .copyImplicitOps(*MBBI)
173 .setMIFlag(MachineInstr::FrameDestroy);
174 } else {
175 BuildPACM(*Subtarget, MBB, TI, DL, MachineInstr::FrameDestroy, PACSym);
176 BuildMI(MBB, TI, DL, TII->get(UseBKey ? AArch64::RETAB : AArch64::RETAA))
177 .copyImplicitOps(*MBBI)
178 .setMIFlag(MachineInstr::FrameDestroy);
179 }
180 MBB.erase(TI);
181 } else {
182 if (MFnI->branchProtectionPAuthLR() && Subtarget->hasPAuthLR()) {
183 assert(PACSym && "No PAC instruction to refer to");
184 BuildMI(MBB, MBBI, DL,
185 TII->get(UseBKey ? AArch64::AUTIBSPPCi : AArch64::AUTIASPPCi))
186 .addSym(PACSym)
187 .setMIFlag(MachineInstr::FrameDestroy);
188 } else {
189 BuildPACM(*Subtarget, MBB, MBBI, DL, MachineInstr::FrameDestroy, PACSym);
190 BuildMI(MBB, MBBI, DL,
191 TII->get(UseBKey ? AArch64::AUTIBSP : AArch64::AUTIASP))
192 .setMIFlag(MachineInstr::FrameDestroy);
193 }
194
195 if (EmitAsyncCFI) {
196 unsigned CFIIndex =
197 MF.addFrameInst(MCCFIInstruction::createNegateRAState(nullptr));
198 BuildMI(MBB, MBBI, DL, TII->get(TargetOpcode::CFI_INSTRUCTION))
199 .addCFIIndex(CFIIndex)
200 .setMIFlags(MachineInstr::FrameDestroy);
201 }
202 if (NeedsWinCFI) {
203 BuildMI(MBB, MBBI, DL, TII->get(AArch64::SEH_PACSignLR))
204 .setMIFlag(MachineInstr::FrameDestroy);
205 }
206 }
207 }
208
209 namespace {
210
211 // Mark dummy LDR instruction as volatile to prevent removing it as dead code.
createCheckMemOperand(MachineFunction & MF,const AArch64Subtarget & Subtarget)212 MachineMemOperand *createCheckMemOperand(MachineFunction &MF,
213 const AArch64Subtarget &Subtarget) {
214 MachinePointerInfo PointerInfo(Subtarget.getAddressCheckPSV());
215 auto MOVolatileLoad =
216 MachineMemOperand::MOLoad | MachineMemOperand::MOVolatile;
217
218 return MF.getMachineMemOperand(PointerInfo, MOVolatileLoad, 4, Align(4));
219 }
220
221 } // namespace
222
checkAuthenticatedRegister(MachineBasicBlock::iterator MBBI,AuthCheckMethod Method,Register AuthenticatedReg,Register TmpReg,bool UseIKey,unsigned BrkImm)223 MachineBasicBlock &llvm::AArch64PAuth::checkAuthenticatedRegister(
224 MachineBasicBlock::iterator MBBI, AuthCheckMethod Method,
225 Register AuthenticatedReg, Register TmpReg, bool UseIKey, unsigned BrkImm) {
226
227 MachineBasicBlock &MBB = *MBBI->getParent();
228 MachineFunction &MF = *MBB.getParent();
229 const AArch64Subtarget &Subtarget = MF.getSubtarget<AArch64Subtarget>();
230 const AArch64InstrInfo *TII = Subtarget.getInstrInfo();
231 DebugLoc DL = MBBI->getDebugLoc();
232
233 // First, handle the methods not requiring creating extra MBBs.
234 switch (Method) {
235 default:
236 break;
237 case AuthCheckMethod::None:
238 return MBB;
239 case AuthCheckMethod::DummyLoad:
240 BuildMI(MBB, MBBI, DL, TII->get(AArch64::LDRWui), getWRegFromXReg(TmpReg))
241 .addReg(AArch64::LR)
242 .addImm(0)
243 .addMemOperand(createCheckMemOperand(MF, Subtarget));
244 return MBB;
245 }
246
247 // Control flow has to be changed, so arrange new MBBs.
248
249 // At now, at least an AUT* instruction is expected before MBBI
250 assert(MBBI != MBB.begin() &&
251 "Cannot insert the check at the very beginning of MBB");
252 // The block to insert check into.
253 MachineBasicBlock *CheckBlock = &MBB;
254 // The remaining part of the original MBB that is executed on success.
255 MachineBasicBlock *SuccessBlock = MBB.splitAt(*std::prev(MBBI));
256
257 // The block that explicitly generates a break-point exception on failure.
258 MachineBasicBlock *BreakBlock =
259 MF.CreateMachineBasicBlock(MBB.getBasicBlock());
260 MF.push_back(BreakBlock);
261 MBB.splitSuccessor(SuccessBlock, BreakBlock);
262
263 assert(CheckBlock->getFallThrough() == SuccessBlock);
264 BuildMI(BreakBlock, DL, TII->get(AArch64::BRK)).addImm(BrkImm);
265
266 switch (Method) {
267 case AuthCheckMethod::None:
268 case AuthCheckMethod::DummyLoad:
269 llvm_unreachable("Should be handled above");
270 case AuthCheckMethod::HighBitsNoTBI:
271 BuildMI(CheckBlock, DL, TII->get(AArch64::EORXrs), TmpReg)
272 .addReg(AuthenticatedReg)
273 .addReg(AuthenticatedReg)
274 .addImm(1);
275 BuildMI(CheckBlock, DL, TII->get(AArch64::TBNZX))
276 .addReg(TmpReg)
277 .addImm(62)
278 .addMBB(BreakBlock);
279 return *SuccessBlock;
280 case AuthCheckMethod::XPACHint:
281 assert(AuthenticatedReg == AArch64::LR &&
282 "XPACHint mode is only compatible with checking the LR register");
283 assert(UseIKey && "XPACHint mode is only compatible with I-keys");
284 BuildMI(CheckBlock, DL, TII->get(AArch64::ORRXrs), TmpReg)
285 .addReg(AArch64::XZR)
286 .addReg(AArch64::LR)
287 .addImm(0);
288 BuildMI(CheckBlock, DL, TII->get(AArch64::XPACLRI));
289 BuildMI(CheckBlock, DL, TII->get(AArch64::SUBSXrs), AArch64::XZR)
290 .addReg(TmpReg)
291 .addReg(AArch64::LR)
292 .addImm(0);
293 BuildMI(CheckBlock, DL, TII->get(AArch64::Bcc))
294 .addImm(AArch64CC::NE)
295 .addMBB(BreakBlock);
296 return *SuccessBlock;
297 }
298 llvm_unreachable("Unknown AuthCheckMethod enum");
299 }
300
getCheckerSizeInBytes(AuthCheckMethod Method)301 unsigned llvm::AArch64PAuth::getCheckerSizeInBytes(AuthCheckMethod Method) {
302 switch (Method) {
303 case AuthCheckMethod::None:
304 return 0;
305 case AuthCheckMethod::DummyLoad:
306 return 4;
307 case AuthCheckMethod::HighBitsNoTBI:
308 return 12;
309 case AuthCheckMethod::XPACHint:
310 return 20;
311 }
312 llvm_unreachable("Unknown AuthCheckMethod enum");
313 }
314
checkAuthenticatedLR(MachineBasicBlock::iterator TI) const315 bool AArch64PointerAuth::checkAuthenticatedLR(
316 MachineBasicBlock::iterator TI) const {
317 AuthCheckMethod Method = Subtarget->getAuthenticatedLRCheckMethod();
318
319 if (Method == AuthCheckMethod::None)
320 return false;
321
322 // FIXME If FEAT_FPAC is implemented by the CPU, this check can be skipped.
323
324 assert(!TI->getMF()->hasWinCFI() && "WinCFI is not yet supported");
325
326 // The following code may create a signing oracle:
327 //
328 // <authenticate LR>
329 // TCRETURN ; the callee may sign and spill the LR in its prologue
330 //
331 // To avoid generating a signing oracle, check the authenticated value
332 // before possibly re-signing it in the callee, as follows:
333 //
334 // <authenticate LR>
335 // <check if LR contains a valid address>
336 // b.<cond> break_block
337 // ret_block:
338 // TCRETURN
339 // break_block:
340 // brk <BrkOperand>
341 //
342 // or just
343 //
344 // <authenticate LR>
345 // ldr tmp, [lr]
346 // TCRETURN
347
348 // TmpReg is chosen assuming X16 and X17 are dead after TI.
349 assert(AArch64InstrInfo::isTailCallReturnInst(*TI) &&
350 "Tail call is expected");
351 Register TmpReg =
352 TI->readsRegister(AArch64::X16, TRI) ? AArch64::X17 : AArch64::X16;
353 assert(!TI->readsRegister(TmpReg, TRI) &&
354 "More than a single register is used by TCRETURN");
355
356 checkAuthenticatedRegister(TI, Method, AArch64::LR, TmpReg, /*UseIKey=*/true,
357 BrkOperand);
358
359 return true;
360 }
361
runOnMachineFunction(MachineFunction & MF)362 bool AArch64PointerAuth::runOnMachineFunction(MachineFunction &MF) {
363 const auto *MFnI = MF.getInfo<AArch64FunctionInfo>();
364
365 Subtarget = &MF.getSubtarget<AArch64Subtarget>();
366 TII = Subtarget->getInstrInfo();
367 TRI = Subtarget->getRegisterInfo();
368
369 SmallVector<MachineBasicBlock::instr_iterator> PAuthPseudoInstrs;
370 SmallVector<MachineBasicBlock::instr_iterator> TailCallInstrs;
371
372 bool Modified = false;
373 bool HasAuthenticationInstrs = false;
374
375 for (auto &MBB : MF) {
376 // Using instr_iterator to catch unsupported bundled TCRETURN* instructions
377 // instead of just skipping them.
378 for (auto &MI : MBB.instrs()) {
379 switch (MI.getOpcode()) {
380 default:
381 // Bundled TCRETURN* instructions (such as created by KCFI)
382 // are not supported yet, but no support is required if no
383 // PAUTH_EPILOGUE instructions exist in the same function.
384 // Skip the BUNDLE instruction itself (actual bundled instructions
385 // follow it in the instruction list).
386 if (MI.isBundle())
387 continue;
388 if (AArch64InstrInfo::isTailCallReturnInst(MI))
389 TailCallInstrs.push_back(MI.getIterator());
390 break;
391 case AArch64::PAUTH_PROLOGUE:
392 case AArch64::PAUTH_EPILOGUE:
393 assert(!MI.isBundled());
394 PAuthPseudoInstrs.push_back(MI.getIterator());
395 break;
396 }
397 }
398 }
399
400 for (auto It : PAuthPseudoInstrs) {
401 switch (It->getOpcode()) {
402 case AArch64::PAUTH_PROLOGUE:
403 signLR(MF, It);
404 break;
405 case AArch64::PAUTH_EPILOGUE:
406 authenticateLR(MF, It);
407 HasAuthenticationInstrs = true;
408 break;
409 default:
410 llvm_unreachable("Unhandled opcode");
411 }
412 It->eraseFromParent();
413 Modified = true;
414 }
415
416 // FIXME Do we need to emit any PAuth-related epilogue code at all
417 // when SCS is enabled?
418 if (HasAuthenticationInstrs &&
419 !MFnI->needsShadowCallStackPrologueEpilogue(MF)) {
420 for (auto TailCall : TailCallInstrs) {
421 assert(!TailCall->isBundled() && "Not yet supported");
422 Modified |= checkAuthenticatedLR(TailCall);
423 }
424 }
425
426 return Modified;
427 }
428