https://github.com/atrosinenko updated https://github.com/llvm/llvm-project/pull/139778
>From b5a5ea9b2fc7a85760064994bea7153bb91b746b Mon Sep 17 00:00:00 2001 From: Anatoly Trosinenko <[email protected]> Date: Wed, 7 May 2025 16:42:00 +0300 Subject: [PATCH 1/4] [BOLT] Introduce helpers to match `MCInst`s one at a time (NFC) Introduce matchInst helper function to capture and/or match the operands of MCInst. Unlike the existing `MCPlusBuilder::MCInstMatcher` machinery, matchInst is intended for the use cases when precise control over the instruction order is required. For example, when validating PtrAuth hardening, all registers are usually considered unsafe after a function call, even though callee-saved registers should preserve their old values *under normal operation*. --- bolt/include/bolt/Core/MCInstUtils.h | 128 ++++++++++++++++++ .../Target/AArch64/AArch64MCPlusBuilder.cpp | 90 +++++------- 2 files changed, 162 insertions(+), 56 deletions(-) diff --git a/bolt/include/bolt/Core/MCInstUtils.h b/bolt/include/bolt/Core/MCInstUtils.h index 2e93dfaf4c275..40731a4fed701 100644 --- a/bolt/include/bolt/Core/MCInstUtils.h +++ b/bolt/include/bolt/Core/MCInstUtils.h @@ -149,6 +149,134 @@ static inline raw_ostream &operator<<(raw_ostream &OS, return Ref.print(OS); } +/// Instruction-matching helpers operating on a single instruction at a time. +/// +/// Unlike MCPlusBuilder::MCInstMatcher, this matchInst() function focuses on +/// the cases where a precise control over the instruction order is important: +/// +/// // Bring the short names into the local scope: +/// using namespace MCInstMatcher; +/// // Declare the registers to capture: +/// Reg Xn, Xm; +/// // Capture the 0th and 1st operands, match the 2nd operand against the +/// // just captured Xm register, match the 3rd operand against literal 0: +/// if (!matchInst(MaybeAdd, AArch64::ADDXrs, Xm, Xn, Xm, Imm(0)) +/// return AArch64::NoRegister; +/// // Match the 0th operand against Xm: +/// if (!matchInst(MaybeBr, AArch64::BR, Xm)) +/// return AArch64::NoRegister; +/// // Return the matched register: +/// return Xm.get(); +namespace MCInstMatcher { + +// The base class to match an operand of type T. +// +// The subclasses of OpMatcher are intended to be allocated on the stack and +// to only be used by passing them to matchInst() and by calling their get() +// function, thus the peculiar `mutable` specifiers: to make the calling code +// compact and readable, the templated matchInst() function has to accept both +// long-lived Imm/Reg wrappers declared as local variables (intended to capture +// the first operand's value and match the subsequent operands, whether inside +// a single instruction or across multiple instructions), as well as temporary +// wrappers around literal values to match, f.e. Imm(42) or Reg(AArch64::XZR). +template <typename T> class OpMatcher { + mutable std::optional<T> Value; + mutable std::optional<T> SavedValue; + + // Remember/restore the last Value - to be called by matchInst. + void remember() const { SavedValue = Value; } + void restore() const { Value = SavedValue; } + + template <class... OpMatchers> + friend bool matchInst(const MCInst &, unsigned, const OpMatchers &...); + +protected: + OpMatcher(std::optional<T> ValueToMatch) : Value(ValueToMatch) {} + + bool matchValue(T OpValue) const { + // Check that OpValue does not contradict the existing Value. + bool MatchResult = !Value || *Value == OpValue; + // If MatchResult is false, all matchers will be reset before returning from + // matchInst, including this one, thus no need to assign conditionally. + Value = OpValue; + + return MatchResult; + } + +public: + /// Returns the captured value. + T get() const { + assert(Value.has_value()); + return *Value; + } +}; + +class Reg : public OpMatcher<MCPhysReg> { + bool matches(const MCOperand &Op) const { + if (!Op.isReg()) + return false; + + return matchValue(Op.getReg()); + } + + template <class... OpMatchers> + friend bool matchInst(const MCInst &, unsigned, const OpMatchers &...); + +public: + Reg(std::optional<MCPhysReg> RegToMatch = std::nullopt) + : OpMatcher<MCPhysReg>(RegToMatch) {} +}; + +class Imm : public OpMatcher<int64_t> { + bool matches(const MCOperand &Op) const { + if (!Op.isImm()) + return false; + + return matchValue(Op.getImm()); + } + + template <class... OpMatchers> + friend bool matchInst(const MCInst &, unsigned, const OpMatchers &...); + +public: + Imm(std::optional<int64_t> ImmToMatch = std::nullopt) + : OpMatcher<int64_t>(ImmToMatch) {} +}; + +/// Tries to match Inst and updates Ops on success. +/// +/// If Inst has the specified Opcode and its operand list prefix matches Ops, +/// this function returns true and updates Ops, otherwise false is returned and +/// values of Ops are kept as before matchInst was called. +/// +/// Please note that while Ops are technically passed by a const reference to +/// make invocations like `matchInst(MI, Opcode, Imm(42))` possible, all their +/// fields are marked mutable. +template <class... OpMatchers> +bool matchInst(const MCInst &Inst, unsigned Opcode, const OpMatchers &...Ops) { + if (Inst.getOpcode() != Opcode) + return false; + assert(sizeof...(Ops) <= Inst.getNumOperands() && + "Too many operands are matched for the Opcode"); + + // Ask each matcher to remember its current value in case of rollback. + (Ops.remember(), ...); + + // Check if all matchers match the corresponding operands. + auto It = Inst.begin(); + auto AllMatched = (Ops.matches(*(It++)) && ... && true); + + // If match failed, restore the original captured values. + if (!AllMatched) { + (Ops.restore(), ...); + return false; + } + + return true; +} + +} // namespace MCInstMatcher + } // namespace bolt } // namespace llvm diff --git a/bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp b/bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp index 72f95cea6fa1d..c1052ad5c0ba9 100644 --- a/bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp +++ b/bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp @@ -19,6 +19,7 @@ #include "Utils/AArch64BaseInfo.h" #include "bolt/Core/BinaryBasicBlock.h" #include "bolt/Core/BinaryFunction.h" +#include "bolt/Core/MCInstUtils.h" #include "bolt/Core/MCPlusBuilder.h" #include "llvm/BinaryFormat/ELF.h" #include "llvm/MC/MCContext.h" @@ -389,81 +390,58 @@ class AArch64MCPlusBuilder : public MCPlusBuilder { // 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(); + auto StepBack = [&]() { + while (It != BB.begin()) { + --It; + if (!isCFI(*It)) + return true; + } + return false; }; - - switch (StepAndGetOpcode()) { - default: - // Not matched the branch instruction. + // Step to the last non-CFI instruction. + if (!StepBack()) 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) + using namespace llvm::bolt::MCInstMatcher; + Reg TestedReg; + Reg ScratchReg; + + if (matchInst(*It, AArch64::Bcc, Imm(AArch64CC::EQ) /*, .Lon_success*/)) { + if (!StepBack() || !matchInst(*It, AArch64::SUBSXrs, Reg(AArch64::XZR), + TestedReg, ScratchReg, Imm(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: + if (!StepBack()) return std::nullopt; - case AArch64::XPACLRI: + if (matchInst(*It, 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) + if (TestedReg.get() != AArch64::LR) return std::nullopt; - break; + } else if (!matchInst(*It, AArch64::XPACI, ScratchReg, ScratchReg) && + !matchInst(*It, AArch64::XPACD, ScratchReg, ScratchReg)) { + return std::nullopt; } - // ORRXrs ScratchReg, XZR, TestedReg, 0 (used by "MOV reg, reg" alias) - if (StepAndGetOpcode() != AArch64::ORRXrs) + if (!StepBack() || !matchInst(*It, AArch64::ORRXrs, ScratchReg, + Reg(AArch64::XZR), TestedReg, Imm(0))) 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). + return std::make_pair(TestedReg.get(), &*It); + } - // 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) + if (matchInst(*It, AArch64::TBZX, ScratchReg, Imm(62) /*, .Lon_success*/)) { + if (!StepBack() || !matchInst(*It, AArch64::EORXrs, Reg(ScratchReg), + TestedReg, TestedReg, Imm(1))) return std::nullopt; - return std::make_pair(TestedReg, &*It); + return std::make_pair(TestedReg.get(), &*It); } + + return std::nullopt; } std::optional<MCPhysReg> getAuthCheckedReg(const MCInst &Inst, >From 750f4224326073253eecd81d16a1e410b2f489ef Mon Sep 17 00:00:00 2001 From: Anatoly Trosinenko <[email protected]> Date: Tue, 6 May 2025 11:31:03 +0300 Subject: [PATCH 2/4] [BOLT] Gadget scanner: prevent false positives due to jump tables As part of PAuth hardening, AArch64 LLVM backend can use a special BR_JumpTable pseudo (enabled by -faarch64-jump-table-hardening Clang option) which is expanded in the AsmPrinter into a contiguous sequence without unsafe instructions in the middle. This commit adds another target-specific callback to MCPlusBuilder to make it possible to inhibit false positives for known-safe jump table dispatch sequences. Without special handling, the branch instruction is likely to be reported as a non-protected call (as its destination is not produced by an auth instruction, PC-relative address materialization, etc.) and possibly as a tail call being performed with unsafe link register (as the detection whether the branch instruction is a tail call is an heuristic). For now, only the specific instruction sequence used by the AArch64 LLVM backend is matched. --- bolt/include/bolt/Core/MCInstUtils.h | 9 + bolt/include/bolt/Core/MCPlusBuilder.h | 14 + bolt/lib/Core/MCInstUtils.cpp | 20 + bolt/lib/Passes/PAuthGadgetScanner.cpp | 10 + .../Target/AArch64/AArch64MCPlusBuilder.cpp | 73 ++ .../AArch64/gs-pauth-jump-table.s | 703 ++++++++++++++++++ 6 files changed, 829 insertions(+) create mode 100644 bolt/test/binary-analysis/AArch64/gs-pauth-jump-table.s diff --git a/bolt/include/bolt/Core/MCInstUtils.h b/bolt/include/bolt/Core/MCInstUtils.h index 40731a4fed701..f6ce5591ae57d 100644 --- a/bolt/include/bolt/Core/MCInstUtils.h +++ b/bolt/include/bolt/Core/MCInstUtils.h @@ -141,6 +141,15 @@ class MCInstReference { return nullptr; } + /// Returns the only preceding instruction, or std::nullopt if multiple or no + /// predecessors are possible. + /// + /// If CFG information is available, basic block boundary can be crossed, + /// provided there is exactly one predecessor. If CFG is not available, the + /// preceding instruction in the offset order is returned, unless this is the + /// first instruction of the function. + std::optional<MCInstReference> getSinglePredecessor(); + raw_ostream &print(raw_ostream &OS) const; }; diff --git a/bolt/include/bolt/Core/MCPlusBuilder.h b/bolt/include/bolt/Core/MCPlusBuilder.h index ae04891e791f9..f13e41c75c2ac 100644 --- a/bolt/include/bolt/Core/MCPlusBuilder.h +++ b/bolt/include/bolt/Core/MCPlusBuilder.h @@ -14,6 +14,7 @@ #ifndef BOLT_CORE_MCPLUSBUILDER_H #define BOLT_CORE_MCPLUSBUILDER_H +#include "bolt/Core/MCInstUtils.h" #include "bolt/Core/MCPlus.h" #include "bolt/Core/Relocation.h" #include "llvm/ADT/ArrayRef.h" @@ -711,6 +712,19 @@ class MCPlusBuilder { return std::nullopt; } + /// Tests if BranchInst corresponds to an instruction sequence which is known + /// to be a safe dispatch via jump table. + /// + /// The target can decide which instruction sequences to consider "safe" from + /// the Pointer Authentication point of view, such as any jump table dispatch + /// sequence without function calls inside, any sequence which is contiguous, + /// or only some specific well-known sequences. + virtual bool + isSafeJumpTableBranchForPtrAuth(MCInstReference BranchInst) const { + llvm_unreachable("not implemented"); + return false; + } + virtual bool isTerminator(const MCInst &Inst) const; virtual bool isNoop(const MCInst &Inst) const { diff --git a/bolt/lib/Core/MCInstUtils.cpp b/bolt/lib/Core/MCInstUtils.cpp index 3cdb9673d4dc0..39bc96f087f8e 100644 --- a/bolt/lib/Core/MCInstUtils.cpp +++ b/bolt/lib/Core/MCInstUtils.cpp @@ -54,3 +54,23 @@ raw_ostream &MCInstReference::print(raw_ostream &OS) const { OS << ">"; return OS; } + +std::optional<MCInstReference> MCInstReference::getSinglePredecessor() { + if (const RefInBB *Ref = tryGetRefInBB()) { + if (Ref->It != Ref->BB->begin()) + return MCInstReference(Ref->BB, &*std::prev(Ref->It)); + + if (Ref->BB->pred_size() != 1) + return std::nullopt; + + BinaryBasicBlock *PredBB = *Ref->BB->pred_begin(); + assert(!PredBB->empty() && "Empty basic blocks are not supported yet"); + return MCInstReference(PredBB, &*PredBB->rbegin()); + } + + const RefInBF &Ref = getRefInBF(); + if (Ref.It == Ref.BF->instrs().begin()) + return std::nullopt; + + return MCInstReference(Ref.BF, std::prev(Ref.It)); +} diff --git a/bolt/lib/Passes/PAuthGadgetScanner.cpp b/bolt/lib/Passes/PAuthGadgetScanner.cpp index 5d884e2d37354..3a6c3e3d925cf 100644 --- a/bolt/lib/Passes/PAuthGadgetScanner.cpp +++ b/bolt/lib/Passes/PAuthGadgetScanner.cpp @@ -1370,6 +1370,11 @@ shouldReportUnsafeTailCall(const BinaryContext &BC, const BinaryFunction &BF, return std::nullopt; } + if (BC.MIB->isSafeJumpTableBranchForPtrAuth(Inst)) { + LLVM_DEBUG({ dbgs() << " Safe jump table detected, skipping.\n"; }); + return std::nullopt; + } + // Returns at most one report per instruction - this is probably OK... for (auto Reg : RegsToCheck) if (!S.TrustedRegs[Reg]) @@ -1400,6 +1405,11 @@ shouldReportCallGadget(const BinaryContext &BC, const MCInstReference &Inst, if (S.SafeToDerefRegs[DestReg]) return std::nullopt; + if (BC.MIB->isSafeJumpTableBranchForPtrAuth(Inst)) { + LLVM_DEBUG({ dbgs() << " Safe jump table detected, skipping.\n"; }); + return std::nullopt; + } + return make_gadget_report(CallKind, Inst, DestReg); } diff --git a/bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp b/bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp index c1052ad5c0ba9..751768df793ae 100644 --- a/bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp +++ b/bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp @@ -532,6 +532,79 @@ class AArch64MCPlusBuilder : public MCPlusBuilder { return std::nullopt; } + bool + isSafeJumpTableBranchForPtrAuth(MCInstReference BranchInst) const override { + MCInstReference CurRef = BranchInst; + auto StepBack = [&]() { + do { + auto PredInst = CurRef.getSinglePredecessor(); + if (!PredInst) + return false; + CurRef = *PredInst; + } while (isCFI(CurRef)); + + return true; + }; + + // Match this contiguous sequence: + // cmp Xm, #count + // csel Xm, Xm, xzr, ls + // adrp Xn, .LJTIxyz + // add Xn, Xn, :lo12:.LJTIxyz + // ldrsw Xm, [Xn, Xm, lsl #2] + // .Ltmp: + // adr Xn, .Ltmp + // add Xm, Xn, Xm + // br Xm + + // FIXME: Check label operands of ADR/ADRP+ADD and #count operand of CMP. + + using namespace MCInstMatcher; + Reg Xm, Xn; + + if (!matchInst(CurRef, AArch64::BR, Xm) || !StepBack()) + return false; + + if (!matchInst(CurRef, AArch64::ADDXrs, Xm, Xn, Xm, Imm(0)) || !StepBack()) + return false; + + if (!matchInst(CurRef, AArch64::ADR, Xn /*, .Ltmp*/) || !StepBack()) + return false; + + if (!matchInst(CurRef, AArch64::LDRSWroX, Xm, Xn, Xm, Imm(0), Imm(1)) || + !StepBack()) + return false; + + if (matchInst(CurRef, AArch64::ADR, Xn /*, .LJTIxyz*/)) { + if (!StepBack()) + return false; + if (!matchInst(CurRef, AArch64::HINT, Imm(0)) || !StepBack()) + return false; + } else if (matchInst(CurRef, AArch64::ADDXri, Xn, + Xn /*, :lo12:.LJTIxyz*/)) { + if (!StepBack()) + return false; + if (!matchInst(CurRef, AArch64::ADRP, Xn /*, .LJTIxyz*/) || !StepBack()) + return false; + } else { + return false; + } + + if (!matchInst(CurRef, AArch64::CSELXr, Xm, Xm, Reg(AArch64::XZR), + Imm(AArch64CC::LS)) || + !StepBack()) + return false; + + if (!matchInst(CurRef, AArch64::SUBSXri, Reg(AArch64::XZR), + Xm /*, #count*/)) + return false; + + // Some platforms treat X16 and X17 as more protected registers, others + // do not make such distinction. So far, accept any registers as Xm and Xn. + + return true; + } + bool isADRP(const MCInst &Inst) const override { return Inst.getOpcode() == AArch64::ADRP; } diff --git a/bolt/test/binary-analysis/AArch64/gs-pauth-jump-table.s b/bolt/test/binary-analysis/AArch64/gs-pauth-jump-table.s new file mode 100644 index 0000000000000..5a42ed078e9c2 --- /dev/null +++ b/bolt/test/binary-analysis/AArch64/gs-pauth-jump-table.s @@ -0,0 +1,703 @@ +// -Wl,--no-relax prevents converting ADRP+ADD pairs into NOP+ADR. +// Without -Wl,--emit-relocs BOLT refuses to create CFG information for the below functions. + +// RUN: %clang %cflags -march=armv8.3-a -Wl,--no-relax -Wl,--emit-relocs %s -o %t.exe +// RUN: llvm-bolt-binary-analysis --scanners=pauth %t.exe 2>&1 | FileCheck --check-prefixes=CHECK,CFG %s +// RUN: %clang %cflags -march=armv8.3-a -Wl,--no-relax %s -o %t.exe +// RUN: llvm-bolt-binary-analysis --scanners=pauth %t.exe 2>&1 | FileCheck --check-prefixes=CHECK,NOCFG %s + +// FIXME: Labels could be further validated. Specifically, it could be checked +// that the jump table itself is located in a read-only data section. + +// FIXME: BOLT does not reconstruct CFG correctly for jump tables yet, thus +// register state is pessimistically reset to unsafe at the beginning of +// each basic block without any predecessors. +// Until CFG reconstruction is fixed, add paciasp+autiasp instructions to +// silence "non-protected ret" false-positives and explicitly ignore +// "Warning: the function has unreachable basic blocks..." lines. + + .text + .p2align 2 + .globl good_jump_table + .type good_jump_table,@function +good_jump_table: +// CHECK-NOT: good_jump_table +// CFG: GS-PAUTH: Warning: the function has unreachable basic blocks (possibly incomplete CFG) in function good_jump_table +// CHECK-NOT: good_jump_table + paciasp + cmp x16, #0x2 + csel x16, x16, xzr, ls + adrp x17, 4f + add x17, x17, :lo12:4f + ldrsw x16, [x17, x16, lsl #2] +1: + adr x17, 1b + add x16, x17, x16 + br x16 +2: + autiasp + ret +3: + autiasp + ret + .size good_jump_table, .-good_jump_table + .section .rodata,"a",@progbits + .p2align 2, 0x0 +4: + .word 2b-1b + .word 3b-1b + +// NOP (HINT #0) before ADR is correct (it can be produced by linker due to +// relaxing ADRP+ADD sequence), but other HINT instructions are not. + + .text + .p2align 2 + .globl jump_table_relaxed_adrp_add + .type jump_table_relaxed_adrp_add,@function +jump_table_relaxed_adrp_add: +// CHECK-NOT: jump_table_relaxed_adrp_add +// CFG: GS-PAUTH: Warning: the function has unreachable basic blocks (possibly incomplete CFG) in function jump_table_relaxed_adrp_add +// CHECK-NOT: jump_table_relaxed_adrp_add + paciasp + cmp x16, #0x2 + csel x16, x16, xzr, ls + hint #0 // nop + adr x17, 4f + ldrsw x16, [x17, x16, lsl #2] +1: + adr x17, 1b + add x16, x17, x16 + br x16 +2: + autiasp + ret +3: + autiasp + ret + .size jump_table_relaxed_adrp_add, .-jump_table_relaxed_adrp_add + .section .rodata,"a",@progbits + .p2align 2, 0x0 +4: + .word 2b-1b + .word 3b-1b + + .text + .p2align 2 + .globl jump_table_wrong_hint + .type jump_table_wrong_hint,@function +jump_table_wrong_hint: +// CFG-LABEL: GS-PAUTH: non-protected call found in function jump_table_wrong_hint, basic block {{[^,]+}}, at address +// NOCFG-LABEL: GS-PAUTH: non-protected call found in function jump_table_wrong_hint, at address +// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: br x16 # UNKNOWN CONTROL FLOW +// CHECK-NEXT: The 1 instructions that write to the affected registers after any authentication are: +// CHECK-NEXT: 1. {{[0-9a-f]+}}: add x16, x17, x16 +// CFG-NEXT: This happens in the following basic block: +// CFG-NEXT: {{[0-9a-f]+}}: adr x17, __ENTRY_jump_table_wrong_hint@0x{{[0-9a-f]+}} +// CFG-NEXT: {{[0-9a-f]+}}: add x16, x17, x16 +// CFG-NEXT: {{[0-9a-f]+}}: br x16 # UNKNOWN CONTROL FLOW + paciasp + cmp x16, #0x2 + csel x16, x16, xzr, ls + hint #20 // unknown hint + adr x17, 4f + ldrsw x16, [x17, x16, lsl #2] +1: + adr x17, 1b + add x16, x17, x16 + br x16 +2: + autiasp + ret +3: + autiasp + ret + .size jump_table_wrong_hint, .-jump_table_wrong_hint + .section .rodata,"a",@progbits + .p2align 2, 0x0 +4: + .word 2b-1b + .word 3b-1b + +// For now, all registers are permitted as temporary ones, not only x16 and x17. + + .text + .p2align 2 + .globl jump_table_unsafe_reg_1 + .type jump_table_unsafe_reg_1,@function +jump_table_unsafe_reg_1: +// CHECK-NOT: jump_table_unsafe_reg_1 +// CFG: GS-PAUTH: Warning: the function has unreachable basic blocks (possibly incomplete CFG) in function jump_table_unsafe_reg_1 +// CHECK-NOT: jump_table_unsafe_reg_1 + paciasp + cmp x1, #0x2 + csel x1, x1, xzr, ls + adrp x17, 4f + add x17, x17, :lo12:4f + ldrsw x1, [x17, x1, lsl #2] +1: + adr x17, 1b + add x1, x17, x1 + br x1 +2: + autiasp + ret +3: + autiasp + ret + .size jump_table_unsafe_reg_1, .-jump_table_unsafe_reg_1 + .section .rodata,"a",@progbits + .p2align 2, 0x0 +4: + .word 2b-1b + .word 3b-1b + + .text + .p2align 2 + .globl jump_table_unsafe_reg_2 + .type jump_table_unsafe_reg_2,@function +jump_table_unsafe_reg_2: +// CHECK-NOT: jump_table_unsafe_reg_2 +// CFG: GS-PAUTH: Warning: the function has unreachable basic blocks (possibly incomplete CFG) in function jump_table_unsafe_reg_2 +// CHECK-NOT: jump_table_unsafe_reg_2 + paciasp + cmp x16, #0x2 + csel x16, x16, xzr, ls + adrp x1, 4f + add x1, x1, :lo12:4f + ldrsw x16, [x1, x16, lsl #2] +1: + adr x1, 1b + add x16, x1, x16 + br x16 +2: + autiasp + ret +3: + autiasp + ret + .size jump_table_unsafe_reg_2, .-jump_table_unsafe_reg_2 + .section .rodata,"a",@progbits + .p2align 2, 0x0 +4: + .word 2b-1b + .word 3b-1b + +// FIXME: Detect possibility of jump table overflow. + .text + .p2align 2 + .globl jump_table_wrong_limit + .type jump_table_wrong_limit,@function +jump_table_wrong_limit: +// CHECK-NOT: jump_table_wrong_limit +// CFG: GS-PAUTH: Warning: the function has unreachable basic blocks (possibly incomplete CFG) in function jump_table_wrong_limit +// CHECK-NOT: jump_table_wrong_limit + paciasp + cmp x16, #0x1000 + csel x16, x16, xzr, ls + adrp x17, 4f + add x17, x17, :lo12:4f + ldrsw x16, [x17, x16, lsl #2] +1: + adr x17, 1b + add x16, x17, x16 + br x16 +2: + autiasp + ret +3: + autiasp + ret + .size jump_table_wrong_limit, .-jump_table_wrong_limit + .section .rodata,"a",@progbits + .p2align 2, 0x0 +4: + .word 2b-1b + .word 3b-1b + + .text + .p2align 2 + .globl jump_table_unrelated_inst_1 + .type jump_table_unrelated_inst_1,@function +jump_table_unrelated_inst_1: +// CFG-LABEL: GS-PAUTH: non-protected call found in function jump_table_unrelated_inst_1, basic block {{[^,]+}}, at address +// NOCFG-LABEL: GS-PAUTH: non-protected call found in function jump_table_unrelated_inst_1, at address +// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: br x16 # UNKNOWN CONTROL FLOW +// CHECK-NEXT: The 1 instructions that write to the affected registers after any authentication are: +// CHECK-NEXT: 1. {{[0-9a-f]+}}: add x16, x17, x16 +// CFG-NEXT: This happens in the following basic block: +// CFG-NEXT: {{[0-9a-f]+}}: adr x17, __ENTRY_jump_table_unrelated_inst_1@0x{{[0-9a-f]+}} +// CFG-NEXT: {{[0-9a-f]+}}: nop +// CFG-NEXT: {{[0-9a-f]+}}: add x16, x17, x16 +// CFG-NEXT: {{[0-9a-f]+}}: br x16 # UNKNOWN CONTROL FLOW + paciasp + cmp x16, #0x2 + csel x16, x16, xzr, ls + adrp x17, 4f + add x17, x17, :lo12:4f + ldrsw x16, [x17, x16, lsl #2] +1: + adr x17, 1b + nop + add x16, x17, x16 + br x16 +2: + autiasp + ret +3: + autiasp + ret + .size jump_table_unrelated_inst_1, .-jump_table_unrelated_inst_1 + .section .rodata,"a",@progbits + .p2align 2, 0x0 +4: + .word 2b-1b + .word 3b-1b + + .text + .p2align 2 + .globl jump_table_unrelated_inst_2 + .type jump_table_unrelated_inst_2,@function +jump_table_unrelated_inst_2: +// CFG-LABEL: GS-PAUTH: non-protected call found in function jump_table_unrelated_inst_2, basic block {{[^,]+}}, at address +// NOCFG-LABEL: GS-PAUTH: non-protected call found in function jump_table_unrelated_inst_2, at address +// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: br x16 # UNKNOWN CONTROL FLOW +// CHECK-NEXT: The 1 instructions that write to the affected registers after any authentication are: +// CHECK-NEXT: 1. {{[0-9a-f]+}}: add x16, x17, x16 +// CFG-NEXT: This happens in the following basic block: +// CFG-NEXT: {{[0-9a-f]+}}: adr x17, __ENTRY_jump_table_unrelated_inst_2@0x{{[0-9a-f]+}} +// CFG-NEXT: {{[0-9a-f]+}}: add x16, x17, x16 +// CFG-NEXT: {{[0-9a-f]+}}: br x16 # UNKNOWN CONTROL FLOW + paciasp + cmp x16, #0x2 + csel x16, x16, xzr, ls + adrp x17, 4f + add x17, x17, :lo12:4f + nop + ldrsw x16, [x17, x16, lsl #2] +1: + adr x17, 1b + add x16, x17, x16 + br x16 +2: + autiasp + ret +3: + autiasp + ret + .size jump_table_unrelated_inst_2, .-jump_table_unrelated_inst_2 + .section .rodata,"a",@progbits + .p2align 2, 0x0 +4: + .word 2b-1b + .word 3b-1b + + .text + .p2align 2 + .globl jump_table_multiple_predecessors_1 + .type jump_table_multiple_predecessors_1,@function +jump_table_multiple_predecessors_1: +// NOCFG-NOT: jump_table_multiple_predecessors_1 +// CFG-LABEL: GS-PAUTH: non-protected call found in function jump_table_multiple_predecessors_1, basic block {{[^,]+}}, at address +// CFG-NEXT: The instruction is {{[0-9a-f]+}}: br x16 # UNKNOWN CONTROL FLOW +// CFG-NEXT: The 1 instructions that write to the affected registers after any authentication are: +// CFG-NEXT: 1. {{[0-9a-f]+}}: add x16, x17, x16 +// CFG-NEXT: This happens in the following basic block: +// CFG-NEXT: {{[0-9a-f]+}}: adr x17, __ENTRY_jump_table_multiple_predecessors_1@0x{{[0-9a-f]+}} +// CFG-NEXT: {{[0-9a-f]+}}: add x16, x17, x16 +// CFG-NEXT: {{[0-9a-f]+}}: br x16 # UNKNOWN CONTROL FLOW + paciasp + cbz x1, 1f // this instruction can jump to the middle of the sequence + cmp x16, #0x2 + csel x16, x16, xzr, ls + adrp x17, 4f + add x17, x17, :lo12:4f + ldrsw x16, [x17, x16, lsl #2] +1: + adr x17, 1b // multiple predecessors are possible + add x16, x17, x16 + br x16 +2: + autiasp + ret +3: + autiasp + ret + .size jump_table_multiple_predecessors_1, .-jump_table_multiple_predecessors_1 + .section .rodata,"a",@progbits + .p2align 2, 0x0 +4: + .word 2b-1b + .word 3b-1b + + .text + .p2align 2 + .globl jump_table_multiple_predecessors_2 + .type jump_table_multiple_predecessors_2,@function +jump_table_multiple_predecessors_2: +// NOCFG-NOT: jump_table_multiple_predecessors_2 +// CFG-LABEL: GS-PAUTH: non-protected call found in function jump_table_multiple_predecessors_2, basic block {{[^,]+}}, at address +// CFG-NEXT: The instruction is {{[0-9a-f]+}}: br x16 # UNKNOWN CONTROL FLOW +// CFG-NEXT: The 1 instructions that write to the affected registers after any authentication are: +// CFG-NEXT: 1. {{[0-9a-f]+}}: add x16, x17, x16 +// CFG-NEXT: This happens in the following basic block: +// CFG-NEXT: {{[0-9a-f]+}}: adr x17, __ENTRY_jump_table_multiple_predecessors_2@0x{{[0-9a-f]+}} +// CFG-NEXT: {{[0-9a-f]+}}: add x16, x17, x16 +// CFG-NEXT: {{[0-9a-f]+}}: br x16 # UNKNOWN CONTROL FLOW + paciasp + cbz x1, 5f // this instruction can jump to the middle of the sequence + cmp x16, #0x2 + csel x16, x16, xzr, ls +5: + adrp x17, 4f // multiple predecessors are possible + add x17, x17, :lo12:4f + ldrsw x16, [x17, x16, lsl #2] +1: + adr x17, 1b + add x16, x17, x16 + br x16 +2: + autiasp + ret +3: + autiasp + ret + .size jump_table_multiple_predecessors_2, .-jump_table_multiple_predecessors_2 + .section .rodata,"a",@progbits + .p2align 2, 0x0 +4: + .word 2b-1b + .word 3b-1b + +// Test a few pattern violations... + + .text + .p2align 2 + .globl jump_table_wrong_reg_1 + .type jump_table_wrong_reg_1,@function +jump_table_wrong_reg_1: +// CFG-LABEL: GS-PAUTH: non-protected call found in function jump_table_wrong_reg_1, basic block {{[^,]+}}, at address +// NOCFG-LABEL: GS-PAUTH: non-protected call found in function jump_table_wrong_reg_1, at address +// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: br x1 # UNKNOWN CONTROL FLOW +// CHECK-NEXT: The 0 instructions that write to the affected registers after any authentication are: + paciasp + cmp x16, #0x2 + csel x16, x16, xzr, ls + adrp x17, 4f + add x17, x17, :lo12:4f + ldrsw x16, [x17, x16, lsl #2] +1: + adr x17, 1b + add x16, x17, x16 + br x1 // wrong reg +2: + autiasp + ret +3: + autiasp + ret + .size jump_table_wrong_reg_1, .-jump_table_wrong_reg_1 + .section .rodata,"a",@progbits + .p2align 2, 0x0 +4: + .word 2b-1b + .word 3b-1b + + .text + .p2align 2 + .globl jump_table_wrong_reg_2 + .type jump_table_wrong_reg_2,@function +jump_table_wrong_reg_2: +// CFG-LABEL: GS-PAUTH: non-protected call found in function jump_table_wrong_reg_2, basic block {{[^,]+}}, at address +// NOCFG-LABEL: GS-PAUTH: non-protected call found in function jump_table_wrong_reg_2, at address +// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: br x16 # UNKNOWN CONTROL FLOW +// CHECK-NEXT: The 1 instructions that write to the affected registers after any authentication are: +// CHECK-NEXT: 1. {{[0-9a-f]+}}: add x16, x17, x1 +// CFG-NEXT: This happens in the following basic block: +// CFG-NEXT: {{[0-9a-f]+}}: adr x17, __ENTRY_jump_table_wrong_reg_2@0x{{[0-9a-f]+}} +// CFG-NEXT: {{[0-9a-f]+}}: add x16, x17, x1 +// CFG-NEXT: {{[0-9a-f]+}}: br x16 # UNKNOWN CONTROL FLOW + paciasp + cmp x16, #0x2 + csel x16, x16, xzr, ls + adrp x17, 4f + add x17, x17, :lo12:4f + ldrsw x16, [x17, x16, lsl #2] +1: + adr x17, 1b + add x16, x17, x1 // wrong reg + br x16 +2: + autiasp + ret +3: + autiasp + ret + .size jump_table_wrong_reg_2, .-jump_table_wrong_reg_2 + .section .rodata,"a",@progbits + .p2align 2, 0x0 +4: + .word 2b-1b + .word 3b-1b + + .text + .p2align 2 + .globl jump_table_wrong_reg_3 + .type jump_table_wrong_reg_3,@function +jump_table_wrong_reg_3: +// CFG-LABEL: GS-PAUTH: non-protected call found in function jump_table_wrong_reg_3, basic block {{[^,]+}}, at address +// NOCFG-LABEL: GS-PAUTH: non-protected call found in function jump_table_wrong_reg_3, at address +// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: br x16 # UNKNOWN CONTROL FLOW +// CHECK-NEXT: The 1 instructions that write to the affected registers after any authentication are: +// CHECK-NEXT: 1. {{[0-9a-f]+}}: add x16, x17, x16 +// CFG-NEXT: This happens in the following basic block: +// CFG-NEXT: {{[0-9a-f]+}}: adr x17, __ENTRY_jump_table_wrong_reg_3@0x{{[0-9a-f]+}} +// CFG-NEXT: {{[0-9a-f]+}}: add x16, x17, x16 +// CFG-NEXT: {{[0-9a-f]+}}: br x16 # UNKNOWN CONTROL FLOW + paciasp + cmp x16, #0x2 + csel x16, x16, xzr, ls + adrp x17, 4f + add x17, x1, :lo12:4f // wrong reg + ldrsw x16, [x17, x16, lsl #2] +1: + adr x17, 1b + add x16, x17, x16 + br x16 +2: + autiasp + ret +3: + autiasp + ret + .size jump_table_wrong_reg_3, .-jump_table_wrong_reg_3 + .section .rodata,"a",@progbits + .p2align 2, 0x0 +4: + .word 2b-1b + .word 3b-1b + + .text + .p2align 2 + .globl jump_table_wrong_reg_4 + .type jump_table_wrong_reg_4,@function +jump_table_wrong_reg_4: +// CFG-LABEL: GS-PAUTH: non-protected call found in function jump_table_wrong_reg_4, basic block {{[^,]+}}, at address +// NOCFG-LABEL: GS-PAUTH: non-protected call found in function jump_table_wrong_reg_4, at address +// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: br x16 # UNKNOWN CONTROL FLOW +// CHECK-NEXT: The 1 instructions that write to the affected registers after any authentication are: +// CHECK-NEXT: 1. {{[0-9a-f]+}}: add x16, x17, x16 +// CFG-NEXT: This happens in the following basic block: +// CFG-NEXT: {{[0-9a-f]+}}: adr x17, __ENTRY_jump_table_wrong_reg_4@0x{{[0-9a-f]+}} +// CFG-NEXT: {{[0-9a-f]+}}: add x16, x17, x16 +// CFG-NEXT: {{[0-9a-f]+}}: br x16 # UNKNOWN CONTROL FLOW + paciasp + cmp x16, #0x2 + csel x16, x16, x1, ls // wrong reg + adrp x17, 4f + add x17, x17, :lo12:4f + ldrsw x16, [x17, x16, lsl #2] +1: + adr x17, 1b + add x16, x17, x16 + br x16 +2: + autiasp + ret +3: + autiasp + ret + .size jump_table_wrong_reg_4, .-jump_table_wrong_reg_4 + .section .rodata,"a",@progbits + .p2align 2, 0x0 +4: + .word 2b-1b + .word 3b-1b + + .text + .p2align 2 + .globl jump_table_wrong_imm_1 + .type jump_table_wrong_imm_1,@function +jump_table_wrong_imm_1: +// CFG-LABEL: GS-PAUTH: non-protected call found in function jump_table_wrong_imm_1, basic block {{[^,]+}}, at address +// NOCFG-LABEL: GS-PAUTH: non-protected call found in function jump_table_wrong_imm_1, at address +// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: br x16 # UNKNOWN CONTROL FLOW +// CHECK-NEXT: The 1 instructions that write to the affected registers after any authentication are: +// CHECK-NEXT: 1. {{[0-9a-f]+}}: add x16, x17, x16 +// CFG-NEXT: This happens in the following basic block: +// CFG-NEXT: {{[0-9a-f]+}}: adr x17, __ENTRY_jump_table_wrong_imm_1@0x{{[0-9a-f]+}} +// CFG-NEXT: {{[0-9a-f]+}}: add x16, x17, x16 +// CFG-NEXT: {{[0-9a-f]+}}: br x16 # UNKNOWN CONTROL FLOW + paciasp + cmp x16, #0x2 + csel x16, x16, xzr, ls + adrp x17, 4f + add x17, x17, :lo12:4f + ldrsw x16, [x17, x16, sxtx #2] // wrong: sxtx instead of lsl +1: + adr x17, 1b + add x16, x17, x16 + br x16 +2: + autiasp + ret +3: + autiasp + ret + .size jump_table_wrong_imm_1, .-jump_table_wrong_imm_1 + .section .rodata,"a",@progbits + .p2align 2, 0x0 +4: + .word 2b-1b + .word 3b-1b + + .text + .p2align 2 + .globl jump_table_wrong_imm_2 + .type jump_table_wrong_imm_2,@function +jump_table_wrong_imm_2: +// CFG-LABEL: GS-PAUTH: non-protected call found in function jump_table_wrong_imm_2, basic block {{[^,]+}}, at address +// NOCFG-LABEL: GS-PAUTH: non-protected call found in function jump_table_wrong_imm_2, at address +// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: br x16 # UNKNOWN CONTROL FLOW +// CHECK-NEXT: The 1 instructions that write to the affected registers after any authentication are: +// CHECK-NEXT: 1. {{[0-9a-f]+}}: add x16, x17, x16 +// CFG-NEXT: This happens in the following basic block: +// CFG-NEXT: {{[0-9a-f]+}}: adr x17, __ENTRY_jump_table_wrong_imm_2@0x{{[0-9a-f]+}} +// CFG-NEXT: {{[0-9a-f]+}}: add x16, x17, x16 +// CFG-NEXT: {{[0-9a-f]+}}: br x16 # UNKNOWN CONTROL FLOW + paciasp + cmp x16, #0x2 + csel x16, x16, xzr, lt // wrong: lt instead of ls + adrp x17, 4f + add x17, x17, :lo12:4f + ldrsw x16, [x17, x16, lsl #2] +1: + adr x17, 1b + add x16, x17, x16 + br x16 +2: + autiasp + ret +3: + autiasp + ret + .size jump_table_wrong_imm_2, .-jump_table_wrong_imm_2 + .section .rodata,"a",@progbits + .p2align 2, 0x0 +4: + .word 2b-1b + .word 3b-1b + + .text + .p2align 2 + .globl jump_table_wrong_imm_3 + .type jump_table_wrong_imm_3,@function +jump_table_wrong_imm_3: +// CFG-LABEL: GS-PAUTH: non-protected call found in function jump_table_wrong_imm_3, basic block {{[^,]+}}, at address +// NOCFG-LABEL: GS-PAUTH: non-protected call found in function jump_table_wrong_imm_3, at address +// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: br x16 # UNKNOWN CONTROL FLOW +// CHECK-NEXT: The 1 instructions that write to the affected registers after any authentication are: +// CHECK-NEXT: 1. {{[0-9a-f]+}}: add x16, x17, x16 +// CFG-NEXT: This happens in the following basic block: +// CFG-NEXT: {{[0-9a-f]+}}: adr x17, __ENTRY_jump_table_wrong_imm_3@0x{{[0-9a-f]+}} +// CFG-NEXT: {{[0-9a-f]+}}: add x16, x17, x16 +// CFG-NEXT: {{[0-9a-f]+}}: br x16 # UNKNOWN CONTROL FLOW + paciasp + cmp x16, #0x2 + csel x16, x16, xzr, ls + adrp x17, 4f + add x17, x17, :lo12:4f + ldrsw x16, [x17, x16, lsl #2] +1: + adr x17, 1b + add x16, x17, x16, lsl #2 // wrong: lsl #2 + br x16 +2: + autiasp + ret +3: + autiasp + ret + .size jump_table_wrong_imm_3, .-jump_table_wrong_imm_3 + .section .rodata,"a",@progbits + .p2align 2, 0x0 +4: + .word 2b-1b + .word 3b-1b + +// CFI instructions should be skipped and should not prevent matching +// the instruction sequence. + + .text + .p2align 2 + .globl skip_cfi_instructions + .type skip_cfi_instructions,@function +skip_cfi_instructions: + .cfi_startproc +// CHECK-NOT: skip_cfi_instructions +// CFG: GS-PAUTH: Warning: the function has unreachable basic blocks (possibly incomplete CFG) in function skip_cfi_instructions +// CHECK-NOT: skip_cfi_instructions + paciasp + cmp x16, #0x2 + csel x16, x16, xzr, ls + adrp x17, 4f + .cfi_def_cfa_offset 16 // should be skipped over when matching the sequence + add x17, x17, :lo12:4f + ldrsw x16, [x17, x16, lsl #2] +1: + adr x17, 1b + add x16, x17, x16 + br x16 +2: + autiasp + ret +3: + autiasp + ret + .size skip_cfi_instructions, .-skip_cfi_instructions + .cfi_endproc + .section .rodata,"a",@progbits + .p2align 2, 0x0 +4: + .word 2b-1b + .word 3b-1b + + .text + .p2align 2 + .globl incomplete_jump_table + .type incomplete_jump_table,@function +incomplete_jump_table: +// CFG-LABEL: GS-PAUTH: non-protected call found in function incomplete_jump_table, basic block {{[^,]+}}, at address +// NOCFG-LABEL: GS-PAUTH: non-protected call found in function incomplete_jump_table, at address +// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: br x16 # UNKNOWN CONTROL FLOW +// CHECK-NEXT: The 1 instructions that write to the affected registers after any authentication are: +// CHECK-NEXT: 1. {{[0-9a-f]+}}: add x16, x17, x16 +// CFG-NEXT: This happens in the following basic block: +// CFG-NEXT: {{[0-9a-f]+}}: adr x17, __ENTRY_incomplete_jump_table@0x{{[0-9a-f]+}} +// CFG-NEXT: {{[0-9a-f]+}}: add x16, x17, x16 +// CFG-NEXT: {{[0-9a-f]+}}: br x16 # UNKNOWN CONTROL FLOW + // Do not try to step past the start of the function. + ldrsw x16, [x17, x16, lsl #2] +1: + adr x17, 1b + add x16, x17, x16 + br x16 +2: + autiasp + ret +3: + autiasp + ret + .size incomplete_jump_table, .-incomplete_jump_table + .section .rodata,"a",@progbits + .p2align 2, 0x0 +4: + .word 2b-1b + .word 3b-1b + + .text + .globl main + .type main,@function +main: + mov x0, 0 + ret + .size main, .-main >From ca11a615dfd5e589cbc864aacbb5bc165b81ad05 Mon Sep 17 00:00:00 2001 From: Anatoly Trosinenko <[email protected]> Date: Mon, 23 Jun 2025 17:32:19 +0300 Subject: [PATCH 3/4] Update warning message in tests --- .../binary-analysis/AArch64/gs-pauth-jump-table.s | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/bolt/test/binary-analysis/AArch64/gs-pauth-jump-table.s b/bolt/test/binary-analysis/AArch64/gs-pauth-jump-table.s index 5a42ed078e9c2..8af93ca8d9da5 100644 --- a/bolt/test/binary-analysis/AArch64/gs-pauth-jump-table.s +++ b/bolt/test/binary-analysis/AArch64/gs-pauth-jump-table.s @@ -22,7 +22,7 @@ .type good_jump_table,@function good_jump_table: // CHECK-NOT: good_jump_table -// CFG: GS-PAUTH: Warning: the function has unreachable basic blocks (possibly incomplete CFG) in function good_jump_table +// CFG: GS-PAUTH: Warning: possibly imprecise CFG, the analysis quality may be degraded in this function. According to BOLT, unreachable code is found in function good_jump_table // CHECK-NOT: good_jump_table paciasp cmp x16, #0x2 @@ -56,7 +56,7 @@ good_jump_table: .type jump_table_relaxed_adrp_add,@function jump_table_relaxed_adrp_add: // CHECK-NOT: jump_table_relaxed_adrp_add -// CFG: GS-PAUTH: Warning: the function has unreachable basic blocks (possibly incomplete CFG) in function jump_table_relaxed_adrp_add +// CFG: GS-PAUTH: Warning: possibly imprecise CFG, the analysis quality may be degraded in this function. According to BOLT, unreachable code is found in function jump_table_relaxed_adrp_add // CHECK-NOT: jump_table_relaxed_adrp_add paciasp cmp x16, #0x2 @@ -126,7 +126,7 @@ jump_table_wrong_hint: .type jump_table_unsafe_reg_1,@function jump_table_unsafe_reg_1: // CHECK-NOT: jump_table_unsafe_reg_1 -// CFG: GS-PAUTH: Warning: the function has unreachable basic blocks (possibly incomplete CFG) in function jump_table_unsafe_reg_1 +// CFG: GS-PAUTH: Warning: possibly imprecise CFG, the analysis quality may be degraded in this function. According to BOLT, unreachable code is found in function jump_table_unsafe_reg_1 // CHECK-NOT: jump_table_unsafe_reg_1 paciasp cmp x1, #0x2 @@ -157,7 +157,7 @@ jump_table_unsafe_reg_1: .type jump_table_unsafe_reg_2,@function jump_table_unsafe_reg_2: // CHECK-NOT: jump_table_unsafe_reg_2 -// CFG: GS-PAUTH: Warning: the function has unreachable basic blocks (possibly incomplete CFG) in function jump_table_unsafe_reg_2 +// CFG: GS-PAUTH: Warning: possibly imprecise CFG, the analysis quality may be degraded in this function. According to BOLT, unreachable code is found in function jump_table_unsafe_reg_2 // CHECK-NOT: jump_table_unsafe_reg_2 paciasp cmp x16, #0x2 @@ -189,7 +189,7 @@ jump_table_unsafe_reg_2: .type jump_table_wrong_limit,@function jump_table_wrong_limit: // CHECK-NOT: jump_table_wrong_limit -// CFG: GS-PAUTH: Warning: the function has unreachable basic blocks (possibly incomplete CFG) in function jump_table_wrong_limit +// CFG: GS-PAUTH: Warning: possibly imprecise CFG, the analysis quality may be degraded in this function. According to BOLT, unreachable code is found in function jump_table_wrong_limit // CHECK-NOT: jump_table_wrong_limit paciasp cmp x16, #0x1000 @@ -634,7 +634,7 @@ jump_table_wrong_imm_3: skip_cfi_instructions: .cfi_startproc // CHECK-NOT: skip_cfi_instructions -// CFG: GS-PAUTH: Warning: the function has unreachable basic blocks (possibly incomplete CFG) in function skip_cfi_instructions +// CFG: GS-PAUTH: Warning: possibly imprecise CFG, the analysis quality may be degraded in this function. According to BOLT, unreachable code is found in function skip_cfi_instructions // CHECK-NOT: skip_cfi_instructions paciasp cmp x16, #0x2 >From fb4ed5bb184bed623ad052ae849532f7d12c840d Mon Sep 17 00:00:00 2001 From: Anatoly Trosinenko <[email protected]> Date: Tue, 13 May 2025 19:50:41 +0300 Subject: [PATCH 4/4] [BOLT] Gadget scanner: optionally assume auth traps on failure On AArch64 it is possible for an auth instruction to either return an invalid address value on failure (without FEAT_FPAC) or generate an error (with FEAT_FPAC). It thus may be possible to never emit explicit pointer checks, if the target CPU is known to support FEAT_FPAC. This commit implements an --auth-traps-on-failure command line option, which essentially makes "safe-to-dereference" and "trusted" register properties identical and disables scanning for authentication oracles completely. --- bolt/lib/Passes/PAuthGadgetScanner.cpp | 112 +++++++---- .../binary-analysis/AArch64/cmdline-args.test | 1 + .../AArch64/gs-pauth-authentication-oracles.s | 6 +- .../binary-analysis/AArch64/gs-pauth-calls.s | 5 +- .../AArch64/gs-pauth-debug-output.s | 177 ++++++++++------- .../AArch64/gs-pauth-jump-table.s | 6 +- .../AArch64/gs-pauth-signing-oracles.s | 54 ++--- .../AArch64/gs-pauth-tail-calls.s | 184 +++++++++--------- 8 files changed, 318 insertions(+), 227 deletions(-) diff --git a/bolt/lib/Passes/PAuthGadgetScanner.cpp b/bolt/lib/Passes/PAuthGadgetScanner.cpp index 3a6c3e3d925cf..4fc0bf2ae54a9 100644 --- a/bolt/lib/Passes/PAuthGadgetScanner.cpp +++ b/bolt/lib/Passes/PAuthGadgetScanner.cpp @@ -14,6 +14,7 @@ #include "bolt/Passes/PAuthGadgetScanner.h" #include "bolt/Core/ParallelUtilities.h" #include "bolt/Passes/DataflowAnalysis.h" +#include "bolt/Utils/CommandLineOpts.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallSet.h" #include "llvm/MC/MCInst.h" @@ -26,6 +27,11 @@ namespace llvm { namespace bolt { namespace PAuthGadgetScanner { +static cl::opt<bool> AuthTrapsOnFailure( + "auth-traps-on-failure", + cl::desc("Assume authentication instructions always trap on failure"), + cl::cat(opts::BinaryAnalysisCategory)); + [[maybe_unused]] static void traceInst(const BinaryContext &BC, StringRef Label, const MCInst &MI) { dbgs() << " " << Label << ": "; @@ -364,6 +370,34 @@ class SrcSafetyAnalysis { return Clobbered; } + std::optional<MCPhysReg> getRegMadeTrustedByChecking(const MCInst &Inst, + SrcState Cur) const { + // This functions cannot return multiple registers. This is never the case + // on AArch64. + std::optional<MCPhysReg> RegCheckedByInst = + BC.MIB->getAuthCheckedReg(Inst, /*MayOverwrite=*/false); + if (RegCheckedByInst && Cur.SafeToDerefRegs[*RegCheckedByInst]) + return *RegCheckedByInst; + + auto It = CheckerSequenceInfo.find(&Inst); + if (It == CheckerSequenceInfo.end()) + return std::nullopt; + + MCPhysReg RegCheckedBySequence = It->second.first; + const MCInst *FirstCheckerInst = It->second.second; + + // FirstCheckerInst should belong to the same basic block (see the + // assertion in DataflowSrcSafetyAnalysis::run()), meaning it was + // deterministically processed a few steps before this instruction. + const SrcState &StateBeforeChecker = getStateBefore(*FirstCheckerInst); + + // The sequence checks the register, but it should be authenticated before. + if (!StateBeforeChecker.SafeToDerefRegs[RegCheckedBySequence]) + return std::nullopt; + + return RegCheckedBySequence; + } + // Returns all registers that can be treated as if they are written by an // authentication instruction. SmallVector<MCPhysReg> getRegsMadeSafeToDeref(const MCInst &Point, @@ -386,18 +420,38 @@ class SrcSafetyAnalysis { Regs.push_back(DstAndSrc->first); } + // Make sure explicit checker sequence keeps register safe-to-dereference + // when the register would be clobbered according to the regular rules: + // + // ; LR is safe to dereference here + // mov x16, x30 ; start of the sequence, LR is s-t-d right before + // xpaclri ; clobbers LR, LR is not safe anymore + // cmp x30, x16 + // b.eq 1f ; end of the sequence: LR is marked as trusted + // brk 0x1234 + // 1: + // ; at this point LR would be marked as trusted, + // ; but not safe-to-dereference + // + // or even just + // + // ; X1 is safe to dereference here + // ldr x0, [x1, #8]! + // ; X1 is trusted here, but it was clobbered due to address write-back + if (auto CheckedReg = getRegMadeTrustedByChecking(Point, Cur)) + Regs.push_back(*CheckedReg); + return Regs; } // Returns all registers made trusted by this instruction. SmallVector<MCPhysReg> getRegsMadeTrusted(const MCInst &Point, const SrcState &Cur) const { + assert(!AuthTrapsOnFailure && "Use getRegsMadeSafeToDeref instead"); SmallVector<MCPhysReg> Regs; // An authenticated pointer can be checked, or - std::optional<MCPhysReg> CheckedReg = - BC.MIB->getAuthCheckedReg(Point, /*MayOverwrite=*/false); - if (CheckedReg && Cur.SafeToDerefRegs[*CheckedReg]) + if (auto CheckedReg = getRegMadeTrustedByChecking(Point, Cur)) Regs.push_back(*CheckedReg); // ... a pointer can be authenticated by an instruction that always checks @@ -408,19 +462,6 @@ class SrcSafetyAnalysis { if (AutReg && IsChecked) Regs.push_back(*AutReg); - if (CheckerSequenceInfo.contains(&Point)) { - MCPhysReg CheckedReg; - const MCInst *FirstCheckerInst; - std::tie(CheckedReg, FirstCheckerInst) = CheckerSequenceInfo.at(&Point); - - // FirstCheckerInst should belong to the same basic block (see the - // assertion in DataflowSrcSafetyAnalysis::run()), meaning it was - // deterministically processed a few steps before this instruction. - const SrcState &StateBeforeChecker = getStateBefore(*FirstCheckerInst); - if (StateBeforeChecker.SafeToDerefRegs[CheckedReg]) - Regs.push_back(CheckedReg); - } - // ... a safe address can be materialized, or if (auto NewAddrReg = BC.MIB->getMaterializedAddressRegForPtrAuth(Point)) Regs.push_back(*NewAddrReg); @@ -463,28 +504,11 @@ class SrcSafetyAnalysis { BitVector Clobbered = getClobberedRegs(Point); SmallVector<MCPhysReg> NewSafeToDerefRegs = getRegsMadeSafeToDeref(Point, Cur); - SmallVector<MCPhysReg> NewTrustedRegs = getRegsMadeTrusted(Point, Cur); - - // Ideally, being trusted is a strictly stronger property than being - // safe-to-dereference. To simplify the computation of Next state, enforce - // this for NewSafeToDerefRegs and NewTrustedRegs. Additionally, this - // fixes the properly for "cumulative" register states in tricky cases - // like the following: - // - // ; LR is safe to dereference here - // mov x16, x30 ; start of the sequence, LR is s-t-d right before - // xpaclri ; clobbers LR, LR is not safe anymore - // cmp x30, x16 - // b.eq 1f ; end of the sequence: LR is marked as trusted - // brk 0x1234 - // 1: - // ; at this point LR would be marked as trusted, - // ; but not safe-to-dereference - // - for (auto TrustedReg : NewTrustedRegs) { - if (!is_contained(NewSafeToDerefRegs, TrustedReg)) - NewSafeToDerefRegs.push_back(TrustedReg); - } + // If authentication instructions trap on failure, safe-to-dereference + // registers are always trusted. + SmallVector<MCPhysReg> NewTrustedRegs = + AuthTrapsOnFailure ? NewSafeToDerefRegs + : getRegsMadeTrusted(Point, Cur); // Then, compute the state after this instruction is executed. SrcState Next = Cur; @@ -521,6 +545,11 @@ class SrcSafetyAnalysis { dbgs() << ")\n"; }); + // Being trusted is a strictly stronger property than being + // safe-to-dereference. + assert(!Next.TrustedRegs.test(Next.SafeToDerefRegs) && + "SafeToDerefRegs should contain all TrustedRegs"); + return Next; } @@ -1136,6 +1165,11 @@ class DataflowDstSafetyAnalysis } void run() override { + // As long as DstSafetyAnalysis is only computed to detect authentication + // oracles, it is a waste of time to compute it when authentication + // instructions are known to always trap on failure. + assert(!AuthTrapsOnFailure && + "DstSafetyAnalysis is useless with faulting auth"); for (BinaryBasicBlock &BB : Func) { if (auto CheckerInfo = BC.MIB->getAuthCheckedReg(BB)) { LLVM_DEBUG({ @@ -1587,6 +1621,8 @@ void FunctionAnalysisContext::findUnsafeDefs( SmallVector<PartialReport<MCPhysReg>> &Reports) { if (PacRetGadgetsOnly) return; + if (AuthTrapsOnFailure) + return; auto Analysis = DstSafetyAnalysis::create(BF, AllocatorId, {}); LLVM_DEBUG({ dbgs() << "Running dst register safety analysis...\n"; }); diff --git a/bolt/test/binary-analysis/AArch64/cmdline-args.test b/bolt/test/binary-analysis/AArch64/cmdline-args.test index 3e70b2c0d3bb9..9660ad3bf80f7 100644 --- a/bolt/test/binary-analysis/AArch64/cmdline-args.test +++ b/bolt/test/binary-analysis/AArch64/cmdline-args.test @@ -33,6 +33,7 @@ HELP-NEXT: OPTIONS: HELP-EMPTY: HELP-NEXT: BinaryAnalysis options: HELP-EMPTY: +HELP-NEXT: --auth-traps-on-failure - Assume authentication instructions always trap on failure HELP-NEXT: --scanners=<value> - which gadget scanners to run HELP-NEXT: =pacret - pac-ret: return address protection (subset of "pauth") HELP-NEXT: =pauth - All Pointer Authentication scanners diff --git a/bolt/test/binary-analysis/AArch64/gs-pauth-authentication-oracles.s b/bolt/test/binary-analysis/AArch64/gs-pauth-authentication-oracles.s index f44ba21b9d484..9f580b66f47c7 100644 --- a/bolt/test/binary-analysis/AArch64/gs-pauth-authentication-oracles.s +++ b/bolt/test/binary-analysis/AArch64/gs-pauth-authentication-oracles.s @@ -1,6 +1,7 @@ // RUN: %clang %cflags -march=armv8.3-a %s -o %t.exe -// RUN: llvm-bolt-binary-analysis --scanners=pacret %t.exe 2>&1 | FileCheck -check-prefix=PACRET %s -// RUN: llvm-bolt-binary-analysis --scanners=pauth %t.exe 2>&1 | FileCheck %s +// RUN: llvm-bolt-binary-analysis --scanners=pacret %t.exe 2>&1 | FileCheck -check-prefix=PACRET %s +// RUN: llvm-bolt-binary-analysis --scanners=pauth --auth-traps-on-failure %t.exe 2>&1 | FileCheck -check-prefix=FPAC %s +// RUN: llvm-bolt-binary-analysis --scanners=pauth %t.exe 2>&1 | FileCheck %s // The detection of compiler-generated explicit pointer checks is tested in // gs-pauth-address-checks.s, for that reason only test here "dummy-load" and @@ -8,6 +9,7 @@ // detected per-instruction and per-BB. // PACRET-NOT: authentication oracle found in function +// FPAC-NOT: authentication oracle found in function .text diff --git a/bolt/test/binary-analysis/AArch64/gs-pauth-calls.s b/bolt/test/binary-analysis/AArch64/gs-pauth-calls.s index fb0bc7cff2377..5e88e105a33f0 100644 --- a/bolt/test/binary-analysis/AArch64/gs-pauth-calls.s +++ b/bolt/test/binary-analysis/AArch64/gs-pauth-calls.s @@ -1,6 +1,7 @@ // RUN: %clang %cflags -march=armv8.3-a %s -o %t.exe -// RUN: llvm-bolt-binary-analysis --scanners=pacret %t.exe 2>&1 | FileCheck -check-prefix=PACRET %s -// RUN: llvm-bolt-binary-analysis --scanners=pauth %t.exe 2>&1 | FileCheck %s +// RUN: llvm-bolt-binary-analysis --scanners=pacret %t.exe 2>&1 | FileCheck -check-prefix=PACRET %s +// RUN: llvm-bolt-binary-analysis --scanners=pauth --auth-traps-on-failure %t.exe 2>&1 | FileCheck %s +// RUN: llvm-bolt-binary-analysis --scanners=pauth %t.exe 2>&1 | FileCheck %s // PACRET-NOT: non-protected call found in function diff --git a/bolt/test/binary-analysis/AArch64/gs-pauth-debug-output.s b/bolt/test/binary-analysis/AArch64/gs-pauth-debug-output.s index b1cec7f92ad05..ee8521ff1f810 100644 --- a/bolt/test/binary-analysis/AArch64/gs-pauth-debug-output.s +++ b/bolt/test/binary-analysis/AArch64/gs-pauth-debug-output.s @@ -1,10 +1,14 @@ // REQUIRES: asserts // // RUN: %clang %cflags -march=armv8.3-a %s -o %t.exe -// RUN: llvm-bolt-binary-analysis --scanners=pacret -no-threads \ -// RUN: -debug-only bolt-pauth-scanner %t.exe 2>&1 | FileCheck %s -// RUN: llvm-bolt-binary-analysis --scanners=pauth -no-threads \ -// RUN: -debug-only bolt-pauth-scanner %t.exe 2>&1 | FileCheck -check-prefixes=CHECK,PAUTH %s +// RUN: llvm-bolt-binary-analysis --scanners=pacret --no-threads \ +// RUN: -debug-only bolt-pauth-scanner %t.exe 2>&1 | FileCheck -check-prefixes=CHECK,NOFPAC %s +// RUN: llvm-bolt-binary-analysis --scanners=pacret --no-threads --auth-traps-on-failure \ +// RUN: -debug-only bolt-pauth-scanner %t.exe 2>&1 | FileCheck -check-prefixes=CHECK,FPAC %s +// RUN: llvm-bolt-binary-analysis --scanners=pauth --no-threads \ +// RUN: -debug-only bolt-pauth-scanner %t.exe 2>&1 | FileCheck -check-prefixes=CHECK,NOFPAC,AUTH-ORACLES,PAUTH %s +// RUN: llvm-bolt-binary-analysis --scanners=pauth --no-threads --auth-traps-on-failure \ +// RUN: -debug-only bolt-pauth-scanner %t.exe 2>&1 | FileCheck -check-prefixes=CHECK,FPAC,PAUTH %s // Check the debug output generated by PAuth gadget scanner to make sure the // that output is kept meaningful and to provide an overview of what happens @@ -61,30 +65,54 @@ simple: // CHECK-NEXT: State 1: src-state<empty> // CHECK-NEXT: State 2: src-state<SafeToDerefRegs: , TrustedRegs: , Insts: >) // CHECK-NEXT: merged state: src-state<SafeToDerefRegs: , TrustedRegs: , Insts: > -// CHECK-NEXT: SrcSafetyAnalysis::ComputeNext( autiza x0, src-state<SafeToDerefRegs: , TrustedRegs: , Insts: >) -// CHECK-NEXT: .. result: (src-state<SafeToDerefRegs: W0 X0 W0_HI , TrustedRegs: , Insts: >) -// CHECK-NEXT: SrcSafetyAnalysis::ComputeNext( blr x0, src-state<SafeToDerefRegs: W0 X0 W0_HI , TrustedRegs: , Insts: >) -// CHECK-NEXT: .. result: (src-state<SafeToDerefRegs: , TrustedRegs: , Insts: >) -// CHECK-NEXT: SrcSafetyAnalysis::ComputeNext( ldp x29, x30, [sp], #0x10, src-state<SafeToDerefRegs: , TrustedRegs: , Insts: >) -// CHECK-NEXT: .. result: (src-state<SafeToDerefRegs: , TrustedRegs: , Insts: >) -// CHECK-NEXT: SrcSafetyAnalysis::ComputeNext( hint #29, src-state<SafeToDerefRegs: , TrustedRegs: , Insts: >) -// CHECK-NEXT: .. result: (src-state<SafeToDerefRegs: LR W30 W30_HI , TrustedRegs: , Insts: >) -// CHECK-NEXT: SrcSafetyAnalysis::ComputeNext( ret x30, src-state<SafeToDerefRegs: LR W30 W30_HI , TrustedRegs: , Insts: >) -// CHECK-NEXT: .. result: (src-state<SafeToDerefRegs: LR W30 W30_HI , TrustedRegs: , Insts: >) -// CHECK-NEXT: DataflowSrcSafetyAnalysis::Confluence( -// CHECK-NEXT: State 1: src-state<SafeToDerefRegs: , TrustedRegs: , Insts: > -// CHECK-NEXT: State 2: src-state<SafeToDerefRegs: , TrustedRegs: , Insts: >) -// CHECK-NEXT: merged state: src-state<SafeToDerefRegs: , TrustedRegs: , Insts: > -// CHECK-NEXT: SrcSafetyAnalysis::ComputeNext( autiza x0, src-state<SafeToDerefRegs: , TrustedRegs: , Insts: >) -// CHECK-NEXT: .. result: (src-state<SafeToDerefRegs: W0 X0 W0_HI , TrustedRegs: , Insts: >) -// CHECK-NEXT: SrcSafetyAnalysis::ComputeNext( blr x0, src-state<SafeToDerefRegs: W0 X0 W0_HI , TrustedRegs: , Insts: >) -// CHECK-NEXT: .. result: (src-state<SafeToDerefRegs: , TrustedRegs: , Insts: >) -// CHECK-NEXT: SrcSafetyAnalysis::ComputeNext( ldp x29, x30, [sp], #0x10, src-state<SafeToDerefRegs: , TrustedRegs: , Insts: >) -// CHECK-NEXT: .. result: (src-state<SafeToDerefRegs: , TrustedRegs: , Insts: >) -// CHECK-NEXT: SrcSafetyAnalysis::ComputeNext( hint #29, src-state<SafeToDerefRegs: , TrustedRegs: , Insts: >) -// CHECK-NEXT: .. result: (src-state<SafeToDerefRegs: LR W30 W30_HI , TrustedRegs: , Insts: >) -// CHECK-NEXT: SrcSafetyAnalysis::ComputeNext( ret x30, src-state<SafeToDerefRegs: LR W30 W30_HI , TrustedRegs: , Insts: >) -// CHECK-NEXT: .. result: (src-state<SafeToDerefRegs: LR W30 W30_HI , TrustedRegs: , Insts: >) +// NOFPAC-NEXT: SrcSafetyAnalysis::ComputeNext( autiza x0, src-state<SafeToDerefRegs: , TrustedRegs: , Insts: >) +// NOFPAC-NEXT: .. result: (src-state<SafeToDerefRegs: W0 X0 W0_HI , TrustedRegs: , Insts: >) +// NOFPAC-NEXT: SrcSafetyAnalysis::ComputeNext( blr x0, src-state<SafeToDerefRegs: W0 X0 W0_HI , TrustedRegs: , Insts: >) +// NOFPAC-NEXT: .. result: (src-state<SafeToDerefRegs: , TrustedRegs: , Insts: >) +// NOFPAC-NEXT: SrcSafetyAnalysis::ComputeNext( ldp x29, x30, [sp], #0x10, src-state<SafeToDerefRegs: , TrustedRegs: , Insts: >) +// NOFPAC-NEXT: .. result: (src-state<SafeToDerefRegs: , TrustedRegs: , Insts: >) +// NOFPAC-NEXT: SrcSafetyAnalysis::ComputeNext( hint #29, src-state<SafeToDerefRegs: , TrustedRegs: , Insts: >) +// NOFPAC-NEXT: .. result: (src-state<SafeToDerefRegs: LR W30 W30_HI , TrustedRegs: , Insts: >) +// NOFPAC-NEXT: SrcSafetyAnalysis::ComputeNext( ret x30, src-state<SafeToDerefRegs: LR W30 W30_HI , TrustedRegs: , Insts: >) +// NOFPAC-NEXT: .. result: (src-state<SafeToDerefRegs: LR W30 W30_HI , TrustedRegs: , Insts: >) +// NOFPAC-NEXT: DataflowSrcSafetyAnalysis::Confluence( +// NOFPAC-NEXT: State 1: src-state<SafeToDerefRegs: , TrustedRegs: , Insts: > +// NOFPAC-NEXT: State 2: src-state<SafeToDerefRegs: , TrustedRegs: , Insts: >) +// NOFPAC-NEXT: merged state: src-state<SafeToDerefRegs: , TrustedRegs: , Insts: > +// NOFPAC-NEXT: SrcSafetyAnalysis::ComputeNext( autiza x0, src-state<SafeToDerefRegs: , TrustedRegs: , Insts: >) +// NOFPAC-NEXT: .. result: (src-state<SafeToDerefRegs: W0 X0 W0_HI , TrustedRegs: , Insts: >) +// NOFPAC-NEXT: SrcSafetyAnalysis::ComputeNext( blr x0, src-state<SafeToDerefRegs: W0 X0 W0_HI , TrustedRegs: , Insts: >) +// NOFPAC-NEXT: .. result: (src-state<SafeToDerefRegs: , TrustedRegs: , Insts: >) +// NOFPAC-NEXT: SrcSafetyAnalysis::ComputeNext( ldp x29, x30, [sp], #0x10, src-state<SafeToDerefRegs: , TrustedRegs: , Insts: >) +// NOFPAC-NEXT: .. result: (src-state<SafeToDerefRegs: , TrustedRegs: , Insts: >) +// NOFPAC-NEXT: SrcSafetyAnalysis::ComputeNext( hint #29, src-state<SafeToDerefRegs: , TrustedRegs: , Insts: >) +// NOFPAC-NEXT: .. result: (src-state<SafeToDerefRegs: LR W30 W30_HI , TrustedRegs: , Insts: >) +// NOFPAC-NEXT: SrcSafetyAnalysis::ComputeNext( ret x30, src-state<SafeToDerefRegs: LR W30 W30_HI , TrustedRegs: , Insts: >) +// NOFPAC-NEXT: .. result: (src-state<SafeToDerefRegs: LR W30 W30_HI , TrustedRegs: , Insts: >) +// FPAC-NEXT: SrcSafetyAnalysis::ComputeNext( autiza x0, src-state<SafeToDerefRegs: , TrustedRegs: , Insts: >) +// FPAC-NEXT: .. result: (src-state<SafeToDerefRegs: W0 X0 W0_HI , TrustedRegs: W0 X0 W0_HI , Insts: >) +// FPAC-NEXT: SrcSafetyAnalysis::ComputeNext( blr x0, src-state<SafeToDerefRegs: W0 X0 W0_HI , TrustedRegs: W0 X0 W0_HI , Insts: >) +// FPAC-NEXT: .. result: (src-state<SafeToDerefRegs: , TrustedRegs: , Insts: >) +// FPAC-NEXT: SrcSafetyAnalysis::ComputeNext( ldp x29, x30, [sp], #0x10, src-state<SafeToDerefRegs: , TrustedRegs: , Insts: >) +// FPAC-NEXT: .. result: (src-state<SafeToDerefRegs: , TrustedRegs: , Insts: >) +// FPAC-NEXT: SrcSafetyAnalysis::ComputeNext( hint #29, src-state<SafeToDerefRegs: , TrustedRegs: , Insts: >) +// FPAC-NEXT: .. result: (src-state<SafeToDerefRegs: LR W30 W30_HI , TrustedRegs: LR W30 W30_HI , Insts: >) +// FPAC-NEXT: SrcSafetyAnalysis::ComputeNext( ret x30, src-state<SafeToDerefRegs: LR W30 W30_HI , TrustedRegs: LR W30 W30_HI , Insts: >) +// FPAC-NEXT: .. result: (src-state<SafeToDerefRegs: LR W30 W30_HI , TrustedRegs: LR W30 W30_HI , Insts: >) +// FPAC-NEXT: DataflowSrcSafetyAnalysis::Confluence( +// FPAC-NEXT: State 1: src-state<SafeToDerefRegs: , TrustedRegs: , Insts: > +// FPAC-NEXT: State 2: src-state<SafeToDerefRegs: , TrustedRegs: , Insts: >) +// FPAC-NEXT: merged state: src-state<SafeToDerefRegs: , TrustedRegs: , Insts: > +// FPAC-NEXT: SrcSafetyAnalysis::ComputeNext( autiza x0, src-state<SafeToDerefRegs: , TrustedRegs: , Insts: >) +// FPAC-NEXT: .. result: (src-state<SafeToDerefRegs: W0 X0 W0_HI , TrustedRegs: W0 X0 W0_HI , Insts: >) +// FPAC-NEXT: SrcSafetyAnalysis::ComputeNext( blr x0, src-state<SafeToDerefRegs: W0 X0 W0_HI , TrustedRegs: W0 X0 W0_HI , Insts: >) +// FPAC-NEXT: .. result: (src-state<SafeToDerefRegs: , TrustedRegs: , Insts: >) +// FPAC-NEXT: SrcSafetyAnalysis::ComputeNext( ldp x29, x30, [sp], #0x10, src-state<SafeToDerefRegs: , TrustedRegs: , Insts: >) +// FPAC-NEXT: .. result: (src-state<SafeToDerefRegs: , TrustedRegs: , Insts: >) +// FPAC-NEXT: SrcSafetyAnalysis::ComputeNext( hint #29, src-state<SafeToDerefRegs: , TrustedRegs: , Insts: >) +// FPAC-NEXT: .. result: (src-state<SafeToDerefRegs: LR W30 W30_HI , TrustedRegs: LR W30 W30_HI , Insts: >) +// FPAC-NEXT: SrcSafetyAnalysis::ComputeNext( ret x30, src-state<SafeToDerefRegs: LR W30 W30_HI , TrustedRegs: LR W30 W30_HI , Insts: >) +// FPAC-NEXT: .. result: (src-state<SafeToDerefRegs: LR W30 W30_HI , TrustedRegs: LR W30 W30_HI , Insts: >) // CHECK-NEXT: After src register safety analysis: // CHECK-NEXT: Binary Function "simple" { // CHECK-NEXT: Number : 1 @@ -255,53 +283,56 @@ auth_oracle: // ... // CHECK: End of Function "auth_oracle" // ... -// PAUTH: Running dst register safety analysis... -// PAUTH-NEXT: DstSafetyAnalysis::ComputeNext( ret x30, dst-state<CannotEscapeUnchecked: , Insts: >) -// PAUTH-NEXT: .. result: (dst-state<CannotEscapeUnchecked: LR W30 W30_HI , Insts: >) -// PAUTH-NEXT: DstSafetyAnalysis::ComputeNext( autia x0, x1, dst-state<CannotEscapeUnchecked: LR W30 W30_HI , Insts: >) -// PAUTH-NEXT: .. result: (dst-state<CannotEscapeUnchecked: LR W30 W30_HI , Insts: >) -// PAUTH-NEXT: After dst register safety analysis: -// PAUTH-NEXT: Binary Function "auth_oracle" { -// PAUTH-NEXT: Number : 4 -// PAUTH-NEXT: State : CFG constructed +// FPAC-NOT: Running dst register safety analysis +// FPAC-NOT: DstSafetyAnalysis::ComputeNext +// FPAC-NOT: {{.*dst-state.*}} +// AUTH-ORACLES: Running dst register safety analysis... +// AUTH-ORACLES-NEXT: DstSafetyAnalysis::ComputeNext( ret x30, dst-state<CannotEscapeUnchecked: , Insts: >) +// AUTH-ORACLES-NEXT: .. result: (dst-state<CannotEscapeUnchecked: LR W30 W30_HI , Insts: >) +// AUTH-ORACLES-NEXT: DstSafetyAnalysis::ComputeNext( autia x0, x1, dst-state<CannotEscapeUnchecked: LR W30 W30_HI , Insts: >) +// AUTH-ORACLES-NEXT: .. result: (dst-state<CannotEscapeUnchecked: LR W30 W30_HI , Insts: >) +// AUTH-ORACLES-NEXT: After dst register safety analysis: +// AUTH-ORACLES-NEXT: Binary Function "auth_oracle" { +// AUTH-ORACLES-NEXT: Number : 4 +// AUTH-ORACLES-NEXT: State : CFG constructed // ... -// PAUTH: BB Layout : [[BB0]] -// PAUTH-NEXT: } -// PAUTH-NEXT: [[BB0]] (2 instructions, align : 1) -// PAUTH-NEXT: Entry Point -// PAUTH-NEXT: 00000000: autia x0, x1 # DataflowDstSafetyAnalysis: dst-state<CannotEscapeUnchecked: BitVector, Insts: > -// PAUTH-NEXT: 00000004: ret # DataflowDstSafetyAnalysis: dst-state<CannotEscapeUnchecked: BitVector, Insts: > -// PAUTH-EMPTY: -// PAUTH-NEXT: DWARF CFI Instructions: -// PAUTH-NEXT: <empty> -// PAUTH-NEXT: End of Function "auth_oracle" -// PAUTH-EMPTY: -// PAUTH-NEXT: Found auth inst: 00000000: autia x0, x1 # DataflowDstSafetyAnalysis: dst-state<CannotEscapeUnchecked: BitVector, Insts: > -// PAUTH-NEXT: Authenticated reg: X0 -// PAUTH-NEXT: safe output registers: LR W30 W30_HI{{[ \t]*$}} -// PAUTH-EMPTY: -// PAUTH-NEXT: Running detailed dst register safety analysis... -// PAUTH-NEXT: DstSafetyAnalysis::ComputeNext( ret x30, dst-state<CannotEscapeUnchecked: , Insts: [0]()>) -// PAUTH-NEXT: .. result: (dst-state<CannotEscapeUnchecked: LR W30 W30_HI , Insts: [0]()>) -// PAUTH-NEXT: DstSafetyAnalysis::ComputeNext( autia x0, x1, dst-state<CannotEscapeUnchecked: LR W30 W30_HI , Insts: [0]()>) -// PAUTH-NEXT: .. result: (dst-state<CannotEscapeUnchecked: LR W30 W30_HI , Insts: [0](0x{{[0-9a-f]+}} )>) -// PAUTH-NEXT: After detailed dst register safety analysis: -// PAUTH-NEXT: Binary Function "auth_oracle" { -// PAUTH-NEXT: Number : 4 -// PAUTH-NEXT: State : CFG constructed +// AUTH-ORACLES: BB Layout : [[BB0]] +// AUTH-ORACLES-NEXT: } +// AUTH-ORACLES-NEXT: [[BB0]] (2 instructions, align : 1) +// AUTH-ORACLES-NEXT: Entry Point +// AUTH-ORACLES-NEXT: 00000000: autia x0, x1 # DataflowDstSafetyAnalysis: dst-state<CannotEscapeUnchecked: BitVector, Insts: > +// AUTH-ORACLES-NEXT: 00000004: ret # DataflowDstSafetyAnalysis: dst-state<CannotEscapeUnchecked: BitVector, Insts: > +// AUTH-ORACLES-EMPTY: +// AUTH-ORACLES-NEXT: DWARF CFI Instructions: +// AUTH-ORACLES-NEXT: <empty> +// AUTH-ORACLES-NEXT: End of Function "auth_oracle" +// AUTH-ORACLES-EMPTY: +// AUTH-ORACLES-NEXT: Found auth inst: 00000000: autia x0, x1 # DataflowDstSafetyAnalysis: dst-state<CannotEscapeUnchecked: BitVector, Insts: > +// AUTH-ORACLES-NEXT: Authenticated reg: X0 +// AUTH-ORACLES-NEXT: safe output registers: LR W30 W30_HI{{[ \t]*$}} +// AUTH-ORACLES-EMPTY: +// AUTH-ORACLES-NEXT: Running detailed dst register safety analysis... +// AUTH-ORACLES-NEXT: DstSafetyAnalysis::ComputeNext( ret x30, dst-state<CannotEscapeUnchecked: , Insts: [0]()>) +// AUTH-ORACLES-NEXT: .. result: (dst-state<CannotEscapeUnchecked: LR W30 W30_HI , Insts: [0]()>) +// AUTH-ORACLES-NEXT: DstSafetyAnalysis::ComputeNext( autia x0, x1, dst-state<CannotEscapeUnchecked: LR W30 W30_HI , Insts: [0]()>) +// AUTH-ORACLES-NEXT: .. result: (dst-state<CannotEscapeUnchecked: LR W30 W30_HI , Insts: [0](0x{{[0-9a-f]+}} )>) +// AUTH-ORACLES-NEXT: After detailed dst register safety analysis: +// AUTH-ORACLES-NEXT: Binary Function "auth_oracle" { +// AUTH-ORACLES-NEXT: Number : 4 +// AUTH-ORACLES-NEXT: State : CFG constructed // ... -// PAUTH: BB Layout : [[BB0]] -// PAUTH-NEXT: } -// PAUTH-NEXT: [[BB0]] (2 instructions, align : 1) -// PAUTH-NEXT: Entry Point -// PAUTH-NEXT: 00000000: autia x0, x1 # DataflowDstSafetyAnalysis: dst-state<CannotEscapeUnchecked: BitVector, Insts: [0](0x{{[0-9a-f]+}} )> -// PAUTH-NEXT: 00000004: ret # DataflowDstSafetyAnalysis: dst-state<CannotEscapeUnchecked: BitVector, Insts: [0]()> -// PAUTH-EMPTY: -// PAUTH-NEXT: DWARF CFI Instructions: -// PAUTH-NEXT: <empty> -// PAUTH-NEXT: End of Function "auth_oracle" -// PAUTH-EMPTY: -// PAUTH-NEXT: Attaching leakage info to: 00000000: autia x0, x1 # DataflowDstSafetyAnalysis: dst-state<CannotEscapeUnchecked: BitVector, Insts: [0](0x{{[0-9a-f]+}} )> +// AUTH-ORACLES: BB Layout : [[BB0]] +// AUTH-ORACLES-NEXT: } +// AUTH-ORACLES-NEXT: [[BB0]] (2 instructions, align : 1) +// AUTH-ORACLES-NEXT: Entry Point +// AUTH-ORACLES-NEXT: 00000000: autia x0, x1 # DataflowDstSafetyAnalysis: dst-state<CannotEscapeUnchecked: BitVector, Insts: [0](0x{{[0-9a-f]+}} )> +// AUTH-ORACLES-NEXT: 00000004: ret # DataflowDstSafetyAnalysis: dst-state<CannotEscapeUnchecked: BitVector, Insts: [0]()> +// AUTH-ORACLES-EMPTY: +// AUTH-ORACLES-NEXT: DWARF CFI Instructions: +// AUTH-ORACLES-NEXT: <empty> +// AUTH-ORACLES-NEXT: End of Function "auth_oracle" +// AUTH-ORACLES-EMPTY: +// AUTH-ORACLES-NEXT: Attaching leakage info to: 00000000: autia x0, x1 # DataflowDstSafetyAnalysis: dst-state<CannotEscapeUnchecked: BitVector, Insts: [0](0x{{[0-9a-f]+}} )> // Gadget scanner should not crash on CFI instructions, including when debug-printing them. // Note that the particular debug output is not checked, but BOLT should be diff --git a/bolt/test/binary-analysis/AArch64/gs-pauth-jump-table.s b/bolt/test/binary-analysis/AArch64/gs-pauth-jump-table.s index 8af93ca8d9da5..6eab6112ea2d2 100644 --- a/bolt/test/binary-analysis/AArch64/gs-pauth-jump-table.s +++ b/bolt/test/binary-analysis/AArch64/gs-pauth-jump-table.s @@ -2,9 +2,11 @@ // Without -Wl,--emit-relocs BOLT refuses to create CFG information for the below functions. // RUN: %clang %cflags -march=armv8.3-a -Wl,--no-relax -Wl,--emit-relocs %s -o %t.exe -// RUN: llvm-bolt-binary-analysis --scanners=pauth %t.exe 2>&1 | FileCheck --check-prefixes=CHECK,CFG %s +// RUN: llvm-bolt-binary-analysis --scanners=pauth %t.exe 2>&1 | FileCheck --check-prefixes=CHECK,CFG %s +// RUN: llvm-bolt-binary-analysis --scanners=pauth --auth-traps-on-failure %t.exe 2>&1 | FileCheck --check-prefixes=CHECK,CFG %s // RUN: %clang %cflags -march=armv8.3-a -Wl,--no-relax %s -o %t.exe -// RUN: llvm-bolt-binary-analysis --scanners=pauth %t.exe 2>&1 | FileCheck --check-prefixes=CHECK,NOCFG %s +// RUN: llvm-bolt-binary-analysis --scanners=pauth %t.exe 2>&1 | FileCheck --check-prefixes=CHECK,NOCFG %s +// RUN: llvm-bolt-binary-analysis --scanners=pauth --auth-traps-on-failure %t.exe 2>&1 | FileCheck --check-prefixes=CHECK,NOCFG %s // FIXME: Labels could be further validated. Specifically, it could be checked // that the jump table itself is located in a read-only data section. diff --git a/bolt/test/binary-analysis/AArch64/gs-pauth-signing-oracles.s b/bolt/test/binary-analysis/AArch64/gs-pauth-signing-oracles.s index 4d4bb7b0fb251..7d908f234d852 100644 --- a/bolt/test/binary-analysis/AArch64/gs-pauth-signing-oracles.s +++ b/bolt/test/binary-analysis/AArch64/gs-pauth-signing-oracles.s @@ -1,6 +1,7 @@ // RUN: %clang %cflags -march=armv8.3-a+pauth-lr -Wl,--no-relax %s -o %t.exe -// RUN: llvm-bolt-binary-analysis --scanners=pacret %t.exe 2>&1 | FileCheck -check-prefix=PACRET %s -// RUN: llvm-bolt-binary-analysis --scanners=pauth %t.exe 2>&1 | FileCheck %s +// RUN: llvm-bolt-binary-analysis --scanners=pacret %t.exe 2>&1 | FileCheck -check-prefix=PACRET %s +// RUN: llvm-bolt-binary-analysis --scanners=pauth %t.exe 2>&1 | FileCheck -check-prefixes=CHECK,NOFPAC %s +// RUN: llvm-bolt-binary-analysis --scanners=pauth --auth-traps-on-failure %t.exe 2>&1 | FileCheck -check-prefixes=CHECK,FPAC %s // The detection of compiler-generated explicit pointer checks is tested in // gs-pauth-address-checks.s, for that reason only test here "dummy-load" and @@ -66,9 +67,10 @@ good_sign_auted_checked_brk: .globl bad_sign_authed_unchecked .type bad_sign_authed_unchecked,@function bad_sign_authed_unchecked: -// CHECK-LABEL: GS-PAUTH: signing oracle found in function bad_sign_authed_unchecked, basic block {{[^,]+}}, at address -// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: pacda x0, x1 -// CHECK-NEXT: The 0 instructions that write to the affected registers after any authentication are: +// FPAC-NOT: bad_sign_authed_unchecked +// NOFPAC-LABEL: GS-PAUTH: signing oracle found in function bad_sign_authed_unchecked, basic block {{[^,]+}}, at address +// NOFPAC-NEXT: The instruction is {{[0-9a-f]+}}: pacda x0, x1 +// NOFPAC-NEXT: The 0 instructions that write to the affected registers after any authentication are: autda x0, x2 pacda x0, x1 ret @@ -266,9 +268,10 @@ bad_call_between_checked_and_used: .globl bad_transition_check_then_auth .type bad_transition_check_then_auth,@function bad_transition_check_then_auth: -// CHECK-LABEL: GS-PAUTH: signing oracle found in function bad_transition_check_then_auth, basic block {{[^,]+}}, at address -// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: pacda x0, x1 -// CHECK-NEXT: The 0 instructions that write to the affected registers after any authentication are: +// FPAC-NOT: bad_transition_check_then_auth +// NOFPAC-LABEL: GS-PAUTH: signing oracle found in function bad_transition_check_then_auth, basic block {{[^,]+}}, at address +// NOFPAC-NEXT: The instruction is {{[0-9a-f]+}}: pacda x0, x1 +// NOFPAC-NEXT: The 0 instructions that write to the affected registers after any authentication are: ldr x2, [x0] autda x0, x2 pacda x0, x1 @@ -278,9 +281,10 @@ bad_transition_check_then_auth: .globl bad_transition_auth_then_auth .type bad_transition_auth_then_auth,@function bad_transition_auth_then_auth: -// CHECK-LABEL: GS-PAUTH: signing oracle found in function bad_transition_auth_then_auth, basic block {{[^,]+}}, at address -// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: pacda x0, x1 -// CHECK-NEXT: The 0 instructions that write to the affected registers after any authentication are: +// FPAC-NOT: bad_transition_auth_then_auth +// NOFPAC-LABEL: GS-PAUTH: signing oracle found in function bad_transition_auth_then_auth, basic block {{[^,]+}}, at address +// NOFPAC-NEXT: The instruction is {{[0-9a-f]+}}: pacda x0, x1 +// NOFPAC-NEXT: The 0 instructions that write to the affected registers after any authentication are: autda x0, x2 autda x0, x2 pacda x0, x1 @@ -363,9 +367,10 @@ good_sign_auted_checked_brk_multi_bb: .globl bad_sign_authed_unchecked_multi_bb .type bad_sign_authed_unchecked_multi_bb,@function bad_sign_authed_unchecked_multi_bb: -// CHECK-LABEL: GS-PAUTH: signing oracle found in function bad_sign_authed_unchecked_multi_bb, basic block {{[^,]+}}, at address -// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: pacda x0, x1 -// CHECK-NEXT: The 0 instructions that write to the affected registers after any authentication are: +// FPAC-NOT: bad_sign_authed_unchecked_multi_bb +// NOFPAC-LABEL: GS-PAUTH: signing oracle found in function bad_sign_authed_unchecked_multi_bb, basic block {{[^,]+}}, at address +// NOFPAC-NEXT: The instruction is {{[0-9a-f]+}}: pacda x0, x1 +// NOFPAC-NEXT: The 0 instructions that write to the affected registers after any authentication are: autda x0, x2 cbz x3, 1f ldr x2, [x0] @@ -534,9 +539,10 @@ good_sign_auted_checked_ldr_nocfg: .globl bad_sign_authed_unchecked_nocfg .type bad_sign_authed_unchecked_nocfg,@function bad_sign_authed_unchecked_nocfg: -// CHECK-LABEL: GS-PAUTH: signing oracle found in function bad_sign_authed_unchecked_nocfg, at address -// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: pacda x0, x1 -// CHECK-NEXT: The 0 instructions that write to the affected registers after any authentication are: +// FPAC-NOT: bad_sign_authed_unchecked_nocfg +// NOFPAC-LABEL: GS-PAUTH: signing oracle found in function bad_sign_authed_unchecked_nocfg, at address +// NOFPAC-NEXT: The instruction is {{[0-9a-f]+}}: pacda x0, x1 +// NOFPAC-NEXT: The 0 instructions that write to the affected registers after any authentication are: adr x3, 1f br x3 1: @@ -640,9 +646,10 @@ bad_clobber_between_checked_and_used_nocfg: .globl bad_transition_check_then_auth_nocfg .type bad_transition_check_then_auth_nocfg,@function bad_transition_check_then_auth_nocfg: -// CHECK-LABEL: GS-PAUTH: signing oracle found in function bad_transition_check_then_auth_nocfg, at address -// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: pacda x0, x1 -// CHECK-NEXT: The 0 instructions that write to the affected registers after any authentication are: +// FPAC-NOT: bad_transition_check_then_auth_nocfg +// NOFPAC-LABEL: GS-PAUTH: signing oracle found in function bad_transition_check_then_auth_nocfg, at address +// NOFPAC-NEXT: The instruction is {{[0-9a-f]+}}: pacda x0, x1 +// NOFPAC-NEXT: The 0 instructions that write to the affected registers after any authentication are: adr x3, 1f br x3 1: @@ -655,9 +662,10 @@ bad_transition_check_then_auth_nocfg: .globl bad_transition_auth_then_auth_nocfg .type bad_transition_auth_then_auth_nocfg,@function bad_transition_auth_then_auth_nocfg: -// CHECK-LABEL: GS-PAUTH: signing oracle found in function bad_transition_auth_then_auth_nocfg, at address -// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: pacda x0, x1 -// CHECK-NEXT: The 0 instructions that write to the affected registers after any authentication are: +// FPAC-NOT: bad_transition_auth_then_auth_nocfg +// NOFPAC-LABEL: GS-PAUTH: signing oracle found in function bad_transition_auth_then_auth_nocfg, at address +// NOFPAC-NEXT: The instruction is {{[0-9a-f]+}}: pacda x0, x1 +// NOFPAC-NEXT: The 0 instructions that write to the affected registers after any authentication are: adr x3, 1f br x3 1: diff --git a/bolt/test/binary-analysis/AArch64/gs-pauth-tail-calls.s b/bolt/test/binary-analysis/AArch64/gs-pauth-tail-calls.s index 2d3c2f1a632ca..59b7d929275a9 100644 --- a/bolt/test/binary-analysis/AArch64/gs-pauth-tail-calls.s +++ b/bolt/test/binary-analysis/AArch64/gs-pauth-tail-calls.s @@ -1,6 +1,7 @@ // RUN: %clang %cflags -Wl,--entry=_custom_start -march=armv8.3-a %s -o %t.exe -// RUN: llvm-bolt-binary-analysis --scanners=pacret %t.exe 2>&1 | FileCheck -check-prefix=PACRET %s -// RUN: llvm-bolt-binary-analysis --scanners=pauth %t.exe 2>&1 | FileCheck %s +// RUN: llvm-bolt-binary-analysis --scanners=pacret %t.exe 2>&1 | FileCheck -check-prefix=PACRET %s +// RUN: llvm-bolt-binary-analysis --scanners=pauth --auth-traps-on-failure %t.exe 2>&1 | FileCheck -check-prefixes=CHECK,FPAC %s +// RUN: llvm-bolt-binary-analysis --scanners=pauth %t.exe 2>&1 | FileCheck -check-prefixes=CHECK,NOFPAC %s // PACRET-NOT: untrusted link register found before tail call @@ -89,19 +90,20 @@ bad_indirect_tailcall_not_auted: .globl bad_direct_tailcall_untrusted .type bad_direct_tailcall_untrusted,@function bad_direct_tailcall_untrusted: -// CHECK-LABEL: GS-PAUTH: untrusted link register found before tail call in function bad_direct_tailcall_untrusted, basic block {{[^,]+}}, at address -// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: b callee # TAILCALL -// CHECK-NEXT: The 0 instructions that write to the affected registers after any authentication are: -// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_direct_tailcall_untrusted, basic block {{[^,]+}}, at address -// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: autiasp -// CHECK-NEXT: The 1 instructions that leak the affected registers are: -// CHECK-NEXT: 1. {{[0-9a-f]+}}: b callee # TAILCALL -// CHECK-NEXT: This happens in the following basic block: -// CHECK-NEXT: {{[0-9a-f]+}}: paciasp -// CHECK-NEXT: {{[0-9a-f]+}}: stp x29, x30, [sp, #-0x10]! -// CHECK-NEXT: {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10 -// CHECK-NEXT: {{[0-9a-f]+}}: autiasp -// CHECK-NEXT: {{[0-9a-f]+}}: b callee # TAILCALL +// FPAC-NOT: bad_direct_tailcall_untrusted +// NOFPAC-LABEL: GS-PAUTH: untrusted link register found before tail call in function bad_direct_tailcall_untrusted, basic block {{[^,]+}}, at address +// NOFPAC-NEXT: The instruction is {{[0-9a-f]+}}: b callee # TAILCALL +// NOFPAC-NEXT: The 0 instructions that write to the affected registers after any authentication are: +// NOFPAC-LABEL: GS-PAUTH: authentication oracle found in function bad_direct_tailcall_untrusted, basic block {{[^,]+}}, at address +// NOFPAC-NEXT: The instruction is {{[0-9a-f]+}}: autiasp +// NOFPAC-NEXT: The 1 instructions that leak the affected registers are: +// NOFPAC-NEXT: 1. {{[0-9a-f]+}}: b callee # TAILCALL +// NOFPAC-NEXT: This happens in the following basic block: +// NOFPAC-NEXT: {{[0-9a-f]+}}: paciasp +// NOFPAC-NEXT: {{[0-9a-f]+}}: stp x29, x30, [sp, #-0x10]! +// NOFPAC-NEXT: {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10 +// NOFPAC-NEXT: {{[0-9a-f]+}}: autiasp +// NOFPAC-NEXT: {{[0-9a-f]+}}: b callee # TAILCALL paciasp stp x29, x30, [sp, #-0x10]! ldp x29, x30, [sp], #0x10 @@ -114,19 +116,20 @@ bad_direct_tailcall_untrusted: bad_plt_tailcall_untrusted: // FIXME: Calls via PLT are disassembled incorrectly. Nevertheless, they are // still detected as tail calls. -// CHECK-LABEL: GS-PAUTH: untrusted link register found before tail call in function bad_plt_tailcall_untrusted, basic block {{[^,]+}}, at address -// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: b bad_indirect_tailcall_untrusted # TAILCALL -// CHECK-NEXT: The 0 instructions that write to the affected registers after any authentication are: -// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_plt_tailcall_untrusted, basic block {{[^,]+}}, at address -// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: autiasp -// CHECK-NEXT: The 1 instructions that leak the affected registers are: -// CHECK-NEXT: 1. {{[0-9a-f]+}}: b bad_indirect_tailcall_untrusted # TAILCALL -// CHECK-NEXT: This happens in the following basic block: -// CHECK-NEXT: {{[0-9a-f]+}}: paciasp -// CHECK-NEXT: {{[0-9a-f]+}}: stp x29, x30, [sp, #-0x10]! -// CHECK-NEXT: {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10 -// CHECK-NEXT: {{[0-9a-f]+}}: autiasp -// CHECK-NEXT: {{[0-9a-f]+}}: b bad_indirect_tailcall_untrusted # TAILCALL +// FPAC-NOT: bad_plt_tailcall_untrusted +// NOFPAC-LABEL: GS-PAUTH: untrusted link register found before tail call in function bad_plt_tailcall_untrusted, basic block {{[^,]+}}, at address +// NOFPAC-NEXT: The instruction is {{[0-9a-f]+}}: b bad_indirect_tailcall_untrusted # TAILCALL +// NOFPAC-NEXT: The 0 instructions that write to the affected registers after any authentication are: +// NOFPAC-LABEL: GS-PAUTH: authentication oracle found in function bad_plt_tailcall_untrusted, basic block {{[^,]+}}, at address +// NOFPAC-NEXT: The instruction is {{[0-9a-f]+}}: autiasp +// NOFPAC-NEXT: The 1 instructions that leak the affected registers are: +// NOFPAC-NEXT: 1. {{[0-9a-f]+}}: b bad_indirect_tailcall_untrusted # TAILCALL +// NOFPAC-NEXT: This happens in the following basic block: +// NOFPAC-NEXT: {{[0-9a-f]+}}: paciasp +// NOFPAC-NEXT: {{[0-9a-f]+}}: stp x29, x30, [sp, #-0x10]! +// NOFPAC-NEXT: {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10 +// NOFPAC-NEXT: {{[0-9a-f]+}}: autiasp +// NOFPAC-NEXT: {{[0-9a-f]+}}: b bad_indirect_tailcall_untrusted # TAILCALL paciasp stp x29, x30, [sp, #-0x10]! ldp x29, x30, [sp], #0x10 @@ -137,20 +140,21 @@ bad_plt_tailcall_untrusted: .globl bad_indirect_tailcall_untrusted .type bad_indirect_tailcall_untrusted,@function bad_indirect_tailcall_untrusted: -// CHECK-LABEL: GS-PAUTH: untrusted link register found before tail call in function bad_indirect_tailcall_untrusted, basic block {{[^,]+}}, at address -// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: br x0 # TAILCALL -// CHECK-NEXT: The 0 instructions that write to the affected registers after any authentication are: -// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_indirect_tailcall_untrusted, basic block {{[^,]+}}, at address -// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: autiasp -// CHECK-NEXT: The 1 instructions that leak the affected registers are: -// CHECK-NEXT: 1. {{[0-9a-f]+}}: br x0 # TAILCALL -// CHECK-NEXT: This happens in the following basic block: -// CHECK-NEXT: {{[0-9a-f]+}}: paciasp -// CHECK-NEXT: {{[0-9a-f]+}}: stp x29, x30, [sp, #-0x10]! -// CHECK-NEXT: {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10 -// CHECK-NEXT: {{[0-9a-f]+}}: autiasp -// CHECK-NEXT: {{[0-9a-f]+}}: autia x0, x1 -// CHECK-NEXT: {{[0-9a-f]+}}: br x0 # TAILCALL +// FPAC-NOT: bad_indirect_tailcall_untrusted +// NOFPAC-LABEL: GS-PAUTH: untrusted link register found before tail call in function bad_indirect_tailcall_untrusted, basic block {{[^,]+}}, at address +// NOFPAC-NEXT: The instruction is {{[0-9a-f]+}}: br x0 # TAILCALL +// NOFPAC-NEXT: The 0 instructions that write to the affected registers after any authentication are: +// NOFPAC-LABEL: GS-PAUTH: authentication oracle found in function bad_indirect_tailcall_untrusted, basic block {{[^,]+}}, at address +// NOFPAC-NEXT: The instruction is {{[0-9a-f]+}}: autiasp +// NOFPAC-NEXT: The 1 instructions that leak the affected registers are: +// NOFPAC-NEXT: 1. {{[0-9a-f]+}}: br x0 # TAILCALL +// NOFPAC-NEXT: This happens in the following basic block: +// NOFPAC-NEXT: {{[0-9a-f]+}}: paciasp +// NOFPAC-NEXT: {{[0-9a-f]+}}: stp x29, x30, [sp, #-0x10]! +// NOFPAC-NEXT: {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10 +// NOFPAC-NEXT: {{[0-9a-f]+}}: autiasp +// NOFPAC-NEXT: {{[0-9a-f]+}}: autia x0, x1 +// NOFPAC-NEXT: {{[0-9a-f]+}}: br x0 # TAILCALL paciasp stp x29, x30, [sp, #-0x10]! ldp x29, x30, [sp], #0x10 @@ -251,13 +255,14 @@ bad_indirect_tailcall_not_auted_multi_bb: .globl bad_direct_tailcall_untrusted_multi_bb .type bad_direct_tailcall_untrusted_multi_bb,@function bad_direct_tailcall_untrusted_multi_bb: -// CHECK-LABEL: GS-PAUTH: untrusted link register found before tail call in function bad_direct_tailcall_untrusted_multi_bb, basic block {{[^,]+}}, at address -// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: b callee # TAILCALL -// CHECK-NEXT: The 0 instructions that write to the affected registers after any authentication are: -// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_direct_tailcall_untrusted_multi_bb, basic block {{[^,]+}}, at address -// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: autiasp -// CHECK-NEXT: The 1 instructions that leak the affected registers are: -// CHECK-NEXT: 1. {{[0-9a-f]+}}: b callee # TAILCALL +// FPAC-NOT: bad_direct_tailcall_untrusted_multi_bb +// NOFPAC-LABEL: GS-PAUTH: untrusted link register found before tail call in function bad_direct_tailcall_untrusted_multi_bb, basic block {{[^,]+}}, at address +// NOFPAC-NEXT: The instruction is {{[0-9a-f]+}}: b callee # TAILCALL +// NOFPAC-NEXT: The 0 instructions that write to the affected registers after any authentication are: +// NOFPAC-LABEL: GS-PAUTH: authentication oracle found in function bad_direct_tailcall_untrusted_multi_bb, basic block {{[^,]+}}, at address +// NOFPAC-NEXT: The instruction is {{[0-9a-f]+}}: autiasp +// NOFPAC-NEXT: The 1 instructions that leak the affected registers are: +// NOFPAC-NEXT: 1. {{[0-9a-f]+}}: b callee # TAILCALL paciasp stp x29, x30, [sp, #-0x10]! ldp x29, x30, [sp], #0x10 @@ -271,12 +276,13 @@ bad_direct_tailcall_untrusted_multi_bb: .globl bad_indirect_tailcall_untrusted_multi_bb .type bad_indirect_tailcall_untrusted_multi_bb,@function bad_indirect_tailcall_untrusted_multi_bb: -// CHECK-LABEL: GS-PAUTH: untrusted link register found before tail call in function bad_indirect_tailcall_untrusted_multi_bb, basic block {{[^,]+}}, at address -// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: br x0 # UNKNOWN CONTROL FLOW -// CHECK-NEXT: The 0 instructions that write to the affected registers after any authentication are: -// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_indirect_tailcall_untrusted_multi_bb, basic block {{[^,]+}}, at address -// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: autiasp -// CHECK-NEXT: The 0 instructions that leak the affected registers are: +// FPAC-NOT: bad_indirect_tailcall_untrusted_multi_bb +// NOFPAC-LABEL: GS-PAUTH: untrusted link register found before tail call in function bad_indirect_tailcall_untrusted_multi_bb, basic block {{[^,]+}}, at address +// NOFPAC-NEXT: The instruction is {{[0-9a-f]+}}: br x0 # UNKNOWN CONTROL FLOW +// NOFPAC-NEXT: The 0 instructions that write to the affected registers after any authentication are: +// NOFPAC-LABEL: GS-PAUTH: authentication oracle found in function bad_indirect_tailcall_untrusted_multi_bb, basic block {{[^,]+}}, at address +// NOFPAC-NEXT: The instruction is {{[0-9a-f]+}}: autiasp +// NOFPAC-NEXT: The 0 instructions that leak the affected registers are: paciasp stp x29, x30, [sp, #-0x10]! ldp x29, x30, [sp], #0x10 @@ -397,13 +403,14 @@ bad_indirect_tailcall_not_auted_nocfg: .globl bad_direct_tailcall_untrusted_nocfg .type bad_direct_tailcall_untrusted_nocfg,@function bad_direct_tailcall_untrusted_nocfg: -// CHECK-LABEL: GS-PAUTH: untrusted link register found before tail call in function bad_direct_tailcall_untrusted_nocfg, at address -// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: b callee # TAILCALL -// CHECK-NEXT: The 0 instructions that write to the affected registers after any authentication are: -// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_direct_tailcall_untrusted_nocfg, at address -// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: autiasp -// CHECK-NEXT: The 1 instructions that leak the affected registers are: -// CHECK-NEXT: 1. {{[0-9a-f]+}}: b callee # TAILCALL +// FPAC-NOT: bad_direct_tailcall_untrusted_nocfg +// NOFPAC-LABEL: GS-PAUTH: untrusted link register found before tail call in function bad_direct_tailcall_untrusted_nocfg, at address +// NOFPAC-NEXT: The instruction is {{[0-9a-f]+}}: b callee # TAILCALL +// NOFPAC-NEXT: The 0 instructions that write to the affected registers after any authentication are: +// NOFPAC-LABEL: GS-PAUTH: authentication oracle found in function bad_direct_tailcall_untrusted_nocfg, at address +// NOFPAC-NEXT: The instruction is {{[0-9a-f]+}}: autiasp +// NOFPAC-NEXT: The 1 instructions that leak the affected registers are: +// NOFPAC-NEXT: 1. {{[0-9a-f]+}}: b callee # TAILCALL paciasp stp x29, x30, [sp, #-0x10]! adr x3, 1f @@ -419,13 +426,14 @@ bad_direct_tailcall_untrusted_nocfg: bad_plt_tailcall_untrusted_nocfg: // FIXME: Calls via PLT are disassembled incorrectly. Nevertheless, they are // still detected as tail calls. -// CHECK-LABEL: GS-PAUTH: untrusted link register found before tail call in function bad_plt_tailcall_untrusted_nocfg, at address -// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: b bad_indirect_tailcall_untrusted_nocfg # TAILCALL -// CHECK-NEXT: The 0 instructions that write to the affected registers after any authentication are: -// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_plt_tailcall_untrusted_nocfg, at address -// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: autiasp -// CHECK-NEXT: The 1 instructions that leak the affected registers are: -// CHECK-NEXT: 1. {{[0-9a-f]+}}: b bad_indirect_tailcall_untrusted_nocfg # TAILCALL +// FPAC-NOT: bad_plt_tailcall_untrusted_nocfg +// NOFPAC-LABEL: GS-PAUTH: untrusted link register found before tail call in function bad_plt_tailcall_untrusted_nocfg, at address +// NOFPAC-NEXT: The instruction is {{[0-9a-f]+}}: b bad_indirect_tailcall_untrusted_nocfg # TAILCALL +// NOFPAC-NEXT: The 0 instructions that write to the affected registers after any authentication are: +// NOFPAC-LABEL: GS-PAUTH: authentication oracle found in function bad_plt_tailcall_untrusted_nocfg, at address +// NOFPAC-NEXT: The instruction is {{[0-9a-f]+}}: autiasp +// NOFPAC-NEXT: The 1 instructions that leak the affected registers are: +// NOFPAC-NEXT: 1. {{[0-9a-f]+}}: b bad_indirect_tailcall_untrusted_nocfg # TAILCALL paciasp stp x29, x30, [sp, #-0x10]! adr x3, 1f @@ -441,11 +449,12 @@ bad_plt_tailcall_untrusted_nocfg: bad_indirect_tailcall_untrusted_nocfg: // Known false negative: ignoring UNKNOWN CONTROL FLOW without CFG. // Authentication oracle is found by a generic checker, though. -// CHECK-NOT: untrusted link register{{.*}}bad_indirect_tailcall_untrusted_nocfg -// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_indirect_tailcall_untrusted_nocfg, at address -// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: autiasp -// CHECK-NEXT: The 0 instructions that leak the affected registers are: -// CHECK-NOT: untrusted link register{{.*}}bad_indirect_tailcall_untrusted_nocfg +// FPAC-NOT: bad_indirect_tailcall_untrusted_nocfg +// NOFPAC-NOT: untrusted link register{{.*}}bad_indirect_tailcall_untrusted_nocfg +// NOFPAC-LABEL: GS-PAUTH: authentication oracle found in function bad_indirect_tailcall_untrusted_nocfg, at address +// NOFPAC-NEXT: The instruction is {{[0-9a-f]+}}: autiasp +// NOFPAC-NEXT: The 0 instructions that leak the affected registers are: +// NOFPAC-NOT: untrusted link register{{.*}}bad_indirect_tailcall_untrusted_nocfg paciasp stp x29, x30, [sp, #-0x10]! adr x3, 1f @@ -515,19 +524,20 @@ good_indirect_tailcall_no_clobber_v83: .globl bad_indirect_tailcall_untrusted_v83 .type bad_indirect_tailcall_untrusted_v83,@function bad_indirect_tailcall_untrusted_v83: -// CHECK-LABEL: GS-PAUTH: untrusted link register found before tail call in function bad_indirect_tailcall_untrusted_v83, basic block {{[^,]+}}, at address -// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: braa x0, x1 # TAILCALL -// CHECK-NEXT: The 0 instructions that write to the affected registers after any authentication are: -// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_indirect_tailcall_untrusted_v83, basic block {{[^,]+}}, at address -// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: autiasp -// CHECK-NEXT: The 1 instructions that leak the affected registers are: -// CHECK-NEXT: 1. {{[0-9a-f]+}}: braa x0, x1 # TAILCALL -// CHECK-NEXT: This happens in the following basic block: -// CHECK-NEXT: {{[0-9a-f]+}}: paciasp -// CHECK-NEXT: {{[0-9a-f]+}}: stp x29, x30, [sp, #-0x10]! -// CHECK-NEXT: {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10 -// CHECK-NEXT: {{[0-9a-f]+}}: autiasp -// CHECK-NEXT: {{[0-9a-f]+}}: braa x0, x1 # TAILCALL +// FPAC-NOT: bad_indirect_tailcall_untrusted_v83 +// NOFPAC-LABEL: GS-PAUTH: untrusted link register found before tail call in function bad_indirect_tailcall_untrusted_v83, basic block {{[^,]+}}, at address +// NOFPAC-NEXT: The instruction is {{[0-9a-f]+}}: braa x0, x1 # TAILCALL +// NOFPAC-NEXT: The 0 instructions that write to the affected registers after any authentication are: +// NOFPAC-LABEL: GS-PAUTH: authentication oracle found in function bad_indirect_tailcall_untrusted_v83, basic block {{[^,]+}}, at address +// NOFPAC-NEXT: The instruction is {{[0-9a-f]+}}: autiasp +// NOFPAC-NEXT: The 1 instructions that leak the affected registers are: +// NOFPAC-NEXT: 1. {{[0-9a-f]+}}: braa x0, x1 # TAILCALL +// NOFPAC-NEXT: This happens in the following basic block: +// NOFPAC-NEXT: {{[0-9a-f]+}}: paciasp +// NOFPAC-NEXT: {{[0-9a-f]+}}: stp x29, x30, [sp, #-0x10]! +// NOFPAC-NEXT: {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10 +// NOFPAC-NEXT: {{[0-9a-f]+}}: autiasp +// NOFPAC-NEXT: {{[0-9a-f]+}}: braa x0, x1 # TAILCALL paciasp stp x29, x30, [sp, #-0x10]! ldp x29, x30, [sp], #0x10 _______________________________________________ llvm-branch-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-branch-commits
