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 30 AArch64PointerAuth() : MachineFunctionPass(ID) {} 31 32 bool runOnMachineFunction(MachineFunction &MF) override; 33 34 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 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. 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 87 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 140 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. 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 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 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 315 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 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