Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions llvm/lib/Target/AArch64/AArch64AsmPrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,9 @@ class AArch64AsmPrinter : public AsmPrinter {
std::optional<AArch64PACKey::ID> PACKey,
uint64_t PACDisc, Register PACAddrDisc);

// Emit the sequence for PAC.
void emitPtrauthSign(const MachineInstr *MI);

// Emit the sequence to compute the discriminator.
//
// The returned register is either unmodified AddrDisc or ScratchReg.
Expand Down Expand Up @@ -2175,6 +2178,37 @@ void AArch64AsmPrinter::emitPtrauthAuthResign(
OutStreamer->emitLabel(EndSym);
}

void AArch64AsmPrinter::emitPtrauthSign(const MachineInstr *MI) {
Register Val = MI->getOperand(1).getReg();
auto Key = (AArch64PACKey::ID)MI->getOperand(2).getImm();
uint64_t Disc = MI->getOperand(3).getImm();
Register AddrDisc = MI->getOperand(4).getReg();
bool AddrDiscKilled = MI->getOperand(4).isKill();

// As long as at least one of Val and AddrDisc is in GPR64noip, a scratch
// register is available.
Register ScratchReg = Val == AArch64::X16 ? AArch64::X17 : AArch64::X16;
assert(ScratchReg != AddrDisc &&
"Neither X16 nor X17 is available as a scratch register");

// Compute pac discriminator
assert(isUInt<16>(Disc));
Register DiscReg = emitPtrauthDiscriminator(
Disc, AddrDisc, ScratchReg, /*MayUseAddrAsScratch=*/AddrDiscKilled);
bool IsZeroDisc = DiscReg == AArch64::XZR;
unsigned Opc = getPACOpcodeForKey(Key, IsZeroDisc);

// paciza x16 ; if IsZeroDisc
// pacia x16, x17 ; if !IsZeroDisc
MCInst PACInst;
PACInst.setOpcode(Opc);
PACInst.addOperand(MCOperand::createReg(Val));
PACInst.addOperand(MCOperand::createReg(Val));
if (!IsZeroDisc)
PACInst.addOperand(MCOperand::createReg(DiscReg));
EmitToStreamer(*OutStreamer, PACInst);
}

