[llvm-branch-commits] [llvm] [BOLT] Gadget scanner: detect signing oracles (PR #134146)
Anatoly Trosinenko via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Thu Apr 24 11:18:13 PDT 2025
================
@@ -339,6 +369,198 @@ class AArch64MCPlusBuilder : public MCPlusBuilder {
}
}
+ std::optional<std::pair<MCPhysReg, MCInst *>>
+ getAuthCheckedReg(BinaryBasicBlock &BB) const override {
+ // Match several possible hard-coded sequences of instructions which can be
+ // emitted by LLVM backend to check that the authenticated pointer is
+ // correct (see AArch64AsmPrinter::emitPtrauthCheckAuthenticatedValue).
+ //
+ // This function only matches sequences involving branch instructions.
+ // All these sequences have the form:
+ //
+ // (0) ... regular code that authenticates a pointer in Xn ...
+ // (1) analyze Xn
+ // (2) branch to .Lon_success if the pointer is correct
+ // (3) BRK #imm (fall-through basic block)
+ //
+ // In the above pseudocode, (1) + (2) is one of the following sequences:
+ //
+ // - eor Xtmp, Xn, Xn, lsl #1
+ // tbz Xtmp, #62, .Lon_success
+ //
+ // - mov Xtmp, Xn
+ // xpac(i|d) Xn (or xpaclri if Xn is LR)
+ // cmp Xtmp, Xn
+ // b.eq .Lon_success
+ //
+ // Note that any branch destination operand is accepted as .Lon_success -
+ // it is the responsibility of the caller of getAuthCheckedReg to inspect
+ // the list of successors of this basic block as appropriate.
+
+ // Any of the above code sequences assume the fall-through basic block
+ // is a dead-end BRK instruction (any immediate operand is accepted).
+ const BinaryBasicBlock *BreakBB = BB.getFallthrough();
+ if (!BreakBB || BreakBB->empty() ||
+ BreakBB->front().getOpcode() != AArch64::BRK)
+ return std::nullopt;
+
+ // Iterate over the instructions of BB in reverse order, matching opcodes
+ // and operands.
+ MCPhysReg TestedReg = 0;
+ MCPhysReg ScratchReg = 0;
+ auto It = BB.end();
+ auto StepAndGetOpcode = [&It, &BB]() -> int {
+ if (It == BB.begin())
+ return -1;
+ --It;
+ return It->getOpcode();
+ };
+
+ switch (StepAndGetOpcode()) {
+ default:
+ // Not matched the branch instruction.
+ return std::nullopt;
+ case AArch64::Bcc:
+ // Bcc EQ, .Lon_success
+ if (It->getOperand(0).getImm() != AArch64CC::EQ)
+ return std::nullopt;
+ // Not checking .Lon_success (see above).
+
+ // SUBSXrs XZR, TestedReg, ScratchReg, 0 (used by "CMP reg, reg" alias)
+ if (StepAndGetOpcode() != AArch64::SUBSXrs ||
+ It->getOperand(0).getReg() != AArch64::XZR ||
+ It->getOperand(3).getImm() != 0)
+ return std::nullopt;
+ TestedReg = It->getOperand(1).getReg();
+ ScratchReg = It->getOperand(2).getReg();
+
+ // Either XPAC(I|D) ScratchReg, ScratchReg
+ // or XPACLRI
+ switch (StepAndGetOpcode()) {
+ default:
+ return std::nullopt;
+ case AArch64::XPACLRI:
+ // No operands to check, but using XPACLRI forces TestedReg to be X30.
+ if (TestedReg != AArch64::LR)
+ return std::nullopt;
+ break;
+ case AArch64::XPACI:
+ case AArch64::XPACD:
+ if (It->getOperand(0).getReg() != ScratchReg ||
+ It->getOperand(1).getReg() != ScratchReg)
+ return std::nullopt;
+ break;
+ }
+
+ // ORRXrs ScratchReg, XZR, TestedReg, 0 (used by "MOV reg, reg" alias)
+ if (StepAndGetOpcode() != AArch64::ORRXrs)
+ return std::nullopt;
+ if (It->getOperand(0).getReg() != ScratchReg ||
+ It->getOperand(1).getReg() != AArch64::XZR ||
+ It->getOperand(2).getReg() != TestedReg ||
+ It->getOperand(3).getImm() != 0)
+ return std::nullopt;
+
+ return std::make_pair(TestedReg, &*It);
+
+ case AArch64::TBZX:
+ // TBZX ScratchReg, 62, .Lon_success
+ ScratchReg = It->getOperand(0).getReg();
+ if (It->getOperand(1).getImm() != 62)
+ return std::nullopt;
+ // Not checking .Lon_success (see above).
+
+ // EORXrs ScratchReg, TestedReg, TestedReg, 1
+ if (StepAndGetOpcode() != AArch64::EORXrs)
+ return std::nullopt;
+ TestedReg = It->getOperand(1).getReg();
+ if (It->getOperand(0).getReg() != ScratchReg ||
+ It->getOperand(2).getReg() != TestedReg ||
+ It->getOperand(3).getImm() != 1)
+ return std::nullopt;
+
+ return std::make_pair(TestedReg, &*It);
+ }
+ }
+
+ MCPhysReg getAuthCheckedReg(const MCInst &Inst,
+ bool MayOverwrite) const override {
+ // Cannot trivially reuse AArch64InstrInfo::getMemOperandWithOffsetWidth()
+ // method as it accepts an instance of MachineInstr, not MCInst.
+ const MCInstrDesc &Desc = Info->get(Inst.getOpcode());
+
+ // If signing oracles are considered, the particular value left in the base
+ // register after this instruction is important. This function checks that
+ // if the base register was overwritten, it is due to address write-back:
+ //
+ // ; good:
+ // autdza x1 ; x1 is authenticated (may fail)
+ // ldr x0, [x1, #8] ; x1 is checked and not changed
+ // pacdzb x1
+ //
+ // ; also good:
+ // autdza x1
+ // ldr x0, [x1, #8]! ; x1 is checked and incremented by 8
+ // pacdzb x1
+ //
+ // ; bad (the value being signed is not the authenticated one):
+ // autdza x1
+ // ldr x1, [x1, #8] ; x1 is overwritten with an unrelated value
+ // pacdzb x1
----------------
atrosinenko wrote:
Added in f5455c6b4b0d0ae07dd8062643e32bfba4111946, thanks!
https://github.com/llvm/llvm-project/pull/134146
More information about the llvm-branch-commits
mailing list