[llvm-branch-commits] [llvm] [BOLT] Gadget scanner: detect authentication oracles (PR #135663)

Anatoly Trosinenko via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Fri Apr 18 09:35:00 PDT 2025


https://github.com/atrosinenko updated https://github.com/llvm/llvm-project/pull/135663

>From 4c18b44629afc3ddc52d4b539b65d2f9af1b650d Mon Sep 17 00:00:00 2001
From: Anatoly Trosinenko <atrosinenko at accesssoftek.com>
Date: Sat, 5 Apr 2025 14:54:01 +0300
Subject: [PATCH] [BOLT] Gadget scanner: detect authentication oracles

Implement the detection of authentication instructions whose results can
be inspected by an attacker to know whether authentication succeeded.

As the properties of output registers of authentication instructions are
inspected, add a second set of analysis-related classes to iterate over
the instructions in reverse order.
---
 bolt/include/bolt/Passes/PAuthGadgetScanner.h |  12 +
 bolt/lib/Passes/PAuthGadgetScanner.cpp        | 541 +++++++++++++
 .../AArch64/gs-pauth-authentication-oracles.s | 723 ++++++++++++++++++
 .../AArch64/gs-pauth-debug-output.s           |  78 ++
 4 files changed, 1354 insertions(+)
 create mode 100644 bolt/test/binary-analysis/AArch64/gs-pauth-authentication-oracles.s

diff --git a/bolt/include/bolt/Passes/PAuthGadgetScanner.h b/bolt/include/bolt/Passes/PAuthGadgetScanner.h
index 3b6c1f6af94a0..2b923e362941f 100644
--- a/bolt/include/bolt/Passes/PAuthGadgetScanner.h
+++ b/bolt/include/bolt/Passes/PAuthGadgetScanner.h
@@ -261,6 +261,15 @@ class ClobberingInfo : public ExtraInfo {
   void print(raw_ostream &OS, const MCInstReference Location) const override;
 };
 
+class LeakageInfo : public ExtraInfo {
+  SmallVector<MCInstReference> LeakingInstrs;
+
+public:
+  LeakageInfo(const ArrayRef<MCInstReference> Instrs) : LeakingInstrs(Instrs) {}
+
+  void print(raw_ostream &OS, const MCInstReference Location) const override;
+};
+
 /// A brief version of a report that can be further augmented with the details.
 ///
 /// It is common for a particular type of gadget detector to be tied to some
@@ -302,6 +311,9 @@ class FunctionAnalysis {
   void findUnsafeUses(SmallVector<BriefReport<MCPhysReg>> &Reports);
   void augmentUnsafeUseReports(const ArrayRef<BriefReport<MCPhysReg>> Reports);
 
+  void findUnsafeDefs(SmallVector<BriefReport<MCPhysReg>> &Reports);
+  void augmentUnsafeDefReports(const ArrayRef<BriefReport<MCPhysReg>> Reports);
+
 public:
   FunctionAnalysis(BinaryFunction &BF, MCPlusBuilder::AllocatorIdTy AllocatorId,
                    bool PacRetGadgetsOnly)
diff --git a/bolt/lib/Passes/PAuthGadgetScanner.cpp b/bolt/lib/Passes/PAuthGadgetScanner.cpp
index 2e4d8ff933a65..f12354390c3f3 100644
--- a/bolt/lib/Passes/PAuthGadgetScanner.cpp
+++ b/bolt/lib/Passes/PAuthGadgetScanner.cpp
@@ -716,6 +716,461 @@ SrcSafetyAnalysis::create(BinaryFunction &BF,
                                                        RegsToTrackInstsFor);
 }
 
+/// A state representing which registers are safe to be used as the destination
+/// operand of an authentication instruction.
+///
+/// Similar to SrcState, it is the analysis that should take register aliasing
+/// into account.
+///
+/// Depending on the implementation, it may be possible that an authentication
+/// instruction returns an invalid pointer on failure instead of terminating
+/// the program immediately (assuming the program will crash as soon as that
+/// pointer is dereferenced). To prevent brute-forcing the correct signature,
+/// it should be impossible for an attacker to test if a pointer is correctly
+/// signed - either the program should be terminated on authentication failure
+/// or it should be impossible to tell whether authentication succeeded or not.
+///
+/// For that reason, a restricted set of operations is allowed on any register
+/// containing a value derived from the result of an authentication instruction
+/// until that register is either wiped or checked not to contain a result of a
+/// failed authentication.
+///
+/// Specifically, the safety property for a register is computed by iterating
+/// the instructions in backward order: the source register Xn of an instruction
+/// Inst is safe if at least one of the following is true:
+/// * Inst checks if Xn contains the result of a successful authentication and
+///   terminates the program on failure. Note that Inst can either naturally
+///   dereference Xn (load, branch, return, etc. instructions) or be the first
+///   instruction of an explicit checking sequence.
+/// * Inst performs safe address arithmetic AND both source and result
+///   registers, as well as any temporary registers, must be safe after
+///   execution of Inst (temporaries are not used on AArch64 and thus not
+///   currently supported/allowed).
+///   See MCPlusBuilder::analyzeAddressArithmeticsForPtrAuth for the details.
+/// * Inst fully overwrites Xn with an unrelated value.
+struct DstState {
+  /// The set of registers whose values cannot be inspected by an attacker in
+  /// a way usable as an authentication oracle. The results of authentication
+  /// instructions should be written to such registers.
+  BitVector CannotEscapeUnchecked;
+
+  std::vector<SmallPtrSet<const MCInst *, 4>> FirstInstLeakingReg;
+
+  /// Construct an empty state.
+  DstState() {}
+
+  DstState(unsigned NumRegs, unsigned NumRegsToTrack)
+      : CannotEscapeUnchecked(NumRegs), FirstInstLeakingReg(NumRegsToTrack) {}
+
+  DstState &merge(const DstState &StateIn) {
+    if (StateIn.empty())
+      return *this;
+    if (empty())
+      return (*this = StateIn);
+
+    CannotEscapeUnchecked &= StateIn.CannotEscapeUnchecked;
+    return *this;
+  }
+
+  /// Returns true if this object does not store state of any registers -
+  /// neither safe, nor unsafe ones.
+  bool empty() const { return CannotEscapeUnchecked.empty(); }
+
+  bool operator==(const DstState &RHS) const {
+    return CannotEscapeUnchecked == RHS.CannotEscapeUnchecked;
+  }
+  bool operator!=(const DstState &RHS) const { return !((*this) == RHS); }
+};
+
+raw_ostream &operator<<(raw_ostream &OS, const DstState &S) {
+  OS << "dst-state<";
+  if (S.empty()) {
+    OS << "empty";
+  } else {
+    OS << "CannotEscapeUnchecked: " << S.CannotEscapeUnchecked;
+  }
+  OS << ">";
+  return OS;
+}
+
+class DstStatePrinter {
+public:
+  void print(raw_ostream &OS, const DstState &S) const;
+  explicit DstStatePrinter(const BinaryContext &BC) : BC(BC) {}
+
+private:
+  const BinaryContext &BC;
+};
+
+void DstStatePrinter::print(raw_ostream &OS, const DstState &S) const {
+  RegStatePrinter RegStatePrinter(BC);
+  OS << "dst-state<";
+  if (S.empty()) {
+    assert(S.CannotEscapeUnchecked.empty());
+    OS << "empty";
+  } else {
+    OS << "CannotEscapeUnchecked: ";
+    RegStatePrinter.print(OS, S.CannotEscapeUnchecked);
+  }
+  OS << ">";
+}
+
+/// Computes which registers are safe to be written to by auth instructions.
+///
+/// This is the base class for two implementations: a dataflow-based analysis
+/// which is intended to be used for most functions and a simplified CFG-unaware
+/// version for functions without reconstructed CFG.
+class DstSafetyAnalysis {
+public:
+  DstSafetyAnalysis(BinaryFunction &BF,
+                    const ArrayRef<MCPhysReg> RegsToTrackInstsFor)
+      : BC(BF.getBinaryContext()), NumRegs(BC.MRI->getNumRegs()),
+        RegsToTrackInstsFor(RegsToTrackInstsFor) {}
+
+  virtual ~DstSafetyAnalysis() {}
+
+  static std::shared_ptr<DstSafetyAnalysis>
+  create(BinaryFunction &BF, MCPlusBuilder::AllocatorIdTy AllocId,
+         const ArrayRef<MCPhysReg> RegsToTrackInstsFor);
+
+  virtual void run() = 0;
+  virtual const DstState &getStateAfter(const MCInst &Inst) const = 0;
+
+protected:
+  BinaryContext &BC;
+  const unsigned NumRegs;
+
+  const TrackedRegisters RegsToTrackInstsFor;
+  /// Stores information about the detected instruction sequences emitted to
+  /// check an authenticated pointer. Specifically, if such sequence is detected
+  /// in a basic block, it maps the first instruction of that sequence to the
+  /// register being checked.
+  ///
+  /// As the detection of such sequences requires iterating over the adjacent
+  /// instructions, it should be done before calling computeNext(), which
+  /// operates on separate instructions.
+  DenseMap<const MCInst *, MCPhysReg> RegCheckedAt;
+
+  SmallPtrSet<const MCInst *, 4> &firstLeakingInsts(DstState &S,
+                                                    MCPhysReg Reg) const {
+    unsigned Index = RegsToTrackInstsFor.getIndex(Reg);
+    return S.FirstInstLeakingReg[Index];
+  }
+  const SmallPtrSet<const MCInst *, 4> &firstLeakingInsts(const DstState &S,
+                                                          MCPhysReg Reg) const {
+    unsigned Index = RegsToTrackInstsFor.getIndex(Reg);
+    return S.FirstInstLeakingReg[Index];
+  }
+
+  /// Creates a state with all registers marked unsafe (not to be confused
+  /// with empty state).
+  DstState createUnsafeState() {
+    return DstState(NumRegs, RegsToTrackInstsFor.getNumTrackedRegisters());
+  }
+
+  BitVector getLeakedRegs(const MCInst &Inst) const {
+    BitVector Leaked(NumRegs);
+
+    // Assume a call can read all registers.
+    if (BC.MIB->isCall(Inst)) {
+      Leaked.set();
+      return Leaked;
+    }
+
+    // Compute the set of registers overlapping with any register used by
+    // this instruction.
+
+    const MCInstrDesc &Desc = BC.MII->get(Inst.getOpcode());
+
+    for (MCPhysReg Reg : Desc.implicit_uses())
+      Leaked |= BC.MIB->getAliases(Reg, /*OnlySmaller=*/false);
+
+    for (const MCOperand &Op : BC.MIB->useOperands(Inst)) {
+      if (Op.isReg())
+        Leaked |= BC.MIB->getAliases(Op.getReg(), /*OnlySmaller=*/false);
+    }
+
+    return Leaked;
+  }
+
+  SmallVector<MCPhysReg> getRegsMadeProtected(const MCInst &Inst,
+                                              const BitVector &LeakedRegs,
+                                              const DstState &Cur) const {
+    SmallVector<MCPhysReg> Regs;
+    const MCPhysReg NoReg = BC.MIB->getNoRegister();
+
+    // A pointer can be checked, or
+    if (auto CheckedReg =
+            BC.MIB->getAuthCheckedReg(Inst, /*MayOverwrite=*/true))
+      Regs.push_back(*CheckedReg);
+    if (RegCheckedAt.contains(&Inst))
+      Regs.push_back(RegCheckedAt.at(&Inst));
+
+    // ... it can be used as a branch target, or
+    if (BC.MIB->isIndirectBranch(Inst) || BC.MIB->isIndirectCall(Inst)) {
+      bool IsAuthenticated;
+      MCPhysReg BranchDestReg =
+          BC.MIB->getRegUsedAsIndirectBranchDest(Inst, IsAuthenticated);
+      assert(BranchDestReg != NoReg);
+      if (!IsAuthenticated)
+        Regs.push_back(BranchDestReg);
+    }
+
+    // ... it can be used as a return target, or
+    if (BC.MIB->isReturn(Inst)) {
+      bool IsAuthenticated = false;
+      std::optional<MCPhysReg> RetReg =
+          BC.MIB->getRegUsedAsRetDest(Inst, IsAuthenticated);
+      if (RetReg && !IsAuthenticated)
+        Regs.push_back(*RetReg);
+    }
+
+    // ... an address can be updated in a safe manner, or
+    if (auto DstAndSrc = BC.MIB->analyzeAddressArithmeticsForPtrAuth(Inst)) {
+      MCPhysReg DstReg, SrcReg;
+      std::tie(DstReg, SrcReg) = *DstAndSrc;
+      // Note that *all* registers containing the derived values must be safe,
+      // both source and destination ones. No temporaries are supported at now.
+      if (Cur.CannotEscapeUnchecked[SrcReg] &&
+          Cur.CannotEscapeUnchecked[DstReg])
+        Regs.push_back(SrcReg);
+    }
+
+    // ... the register can be overwritten in whole with an unrelated value -
+    // for that reason, ignore the registers that are both read and written:
+    //
+    //     movk x0, #42, lsl #16  // keeps some bits of x0
+    //     mul  x1, x1, #3        // not all information is actually lost
+    //
+    BitVector FullyOverwrittenRegs;
+    BC.MIB->getWrittenRegs(Inst, FullyOverwrittenRegs);
+    FullyOverwrittenRegs.reset(LeakedRegs);
+    for (MCPhysReg Reg : FullyOverwrittenRegs.set_bits())
+      Regs.push_back(Reg);
+
+    return Regs;
+  }
+
+  DstState computeNext(const MCInst &Point, const DstState &Cur) {
+    DstStatePrinter P(BC);
+    LLVM_DEBUG({
+      dbgs() << "  DstSafetyAnalysis::ComputeNext(";
+      BC.InstPrinter->printInst(&const_cast<MCInst &>(Point), 0, "", *BC.STI,
+                                dbgs());
+      dbgs() << ", ";
+      P.print(dbgs(), Cur);
+      dbgs() << ")\n";
+    });
+
+    // If this instruction is reachable by the analysis, a non-empty state will
+    // be propagated to it sooner or later. Until then, skip computeNext().
+    if (Cur.empty()) {
+      LLVM_DEBUG(
+          { dbgs() << "Skipping computeNext(Point, Cur) as Cur is empty.\n"; });
+      return DstState();
+    }
+
+    // First, compute various properties of the instruction, taking the state
+    // after its execution into account, if necessary.
+
+    BitVector LeakedRegs = getLeakedRegs(Point);
+    SmallVector<MCPhysReg> NewProtectedRegs =
+        getRegsMadeProtected(Point, LeakedRegs, Cur);
+
+    // Then, compute the state before this instruction is executed.
+    DstState Next = Cur;
+
+    Next.CannotEscapeUnchecked.reset(LeakedRegs);
+    for (MCPhysReg Reg : RegsToTrackInstsFor.getRegisters()) {
+      if (LeakedRegs[Reg])
+        firstLeakingInsts(Next, Reg) = {&Point};
+    }
+
+    BitVector NewProtectedSubregs(NumRegs);
+    for (MCPhysReg Reg : NewProtectedRegs)
+      NewProtectedSubregs |= BC.MIB->getAliases(Reg, /*OnlySmaller=*/true);
+    Next.CannotEscapeUnchecked |= NewProtectedSubregs;
+    for (MCPhysReg Reg : RegsToTrackInstsFor.getRegisters()) {
+      if (NewProtectedSubregs[Reg])
+        firstLeakingInsts(Next, Reg).clear();
+    }
+
+    LLVM_DEBUG({
+      dbgs() << "    .. result: (";
+      P.print(dbgs(), Next);
+      dbgs() << ")\n";
+    });
+
+    return Next;
+  }
+
+public:
+  std::vector<MCInstReference>
+  getLeakingInsts(const MCInst &Inst, BinaryFunction &BF,
+                  const std::optional<MCPhysReg> LeakedReg) const {
+    if (!LeakedReg || RegsToTrackInstsFor.empty())
+      return {};
+    const DstState &S = getStateAfter(Inst);
+
+    std::vector<MCInstReference> Result;
+    for (const MCInst *Inst : firstLeakingInsts(S, *LeakedReg)) {
+      MCInstReference Ref = MCInstReference::get(Inst, BF);
+      assert(Ref && "Expected Inst to be found");
+      Result.push_back(Ref);
+    }
+    return Result;
+  }
+};
+
+class DataflowDstSafetyAnalysis
+    : public DstSafetyAnalysis,
+      public DataflowAnalysis<DataflowDstSafetyAnalysis, DstState,
+                              /*Backward=*/true, DstStatePrinter> {
+  using DFParent = DataflowAnalysis<DataflowDstSafetyAnalysis, DstState, true,
+                                    DstStatePrinter>;
+  friend DFParent;
+
+  using DstSafetyAnalysis::BC;
+  using DstSafetyAnalysis::computeNext;
+
+public:
+  DataflowDstSafetyAnalysis(BinaryFunction &BF,
+                            MCPlusBuilder::AllocatorIdTy AllocId,
+                            const ArrayRef<MCPhysReg> RegsToTrackInstsFor)
+      : DstSafetyAnalysis(BF, RegsToTrackInstsFor), DFParent(BF, AllocId) {}
+
+  const DstState &getStateAfter(const MCInst &Inst) const override {
+    return DFParent::getStateBefore(Inst).get();
+  }
+
+  void run() override {
+    for (BinaryBasicBlock &BB : Func) {
+      if (auto CheckerInfo = BC.MIB->getAuthCheckedReg(BB)) {
+        LLVM_DEBUG({
+          dbgs() << "Found pointer checking sequence in " << BB.getName()
+                 << ":\n";
+          traceReg(BC, "Checked register", CheckerInfo->first);
+          traceInst(BC, "First instruction", *CheckerInfo->second);
+        });
+        RegCheckedAt[CheckerInfo->second] = CheckerInfo->first;
+      }
+    }
+    DFParent::run();
+  }
+
+protected:
+  void preflight() {}
+
+  DstState getStartingStateAtBB(const BinaryBasicBlock &BB) {
+    // In general, the initial state should be empty, not everything-is-unsafe,
+    // to give a chance for some meaningful state to be propagated to BB from
+    // an indirectly reachable "exit basic block" ending with a return or tail
+    // call instruction.
+    //
+    // A basic block without any successors, on the other hand, can be
+    // pessimistically initialized to everything-is-unsafe: this will naturally
+    // handle both return and tail call instructions and is harmless for
+    // internal indirect branch instructions (such as computed gotos).
+    if (BB.succ_empty())
+      return createUnsafeState();
+
+    return DstState();
+  }
+
+  DstState getStartingStateAtPoint(const MCInst &Point) { return DstState(); }
+
+  void doConfluence(DstState &StateOut, const DstState &StateIn) {
+    DstStatePrinter P(BC);
+    LLVM_DEBUG({
+      dbgs() << "  DataflowDstSafetyAnalysis::Confluence(\n";
+      dbgs() << "    State 1: ";
+      P.print(dbgs(), StateOut);
+      dbgs() << "\n";
+      dbgs() << "    State 2: ";
+      P.print(dbgs(), StateIn);
+      dbgs() << ")\n";
+    });
+
+    StateOut.merge(StateIn);
+
+    LLVM_DEBUG({
+      dbgs() << "    merged state: ";
+      P.print(dbgs(), StateOut);
+      dbgs() << "\n";
+    });
+  }
+
+  StringRef getAnnotationName() const { return "DataflowDstSafetyAnalysis"; }
+};
+
+class CFGUnawareDstSafetyAnalysis : public DstSafetyAnalysis {
+  BinaryFunction &BF;
+  MCPlusBuilder::AllocatorIdTy AllocId;
+  unsigned StateAnnotationIndex;
+
+  void cleanStateAnnotations() {
+    for (auto &I : BF.instrs())
+      BC.MIB->removeAnnotation(I.second, StateAnnotationIndex);
+  }
+
+  DstState createUnsafeState() const {
+    return DstState(NumRegs, RegsToTrackInstsFor.getNumTrackedRegisters());
+  }
+
+public:
+  CFGUnawareDstSafetyAnalysis(BinaryFunction &BF,
+                              MCPlusBuilder::AllocatorIdTy AllocId,
+                              const ArrayRef<MCPhysReg> RegsToTrackInstsFor)
+      : DstSafetyAnalysis(BF, RegsToTrackInstsFor), BF(BF), AllocId(AllocId) {
+    StateAnnotationIndex =
+        BC.MIB->getOrCreateAnnotationIndex("CFGUnawareDstSafetyAnalysis");
+  }
+
+  void run() override {
+    DstState S = createUnsafeState();
+    for (auto &I : llvm::reverse(BF.instrs())) {
+      MCInst &Inst = I.second;
+
+      // If Inst can change the control flow, we cannot be sure that the next
+      // instruction (to be executed in analyzed program) is the one processed
+      // on the previous iteration, thus pessimistically reset S before
+      // starting to analyze Inst.
+      if (BC.MIB->isCall(Inst) || BC.MIB->isBranch(Inst) ||
+          BC.MIB->isReturn(Inst)) {
+        LLVM_DEBUG({ traceInst(BC, "Control flow instruction", Inst); });
+        S = createUnsafeState();
+      }
+
+      // Check if we need to remove an old annotation (this is the case if
+      // this is the second, detailed, run of the analysis).
+      if (BC.MIB->hasAnnotation(Inst, StateAnnotationIndex))
+        BC.MIB->removeAnnotation(Inst, StateAnnotationIndex);
+      // Attach the state *after* this instruction executes.
+      BC.MIB->addAnnotation(Inst, StateAnnotationIndex, S, AllocId);
+
+      // Compute the next state.
+      S = computeNext(Inst, S);
+    }
+  }
+
+  const DstState &getStateAfter(const MCInst &Inst) const override {
+    return BC.MIB->getAnnotationAs<DstState>(Inst, StateAnnotationIndex);
+  }
+
+  ~CFGUnawareDstSafetyAnalysis() { cleanStateAnnotations(); }
+};
+
+std::shared_ptr<DstSafetyAnalysis>
+DstSafetyAnalysis::create(BinaryFunction &BF,
+                          MCPlusBuilder::AllocatorIdTy AllocId,
+                          const ArrayRef<MCPhysReg> RegsToTrackInstsFor) {
+  if (BF.hasCFG())
+    return std::make_shared<DataflowDstSafetyAnalysis>(BF, AllocId,
+                                                       RegsToTrackInstsFor);
+  return std::make_shared<CFGUnawareDstSafetyAnalysis>(BF, AllocId,
+                                                       RegsToTrackInstsFor);
+}
+
 static BriefReport<MCPhysReg> make_generic_report(MCInstReference Location,
                                                   StringRef Text) {
   auto Report = std::make_shared<GenericReport>(Location, Text);
@@ -807,6 +1262,38 @@ shouldReportSigningOracle(const BinaryContext &BC, const MCInstReference &Inst,
   return make_report(SigningOracleKind, Inst, *SignedReg);
 }
 
+static std::optional<BriefReport<MCPhysReg>>
+shouldReportAuthOracle(const BinaryContext &BC, const MCInstReference &Inst,
+                       const DstState &S) {
+  static const GadgetKind AuthOracleKind("authentication oracle found");
+
+  bool IsChecked = false;
+  std::optional<MCPhysReg> AuthReg =
+      BC.MIB->getWrittenAuthenticatedReg(Inst, IsChecked);
+  if (!AuthReg || IsChecked)
+    return std::nullopt;
+
+  assert(*AuthReg != BC.MIB->getNoRegister());
+  LLVM_DEBUG({
+    traceInst(BC, "Found auth inst", Inst);
+    traceReg(BC, "Authenticated reg", *AuthReg);
+  });
+
+  if (S.empty()) {
+    LLVM_DEBUG({ dbgs() << "    DstState is empty!\n"; });
+    return make_generic_report(
+        Inst, "Warning: no state computed for an authentication instruction "
+              "(possibly unreachable)");
+  }
+
+  LLVM_DEBUG(
+      { traceRegMask(BC, "safe output registers", S.CannotEscapeUnchecked); });
+  if (S.CannotEscapeUnchecked[*AuthReg])
+    return std::nullopt;
+
+  return make_report(AuthOracleKind, Inst, *AuthReg);
+}
+
 template <typename T> static void iterateOverInstrs(BinaryFunction &BF, T Fn) {
   if (BF.hasCFG()) {
     for (BinaryBasicBlock &BB : BF)
@@ -886,6 +1373,50 @@ void FunctionAnalysis::augmentUnsafeUseReports(
   }
 }
 
+void FunctionAnalysis::findUnsafeDefs(
+    SmallVector<BriefReport<MCPhysReg>> &Reports) {
+  if (PacRetGadgetsOnly)
+    return;
+
+  auto Analysis = DstSafetyAnalysis::create(BF, AllocatorId, {});
+  LLVM_DEBUG({ dbgs() << "Running dst register safety analysis...\n"; });
+  Analysis->run();
+  LLVM_DEBUG({
+    dbgs() << "After dst register safety analysis:\n";
+    BF.dump();
+  });
+
+  iterateOverInstrs(BF, [&](MCInstReference Inst) {
+    const DstState &S = Analysis->getStateAfter(Inst);
+
+    if (auto Report = shouldReportAuthOracle(BC, Inst, S))
+      Reports.push_back(*Report);
+  });
+}
+
+void FunctionAnalysis::augmentUnsafeDefReports(
+    const ArrayRef<BriefReport<MCPhysReg>> Reports) {
+  SmallVector<MCPhysReg> RegsToTrack = collectRegsToTrack(Reports);
+  // Re-compute the analysis with register tracking.
+  auto Analysis = DstSafetyAnalysis::create(BF, AllocatorId, RegsToTrack);
+  LLVM_DEBUG(
+      { dbgs() << "\nRunning detailed dst register safety analysis...\n"; });
+  Analysis->run();
+  LLVM_DEBUG({
+    dbgs() << "After detailed dst register safety analysis:\n";
+    BF.dump();
+  });
+
+  // Augment gadget reports.
+  for (auto Report : Reports) {
+    MCInstReference Location = Report.Issue->Location;
+    LLVM_DEBUG({ traceInst(BC, "Attaching leakage info to", Location); });
+    auto DetailedInfo = std::make_shared<LeakageInfo>(
+        Analysis->getLeakingInsts(Location, BF, Report.RequestedDetails));
+    Result.Diagnostics.emplace_back(Report.Issue, DetailedInfo);
+  }
+}
+
 void FunctionAnalysis::run() {
   LLVM_DEBUG({
     dbgs() << "Analyzing function " << BF.getPrintName()
@@ -897,6 +1428,11 @@ void FunctionAnalysis::run() {
   findUnsafeUses(UnsafeUses);
   if (!UnsafeUses.empty())
     augmentUnsafeUseReports(UnsafeUses);
+
+  SmallVector<BriefReport<MCPhysReg>> UnsafeDefs;
+  findUnsafeDefs(UnsafeDefs);
+  if (!UnsafeDefs.empty())
+    augmentUnsafeDefReports(UnsafeDefs);
 }
 
 void Analysis::runOnFunction(BinaryFunction &BF,
@@ -1000,6 +1536,11 @@ void ClobberingInfo::print(raw_ostream &OS,
   printRelatedInstrs(OS, Location, ClobberingInstrs);
 }
 
+void LeakageInfo::print(raw_ostream &OS, const MCInstReference Location) const {
+  OS << "  The " << LeakingInstrs.size()
+     << " instructions that leak the affected registers are:\n";
+  printRelatedInstrs(OS, Location, LeakingInstrs);
+}
 void GenericReport::generateReport(raw_ostream &OS,
                                    const BinaryContext &BC) const {
   printBasicInfo(OS, BC, Text);
diff --git a/bolt/test/binary-analysis/AArch64/gs-pauth-authentication-oracles.s b/bolt/test/binary-analysis/AArch64/gs-pauth-authentication-oracles.s
new file mode 100644
index 0000000000000..7a7c6da145299
--- /dev/null
+++ b/bolt/test/binary-analysis/AArch64/gs-pauth-authentication-oracles.s
@@ -0,0 +1,723 @@
+// 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
+
+// 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
+// "high-bits-notbi" checkers, as the shortest examples of checkers that are
+// detected per-instruction and per-BB.
+
+// PACRET-NOT: authentication oracle found in function
+
+        .text
+
+        .type   sym, at function
+sym:
+        ret
+        .size sym, .-sym
+
+        .globl  callee
+        .type   callee, at function
+callee:
+        ret
+        .size callee, .-callee
+
+        .globl  good_ret
+        .type   good_ret, at function
+good_ret:
+// CHECK-NOT: good_ret
+        autia   x0, x1
+        ret     x0
+        .size good_ret, .-good_ret
+
+        .globl  good_call
+        .type   good_call, at function
+good_call:
+// CHECK-NOT: good_call
+        paciasp
+        stp     x29, x30, [sp, #-16]!
+        mov     x29, sp
+
+        autia   x0, x1
+        blr     x0
+
+        ldp     x29, x30, [sp], #16
+        autiasp
+        ret
+        .size good_call, .-good_call
+
+        .globl  good_branch
+        .type   good_branch, at function
+good_branch:
+// CHECK-NOT: good_branch
+        autia   x0, x1
+        br      x0
+        .size good_branch, .-good_branch
+
+        .globl  good_load_other_reg
+        .type   good_load_other_reg, at function
+good_load_other_reg:
+// CHECK-NOT: good_load_other_reg
+        autia   x0, x1
+        ldr     x2, [x0]
+        ret
+        .size good_load_other_reg, .-good_load_other_reg
+
+        .globl  good_load_same_reg
+        .type   good_load_same_reg, at function
+good_load_same_reg:
+// CHECK-NOT: good_load_same_reg
+        autia   x0, x1
+        ldr     x0, [x0]
+        ret
+        .size good_load_same_reg, .-good_load_same_reg
+
+        .globl  good_explicit_check
+        .type   good_explicit_check, at function
+good_explicit_check:
+// CHECK-NOT: good_explicit_check
+        autia   x0, x1
+        eor     x16, x0, x0, lsl #1
+        tbz     x16, #62, 1f
+        brk     0x1234
+1:
+        ret
+        .size good_explicit_check, .-good_explicit_check
+
+        .globl  bad_unchecked
+        .type   bad_unchecked, at function
+bad_unchecked:
+// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_unchecked, basic block {{[^,]+}}, at address
+// CHECK-NEXT:  The instruction is     {{[0-9a-f]+}}:      autia   x0, x1
+// CHECK-NEXT:  The 0 instructions that leak the affected registers are:
+        autia   x0, x1
+        ret
+        .size bad_unchecked, .-bad_unchecked
+
+        .globl  bad_leaked_to_subroutine
+        .type   bad_leaked_to_subroutine, at function
+bad_leaked_to_subroutine:
+// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_leaked_to_subroutine, basic block {{[^,]+}}, at address
+// CHECK-NEXT:  The instruction is     {{[0-9a-f]+}}:      autia   x0, x1
+// CHECK-NEXT:  The 1 instructions that leak the affected registers are:
+// CHECK-NEXT:  1.     {{[0-9a-f]+}}:      bl      callee
+// 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]+}}:   mov     x29, sp
+// CHECK-NEXT:  {{[0-9a-f]+}}:   autia   x0, x1
+// CHECK-NEXT:  {{[0-9a-f]+}}:   bl      callee
+// CHECK-NEXT:  {{[0-9a-f]+}}:   ldr     x2, [x0]
+// CHECK-NEXT:  {{[0-9a-f]+}}:   ldp     x29, x30, [sp], #0x10
+// CHECK-NEXT:  {{[0-9a-f]+}}:   autiasp
+// CHECK-NEXT:  {{[0-9a-f]+}}:   ret
+        paciasp
+        stp     x29, x30, [sp, #-16]!
+        mov     x29, sp
+
+        autia   x0, x1
+        bl      callee
+        ldr     x2, [x0]
+
+        ldp     x29, x30, [sp], #16
+        autiasp
+        ret
+        .size bad_leaked_to_subroutine, .-bad_leaked_to_subroutine
+
+        .globl  bad_unknown_usage_read
+        .type   bad_unknown_usage_read, at function
+bad_unknown_usage_read:
+// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_unknown_usage_read, basic block {{[^,]+}}, at address
+// CHECK-NEXT:  The instruction is     {{[0-9a-f]+}}:      autia   x0, x1
+// CHECK-NEXT:  The 1 instructions that leak the affected registers are:
+// CHECK-NEXT:  1.     {{[0-9a-f]+}}:      mul     x3, x0, x1
+// CHECK-NEXT:  This happens in the following basic block:
+// CHECK-NEXT:  {{[0-9a-f]+}}:   autia   x0, x1
+// CHECK-NEXT:  {{[0-9a-f]+}}:   mul     x3, x0, x1
+// CHECK-NEXT:  {{[0-9a-f]+}}:   ldr     x2, [x0]
+// CHECK-NEXT:  {{[0-9a-f]+}}:   ret
+        autia   x0, x1
+        mul     x3, x0, x1
+        ldr     x2, [x0]
+        ret
+        .size bad_unknown_usage_read, .-bad_unknown_usage_read
+
+        .globl  bad_unknown_usage_subreg_read
+        .type   bad_unknown_usage_subreg_read, at function
+bad_unknown_usage_subreg_read:
+// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_unknown_usage_subreg_read, basic block {{[^,]+}}, at address
+// CHECK-NEXT:  The instruction is     {{[0-9a-f]+}}:      autia   x0, x1
+// CHECK-NEXT:  The 1 instructions that leak the affected registers are:
+// CHECK-NEXT:  1.     {{[0-9a-f]+}}:      mul     w3, w0, w1
+// CHECK-NEXT:  This happens in the following basic block:
+// CHECK-NEXT:  {{[0-9a-f]+}}:   autia   x0, x1
+// CHECK-NEXT:  {{[0-9a-f]+}}:   mul     w3, w0, w1
+// CHECK-NEXT:  {{[0-9a-f]+}}:   ldr     x2, [x0]
+// CHECK-NEXT:  {{[0-9a-f]+}}:   ret
+        autia   x0, x1
+        mul     w3, w0, w1
+        ldr     x2, [x0]
+        ret
+        .size bad_unknown_usage_subreg_read, .-bad_unknown_usage_subreg_read
+
+        .globl  bad_unknown_usage_update
+        .type   bad_unknown_usage_update, at function
+bad_unknown_usage_update:
+// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_unknown_usage_update, basic block {{[^,]+}}, at address
+// CHECK-NEXT:  The instruction is     {{[0-9a-f]+}}:      autia   x0, x1
+// CHECK-NEXT:  The 1 instructions that leak the affected registers are:
+// CHECK-NEXT:  1.     {{[0-9a-f]+}}:      movk    x0, #0x2a, lsl #16
+// CHECK-NEXT:  This happens in the following basic block:
+// CHECK-NEXT:  {{[0-9a-f]+}}:   autia   x0, x1
+// CHECK-NEXT:  {{[0-9a-f]+}}:   movk    x0, #0x2a, lsl #16
+// CHECK-NEXT:  {{[0-9a-f]+}}:   ldr     x2, [x0]
+// CHECK-NEXT:  {{[0-9a-f]+}}:   ret
+        autia   x0, x1
+        movk    x0, #42, lsl #16 // does not overwrite x0 completely
+        ldr     x2, [x0]
+        ret
+        .size bad_unknown_usage_update, .-bad_unknown_usage_update
+
+        .globl  good_unknown_overwrite
+        .type   good_unknown_overwrite, at function
+good_unknown_overwrite:
+// CHECK-NOT: good_unknown_overwrite
+        autia   x0, x1
+        mul     x0, x1, x2
+        ret
+        .size good_unknown_overwrite, .-good_unknown_overwrite
+
+        .globl  good_address_arith
+        .type   good_address_arith, at function
+good_address_arith:
+// CHECK-NOT: good_address_arith
+        autia   x0, x1
+
+        add     x1, x0, #8
+        sub     x2, x1, #16
+        mov     x3, x2
+
+        ldr     x4, [x3]
+        mov     x0, #0
+        mov     x1, #0
+        mov     x2, #0
+
+        ret
+        .size good_address_arith, .-good_address_arith
+
+        .globl  good_ret_multi_bb
+        .type   good_ret_multi_bb, at function
+good_ret_multi_bb:
+// CHECK-NOT: good_ret_multi_bb
+        autia   x0, x1
+        cbz     x1, 1f
+        nop
+1:
+        ret     x0
+        .size good_ret_multi_bb, .-good_ret_multi_bb
+
+        .globl  good_call_multi_bb
+        .type   good_call_multi_bb, at function
+good_call_multi_bb:
+// CHECK-NOT: good_call_multi_bb
+        paciasp
+        stp     x29, x30, [sp, #-16]!
+        mov     x29, sp
+
+        autia   x0, x1
+        cbz     x1, 1f
+        nop
+1:
+        blr     x0
+        cbz     x1, 2f
+        nop
+2:
+        ldp     x29, x30, [sp], #16
+        autiasp
+        ret
+        .size good_call_multi_bb, .-good_call_multi_bb
+
+        .globl  good_branch_multi_bb
+        .type   good_branch_multi_bb, at function
+good_branch_multi_bb:
+// CHECK-NOT: good_branch_multi_bb
+        autia   x0, x1
+        cbz     x1, 1f
+        nop
+1:
+        br      x0
+        .size good_branch_multi_bb, .-good_branch_multi_bb
+
+        .globl  good_load_other_reg_multi_bb
+        .type   good_load_other_reg_multi_bb, at function
+good_load_other_reg_multi_bb:
+// CHECK-NOT: good_load_other_reg_multi_bb
+        autia   x0, x1
+        cbz     x1, 1f
+        nop
+1:
+        ldr     x2, [x0]
+        cbz     x1, 2f
+        nop
+2:
+        ret
+        .size good_load_other_reg_multi_bb, .-good_load_other_reg_multi_bb
+
+        .globl  good_load_same_reg_multi_bb
+        .type   good_load_same_reg_multi_bb, at function
+good_load_same_reg_multi_bb:
+// CHECK-NOT: good_load_same_reg_multi_bb
+        autia   x0, x1
+        cbz     x1, 1f
+        nop
+1:
+        ldr     x0, [x0]
+        cbz     x1, 2f
+        nop
+2:
+        ret
+        .size good_load_same_reg_multi_bb, .-good_load_same_reg_multi_bb
+
+        .globl  good_explicit_check_multi_bb
+        .type   good_explicit_check_multi_bb, at function
+good_explicit_check_multi_bb:
+// CHECK-NOT: good_explicit_check_multi_bb
+        autia   x0, x1
+        cbz     x1, 1f
+        nop
+1:
+        eor     x16, x0, x0, lsl #1
+        tbz     x16, #62, 2f
+        brk     0x1234
+2:
+        cbz     x1, 3f
+        nop
+3:
+        ret
+        .size good_explicit_check_multi_bb, .-good_explicit_check_multi_bb
+
+        .globl  bad_unchecked_multi_bb
+        .type   bad_unchecked_multi_bb, at function
+bad_unchecked_multi_bb:
+// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_unchecked_multi_bb, basic block {{[^,]+}}, at address
+// CHECK-NEXT:  The instruction is     {{[0-9a-f]+}}:      autia   x0, x1
+// CHECK-NEXT:  The 0 instructions that leak the affected registers are:
+        autia   x0, x1
+        cbz     x1, 1f
+        ldr     x2, [x0]
+1:
+        ret
+        .size bad_unchecked_multi_bb, .-bad_unchecked_multi_bb
+
+        .globl  bad_leaked_to_subroutine_multi_bb
+        .type   bad_leaked_to_subroutine_multi_bb, at function
+bad_leaked_to_subroutine_multi_bb:
+// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_leaked_to_subroutine_multi_bb, basic block {{[^,]+}}, at address
+// CHECK-NEXT:  The instruction is     {{[0-9a-f]+}}:      autia   x0, x1
+// CHECK-NEXT:  The 1 instructions that leak the affected registers are:
+// CHECK-NEXT:  1.     {{[0-9a-f]+}}:      bl      callee
+        paciasp
+        stp     x29, x30, [sp, #-16]!
+        mov     x29, sp
+
+        autia   x0, x1
+        cbz     x1, 1f
+        ldr     x2, [x0]
+1:
+        bl      callee
+        ldr     x2, [x0]
+
+        ldp     x29, x30, [sp], #16
+        autiasp
+        ret
+        .size bad_leaked_to_subroutine_multi_bb, .-bad_leaked_to_subroutine_multi_bb
+
+        .globl  bad_unknown_usage_read_multi_bb
+        .type   bad_unknown_usage_read_multi_bb, at function
+bad_unknown_usage_read_multi_bb:
+// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_unknown_usage_read_multi_bb, basic block {{[^,]+}}, at address
+// CHECK-NEXT:  The instruction is     {{[0-9a-f]+}}:      autia   x0, x1
+// CHECK-NEXT:  The 0 instructions that leak the affected registers are:
+        autia   x0, x1
+        cbz     x3, 1f
+        mul     x3, x0, x1
+1:
+        ldr     x2, [x0]
+        ret
+        .size bad_unknown_usage_read_multi_bb, .-bad_unknown_usage_read_multi_bb
+
+        .globl  bad_unknown_usage_subreg_read_multi_bb
+        .type   bad_unknown_usage_subreg_read_multi_bb, at function
+bad_unknown_usage_subreg_read_multi_bb:
+// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_unknown_usage_subreg_read_multi_bb, basic block {{[^,]+}}, at address
+// CHECK-NEXT:  The instruction is     {{[0-9a-f]+}}:      autia   x0, x1
+// CHECK-NEXT:  The 0 instructions that leak the affected registers are:
+        autia   x0, x1
+        cbz     x3, 1f
+        mul     w3, w0, w1
+1:
+        ldr     x2, [x0]
+        ret
+        .size bad_unknown_usage_subreg_read_multi_bb, .-bad_unknown_usage_subreg_read_multi_bb
+
+        .globl  bad_unknown_usage_update_multi_bb
+        .type   bad_unknown_usage_update_multi_bb, at function
+bad_unknown_usage_update_multi_bb:
+// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_unknown_usage_update_multi_bb, basic block {{[^,]+}}, at address
+// CHECK-NEXT:  The instruction is     {{[0-9a-f]+}}:      autia   x0, x1
+// CHECK-NEXT:  The 0 instructions that leak the affected registers are:
+        autia   x0, x1
+        cbz     x3, 1f
+        movk    x0, #42, lsl #16  // does not overwrite x0 completely
+1:
+        ldr     x2, [x0]
+        ret
+        .size bad_unknown_usage_update_multi_bb, .-bad_unknown_usage_update_multi_bb
+
+        .globl  good_unknown_overwrite_multi_bb
+        .type   good_unknown_overwrite_multi_bb, at function
+good_unknown_overwrite_multi_bb:
+// CHECK-NOT: good_unknown_overwrite_multi_bb
+        autia   x0, x1
+        cbz     x3, 1f
+1:
+        mul     x0, x1, x2
+        ret
+        .size good_unknown_overwrite_multi_bb, .-good_unknown_overwrite_multi_bb
+
+        .globl  good_address_arith_multi_bb
+        .type   good_address_arith_multi_bb, at function
+good_address_arith_multi_bb:
+// CHECK-NOT: good_address_arith_multi_bb
+        autia   x0, x1
+        cbz     x3, 1f
+
+        add     x1, x0, #8
+        sub     x2, x1, #16
+        mov     x0, x2
+
+        mov     x1, #0
+        mov     x2, #0
+1:
+        ldr     x3, [x0]
+        ret
+        .size good_address_arith_multi_bb, .-good_address_arith_multi_bb
+
+        .globl  good_ret_nocfg
+        .type   good_ret_nocfg, at function
+good_ret_nocfg:
+// CHECK-NOT: good_ret_nocfg
+        adr     x2, 1f
+        br      x2
+1:
+        autia   x0, x1
+
+        ret     x0
+        .size good_ret_nocfg, .-good_ret_nocfg
+
+        .globl  good_call_nocfg
+        .type   good_call_nocfg, at function
+good_call_nocfg:
+// CHECK-NOT: good_call_nocfg
+        paciasp
+        stp     x29, x30, [sp, #-16]!
+        mov     x29, sp
+
+        adr     x2, 1f
+        br      x2
+1:
+        autia   x0, x1
+        blr     x0
+
+        ldp     x29, x30, [sp], #16
+        autiasp
+        ret
+        .size good_call_nocfg, .-good_call_nocfg
+
+        .globl  good_branch_nocfg
+        .type   good_branch_nocfg, at function
+good_branch_nocfg:
+// CHECK-NOT: good_branch_nocfg
+        paciasp
+        adr     x2, 1f
+        br      x2
+1:
+        autia   x0, x1
+        autiasp            // authenticate LR before tail call
+        ldr     x2, [x30]  // check LR before tail call
+        br      x0
+        .size good_branch_nocfg, .-good_branch_nocfg
+
+        .globl  good_load_other_reg_nocfg
+        .type   good_load_other_reg_nocfg, at function
+good_load_other_reg_nocfg:
+// CHECK-NOT: good_load_other_reg_nocfg
+        paciasp
+        adr     x2, 1f
+        br      x2
+1:
+        autia   x0, x1
+        ldr     x2, [x0]
+
+        autiasp
+        ret
+        .size good_load_other_reg_nocfg, .-good_load_other_reg_nocfg
+
+        .globl  good_load_same_reg_nocfg
+        .type   good_load_same_reg_nocfg, at function
+good_load_same_reg_nocfg:
+// CHECK-NOT: good_load_same_reg_nocfg
+        paciasp
+        adr     x2, 1f
+        br      x2
+1:
+        autia   x0, x1
+        ldr     x0, [x0]
+
+        autiasp
+        ret
+        .size good_load_same_reg_nocfg, .-good_load_same_reg_nocfg
+
+// FIXME: Multi-instruction checker sequences are not supported without CFG.
+
+        .globl  bad_unchecked_nocfg
+        .type   bad_unchecked_nocfg, at function
+bad_unchecked_nocfg:
+// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_unchecked_nocfg, at address
+// CHECK-NEXT:  The instruction is     {{[0-9a-f]+}}:      autia   x0, x1
+// CHECK-NEXT:  The 0 instructions that leak the affected registers are:
+        paciasp
+        adr     x2, 1f
+        br      x2
+1:
+        autia   x0, x1
+
+        autiasp
+        ret
+        .size bad_unchecked_nocfg, .-bad_unchecked_nocfg
+
+        .globl  bad_leaked_to_subroutine_nocfg
+        .type   bad_leaked_to_subroutine_nocfg, at function
+bad_leaked_to_subroutine_nocfg:
+// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_leaked_to_subroutine_nocfg, at address
+// CHECK-NEXT:  The instruction is     {{[0-9a-f]+}}:      autia   x0, x1
+// CHECK-NEXT:  The 1 instructions that leak the affected registers are:
+// CHECK-NEXT:  1.     {{[0-9a-f]+}}:      bl      callee # Offset: 24
+        paciasp
+        stp     x29, x30, [sp, #-16]!
+        mov     x29, sp
+
+        adr     x2, 1f
+        br      x2
+1:
+        autia   x0, x1
+        bl      callee
+        ldr     x2, [x0]
+
+        ldp     x29, x30, [sp], #16
+        autiasp
+        ret
+        .size bad_leaked_to_subroutine_nocfg, .-bad_leaked_to_subroutine_nocfg
+
+        .globl  bad_unknown_usage_read_nocfg
+        .type   bad_unknown_usage_read_nocfg, at function
+bad_unknown_usage_read_nocfg:
+// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_unknown_usage_read_nocfg, at address
+// CHECK-NEXT:  The instruction is     {{[0-9a-f]+}}:      autia   x0, x1
+// CHECK-NEXT:  The 1 instructions that leak the affected registers are:
+// CHECK-NEXT:  1.     {{[0-9a-f]+}}:      mul     x3, x0, x1
+        paciasp
+        adr     x2, 1f
+        br      x2
+1:
+        autia   x0, x1
+        mul     x3, x0, x1
+        ldr     x2, [x0]
+
+        autiasp
+        ret
+        .size bad_unknown_usage_read_nocfg, .-bad_unknown_usage_read_nocfg
+
+        .globl  bad_unknown_usage_subreg_read_nocfg
+        .type   bad_unknown_usage_subreg_read_nocfg, at function
+bad_unknown_usage_subreg_read_nocfg:
+// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_unknown_usage_subreg_read_nocfg, at address
+// CHECK-NEXT:  The instruction is     {{[0-9a-f]+}}:      autia   x0, x1
+// CHECK-NEXT:  The 1 instructions that leak the affected registers are:
+// CHECK-NEXT:  1.     {{[0-9a-f]+}}:      mul     w3, w0, w1
+        paciasp
+        adr     x2, 1f
+        br      x2
+1:
+        autia   x0, x1
+        mul     w3, w0, w1
+        ldr     x2, [x0]
+
+        autiasp
+        ret
+        .size bad_unknown_usage_subreg_read_nocfg, .-bad_unknown_usage_subreg_read_nocfg
+
+        .globl  bad_unknown_usage_update_nocfg
+        .type   bad_unknown_usage_update_nocfg, at function
+bad_unknown_usage_update_nocfg:
+// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_unknown_usage_update_nocfg, at address
+// CHECK-NEXT:  The instruction is     {{[0-9a-f]+}}:      autia   x0, x1
+// CHECK-NEXT:  The 1 instructions that leak the affected registers are:
+// CHECK-NEXT:  1.     {{[0-9a-f]+}}:      movk    x0, #0x2a, lsl #16
+        paciasp
+        adr     x2, 1f
+        br      x2
+1:
+        autia   x0, x1
+        movk    x0, #42, lsl #16  // does not overwrite x0 completely
+        ldr     x2, [x0]
+
+        autiasp
+        ret
+        .size bad_unknown_usage_update_nocfg, .-bad_unknown_usage_update_nocfg
+
+        .globl  good_unknown_overwrite_nocfg
+        .type   good_unknown_overwrite_nocfg, at function
+good_unknown_overwrite_nocfg:
+// CHECK-NOT: good_unknown_overwrite_nocfg
+        paciasp
+        adr     x2, 1f
+        br      x2
+1:
+        autia   x0, x1
+        mul     x0, x1, x2
+
+        autiasp
+        ret
+        .size good_unknown_overwrite_nocfg, .-good_unknown_overwrite_nocfg
+
+        .globl  good_address_arith_nocfg
+        .type   good_address_arith_nocfg, at function
+good_address_arith_nocfg:
+// CHECK-NOT: good_address_arith_nocfg
+        paciasp
+        adr     x2, 1f
+        br      x2
+1:
+        autia   x0, x1
+        add     x1, x0, #8
+        sub     x2, x1, #16
+        mov     x3, x2
+
+        ldr     x4, [x3]
+        mov     x0, #0
+        mov     x1, #0
+        mov     x2, #0
+
+        autiasp
+        ret
+        .size good_address_arith_nocfg, .-good_address_arith_nocfg
+
+        .globl  good_explicit_check_unrelated_reg
+        .type   good_explicit_check_unrelated_reg, at function
+good_explicit_check_unrelated_reg:
+// CHECK-LABEL: GS-PAUTH: authentication oracle found in function good_explicit_check_unrelated_reg, basic block {{[^,]+}}, at address
+        // FIXME: The below instruction is not an authentication oracle
+        autia   x2, x3    // One of possible execution paths after this instruction
+                          // ends at BRK below, thus BRK used as a trap instruction
+                          // should formally "check everything" not to introduce
+                          // false-positive here.
+        autia   x0, x1
+        eor     x16, x0, x0, lsl #1
+        tbz     x16, #62, 1f
+        brk     0x1234
+1:
+        ldr     x4, [x2]  // Right before this instruction X2 is checked - this
+                          // should be propagated to the basic block ending with
+                          // TBZ instruction above.
+        ret
+        .size good_explicit_check_unrelated_reg, .-good_explicit_check_unrelated_reg
+
+// The last BB (in layout order) is processed first by the data-flow analysis.
+// Its initial state is usually filled in a special way (because it ends with
+// `ret` instruction), and then affects the state propagated to the other BBs
+// Thus, the case of the last instruction in a function being a jump somewhere
+// in the middle is special.
+
+        .globl  good_no_ret_from_last_bb
+        .type   good_no_ret_from_last_bb, at function
+good_no_ret_from_last_bb:
+// CHECK-NOT: good_no_ret_from_last_bb
+        paciasp
+        autiasp     // authenticates LR
+        b       2f
+1:
+        ret
+2:
+        b       1b  // LR is dereferenced by `ret`, which is executed next
+        .size good_no_ret_from_last_bb, .-good_no_ret_from_last_bb
+
+        .globl  bad_no_ret_from_last_bb
+        .type   bad_no_ret_from_last_bb, at function
+bad_no_ret_from_last_bb:
+// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_no_ret_from_last_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:
+        paciasp
+        autiasp     // authenticates LR
+        b       2f
+1:
+        ret     x0
+2:
+        b       1b  // X0 (but not LR) is dereferenced by `ret x0`
+        .size bad_no_ret_from_last_bb, .-bad_no_ret_from_last_bb
+
+// Test that combined auth+something instructions are not reported as
+// authentication oracles.
+
+        .globl  inst_retaa
+        .type   inst_retaa, at function
+inst_retaa:
+// CHECK-NOT: inst_retaa
+        paciasp
+        retaa
+        .size inst_retaa, .-inst_retaa
+
+        .globl  inst_blraa
+        .type   inst_blraa, at function
+inst_blraa:
+// CHECK-NOT: inst_blraa
+        paciasp
+        stp     x29, x30, [sp, #-16]!
+        mov     x29, sp
+
+        blraa   x0, x1
+
+        ldp     x29, x30, [sp], #16
+        retaa
+        .size inst_blraa, .-inst_blraa
+
+        .globl  inst_braa
+        .type   inst_braa, at function
+inst_braa:
+// CHECK-NOT: inst_braa
+        braa    x0, x1
+        .size inst_braa, .-inst_braa
+
+        .globl  inst_ldraa_no_wb
+        .type   inst_ldraa_no_wb, at function
+inst_ldraa_no_wb:
+// CHECK-NOT: inst_ldraa_no_wb
+        ldraa   x1, [x0]
+        ret
+        .size inst_ldraa_no_wb, .-inst_ldraa_no_wb
+
+        .globl  inst_ldraa_wb
+        .type   inst_ldraa_wb, at function
+inst_ldraa_wb:
+// CHECK-NOT: inst_ldraa_wb
+        ldraa   x1, [x0]!
+        ret
+        .size inst_ldraa_wb, .-inst_ldraa_wb
+
+        .globl  main
+        .type   main, at function
+main:
+        mov     x0, 0
+        ret
+        .size   main, .-main
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 82494d834a15c..fd55880921d06 100644
--- a/bolt/test/binary-analysis/AArch64/gs-pauth-debug-output.s
+++ b/bolt/test/binary-analysis/AArch64/gs-pauth-debug-output.s
@@ -251,6 +251,84 @@ nocfg:
 // CHECK-EMPTY:
 // CHECK-NEXT:   Attaching clobbering info to:     00000000:   ret # Offset: 8 # CFGUnawareSrcSafetyAnalysis: src-state<SafeToDerefRegs: BitVector, TrustedRegs: BitVector, Insts: [0]()>
 
+        .globl  auth_oracle
+        .type   auth_oracle, at function
+auth_oracle:
+        autia   x0, x1
+        ret
+        .size auth_oracle, .-auth_oracle
+
+// CHECK-LABEL:Analyzing function auth_oracle, AllocatorId = 1
+// CHECK-NEXT: Binary Function "auth_oracle"  {
+// CHECK-NEXT:   Number      : 4
+// CHECK-NEXT:   State       : CFG constructed
+// ...
+// CHECK:        BB Layout   : [[BB0:[0-9a-zA-Z.]+]]
+// CHECK-NEXT: }
+// CHECK-NEXT: [[BB0]] (2 instructions, align : 1)
+// CHECK-NEXT:   Entry Point
+// CHECK-NEXT:     00000000:   autia   x0, x1
+// CHECK-NEXT:     00000004:   ret
+// CHECK-EMPTY:
+// CHECK-NEXT: DWARF CFI Instructions:
+// CHECK-NEXT:     <empty>
+// CHECK-NEXT: End of Function "auth_oracle"
+// CHECK-EMPTY:
+// CHECK-NEXT: Running src register safety analysis...
+// ...
+// CHECK:      After src register safety analysis:
+// CHECK-NEXT: Binary Function "auth_oracle"  {
+// ...
+// CHECK:      End of Function "auth_oracle"
+// ...
+// PAUTH:      Running dst register safety analysis...
+// PAUTH-NEXT:   DstSafetyAnalysis::ComputeNext(       ret     x30, dst-state<CannotEscapeUnchecked: >)
+// PAUTH-NEXT:     .. result: (dst-state<CannotEscapeUnchecked: LR W30 W30_HI >)
+// PAUTH-NEXT:   DstSafetyAnalysis::ComputeNext(       autia   x0, x1, dst-state<CannotEscapeUnchecked: LR W30 W30_HI >)
+// PAUTH-NEXT:     .. result: (dst-state<CannotEscapeUnchecked: LR W30 W30_HI >)
+// PAUTH-NEXT: After dst register safety analysis:
+// PAUTH-NEXT: Binary Function "auth_oracle"  {
+// PAUTH-NEXT:   Number      : 4
+// PAUTH-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>
+// PAUTH-NEXT:     00000004:   ret # DataflowDstSafetyAnalysis: dst-state<CannotEscapeUnchecked: BitVector>
+// 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>
+// PAUTH-NEXT:     Authenticated reg: X0
+// PAUTH-NEXT:     safe output registers: LR W30 W30_HI
+// PAUTH-EMPTY:
+// PAUTH-NEXT: Running detailed dst register safety analysis...
+// PAUTH-NEXT:   DstSafetyAnalysis::ComputeNext(       ret     x30, dst-state<CannotEscapeUnchecked: >)
+// PAUTH-NEXT:     .. result: (dst-state<CannotEscapeUnchecked: LR W30 W30_HI >)
+// PAUTH-NEXT:   DstSafetyAnalysis::ComputeNext(       autia   x0, x1, dst-state<CannotEscapeUnchecked: LR W30 W30_HI >)
+// PAUTH-NEXT:     .. result: (dst-state<CannotEscapeUnchecked: LR W30 W30_HI >)
+// PAUTH-NEXT: After detailed dst register safety analysis:
+// PAUTH-NEXT: Binary Function "auth_oracle"  {
+// PAUTH-NEXT:   Number      : 4
+// PAUTH-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>
+// PAUTH-NEXT:     00000004:   ret # DataflowDstSafetyAnalysis: dst-state<CannotEscapeUnchecked: BitVector>
+// 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>
+
 // CHECK-LABEL:Analyzing function main, AllocatorId = 1
         .globl  main
         .type   main, at function



More information about the llvm-branch-commits mailing list