void AArch64AsmPrinter::emitPtrauthBranch(const MachineInstr *MI) {
bool IsCall = MI->getOpcode() == AArch64::BLRA;
unsigned BrTarget = MI->getOperand(0).getReg();
Expand Down Expand Up @@ -2890,6 +2924,10 @@ void AArch64AsmPrinter::emitInstruction(const MachineInstr *MI) {
MI->getOperand(4).getImm(), MI->getOperand(5).getReg());
return;

case AArch64::PAC:
emitPtrauthSign(MI);
return;

case AArch64::LOADauthptrstatic:
LowerLOADauthptrstatic(*MI);
return;
Expand Down
82 changes: 82 additions & 0 deletions llvm/lib/Target/AArch64/AArch64ISelLowering.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3098,6 +3098,83 @@ AArch64TargetLowering::EmitGetSMESaveSize(MachineInstr &MI,
return BB;
}

// Helper function to find the instruction that defined a virtual register.
// If unable to find such instruction, returns nullptr.
static const MachineInstr *stripVRegCopies(const MachineRegisterInfo &MRI,
Register Reg) {
while (Reg.isVirtual()) {
MachineInstr *DefMI = MRI.getVRegDef(Reg);
assert(DefMI && "Virtual register definition not found");
unsigned Opcode = DefMI->getOpcode();

if (Opcode == AArch64::COPY) {
Reg = DefMI->getOperand(1).getReg();
// Vreg is defined by copying from physreg.
if (Reg.isPhysical())
return DefMI;
continue;
}
if (Opcode == AArch64::SUBREG_TO_REG) {
Reg = DefMI->getOperand(2).getReg();
continue;
}

return DefMI;
}
return nullptr;
}

void AArch64TargetLowering::fixupPtrauthDiscriminator(
MachineInstr &MI, MachineBasicBlock *BB, MachineOperand &IntDiscOp,
MachineOperand &AddrDiscOp, const TargetRegisterClass *AddrDiscRC) const {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have a guarantee that only this particular mov opcode is used for small immediate discriminators?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for pointing this out, I rechecked which instructions can be used to materialize an immediate constant and it turned out it is possible for MOVi64imm to be used even for small i64 constants when optimization is disabled (see patterns in AArch64InstrInfo.td). Furthermore, I realized it is necessary to check if integer constant is an immediate operand (as opposed to some relocation referring a global symbol, for example). For that reason, I rewritten the chain of "if"-"else if" into a switch and added more tests in 33b61f5.

const TargetInstrInfo *TII = Subtarget->getInstrInfo();
MachineRegisterInfo &MRI = MI.getMF()->getRegInfo();
const DebugLoc &DL = MI.getDebugLoc();

Register AddrDisc = AddrDiscOp.getReg();
int64_t IntDisc = IntDiscOp.getImm();
assert(IntDisc == 0 && "Blend components are already expanded");

const MachineInstr *DiscMI = stripVRegCopies(MRI, AddrDisc);
if (DiscMI) {
switch (DiscMI->getOpcode()) {
case AArch64::MOVKXi:
// blend(addr, imm) which is lowered as "MOVK addr, #imm, #48".
// #imm should be an immediate and not a global symbol, for example.
if (DiscMI->getOperand(2).isImm() &&
DiscMI->getOperand(3).getImm() == 48) {
AddrDisc = DiscMI->getOperand(1).getReg();
IntDisc = DiscMI->getOperand(2).getImm();
}
break;
case AArch64::MOVi32imm:
case AArch64::MOVi64imm:
// Small immediate integer constant passed via VReg.
if (DiscMI->getOperand(1).isImm() &&
isUInt<16>(DiscMI->getOperand(1).getImm())) {
AddrDisc = AArch64::NoRegister;
IntDisc = DiscMI->getOperand(1).getImm();
}
break;
}
}

// For uniformity, always use NoRegister, as XZR is not necessarily contained
// in the requested register class.
if (AddrDisc == AArch64::XZR)
AddrDisc = AArch64::NoRegister;

// Make sure AddrDisc operand respects the register class imposed by MI.
if (AddrDisc && MRI.getRegClass(AddrDisc) != AddrDiscRC) {
Register TmpReg = MRI.createVirtualRegister(AddrDiscRC);
BuildMI(*BB, MI, DL, TII->get(AArch64::COPY), TmpReg).addReg(AddrDisc);
AddrDisc = TmpReg;
}

AddrDiscOp.setReg(AddrDisc);
IntDiscOp.setImm(IntDisc);
}

MachineBasicBlock *AArch64TargetLowering::EmitInstrWithCustomInserter(
MachineInstr &MI, MachineBasicBlock *BB) const {

Expand Down Expand Up @@ -3196,6 +3273,11 @@ MachineBasicBlock *AArch64TargetLowering::EmitInstrWithCustomInserter(
return EmitZTInstr(MI, BB, AArch64::ZERO_T, /*Op0IsDef=*/true);
case AArch64::MOVT_TIZ_PSEUDO:
return EmitZTInstr(MI, BB, AArch64::MOVT_TIZ, /*Op0IsDef=*/true);

case AArch64::PAC:
fixupPtrauthDiscriminator(MI, BB, MI.getOperand(3), MI.getOperand(4),
&AArch64::GPR64noipRegClass);
return BB;
}
}

Expand Down
7 changes: 7 additions & 0 deletions llvm/lib/Target/AArch64/AArch64ISelLowering.h
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,13 @@ class AArch64TargetLowering : public TargetLowering {
MachineBasicBlock *EmitGetSMESaveSize(MachineInstr &MI,
MachineBasicBlock *BB) const;

/// Replace (0, vreg) discriminator components with the operands of blend
/// or with (immediate, NoRegister) when possible.
void fixupPtrauthDiscriminator(MachineInstr &MI, MachineBasicBlock *BB,
MachineOperand &IntDiscOp,
MachineOperand &AddrDiscOp,
const TargetRegisterClass *AddrDiscRC) const;

MachineBasicBlock *
EmitInstrWithCustomInserter(MachineInstr &MI,
MachineBasicBlock *MBB) const override;
Expand Down
22 changes: 21 additions & 1 deletion llvm/lib/Target/AArch64/AArch64InstrInfo.td
Original file line number Diff line number Diff line change
Expand Up @@ -2032,7 +2032,7 @@ let Predicates = [HasPAuth] in {
def DZB : SignAuthZero<prefix_z, 0b11, !strconcat(asm, "dzb"), op>;
}

defm PAC : SignAuth<0b000, 0b010, "pac", int_ptrauth_sign>;
defm PAC : SignAuth<0b000, 0b010, "pac", null_frag>;
defm AUT : SignAuth<0b001, 0b011, "aut", null_frag>;

def XPACI : ClearAuth<0, "xpaci">;
Expand Down Expand Up @@ -2152,6 +2152,26 @@ let Predicates = [HasPAuth] in {
let Uses = [];
}

// PAC pseudo instruction. In AsmPrinter, it is expanded into an actual PAC*
// instruction immediately preceded by the discriminator computation.
// This enforces the expected immediate modifier is used for signing, even
// if an attacker is able to substitute AddrDisc.
def PAC : Pseudo<(outs GPR64:$SignedVal),
(ins GPR64:$Val, i32imm:$Key, i64imm:$Disc, GPR64noip:$AddrDisc),
[], "$SignedVal = $Val">, Sched<[WriteI, ReadI]> {
let isCodeGenOnly = 1;
let hasSideEffects = 0;
let mayStore = 0;
let mayLoad = 0;
let Size = 12;
let Defs = [X16, X17];
let usesCustomInserter = 1;
}

// A standalone pattern is used, so that literal 0 can be passed as $Disc.
def : Pat<(int_ptrauth_sign GPR64:$Val, timm:$Key, GPR64noip:$AddrDisc),
(PAC GPR64:$Val, $Key, 0, GPR64noip:$AddrDisc)>;

// AUT and re-PAC a value, using different keys/data.
// This directly manipulates x16/x17, which are the only registers that
// certain OSs guarantee are safe to use for sensitive operations.
Expand Down
Loading