#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"
#include "llvm/Support/Format.h"
#include <memory>
#define DEBUG_TYPE "bolt-pauth-scanner"
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 << ": ";
BC.printInstruction(dbgs(), MI);
}
[[maybe_unused]] static void traceReg(const BinaryContext &BC, StringRef Label,
MCPhysReg Reg) {
dbgs() << " " << Label << ": ";
if (Reg == BC.MIB->getNoRegister())
dbgs() << "(none)";
else
dbgs() << BC.MRI->getName(Reg);
dbgs() << "\n";
}
[[maybe_unused]] static void traceRegMask(const BinaryContext &BC,
StringRef Label, BitVector Mask) {
dbgs() << " " << Label << ": ";
RegStatePrinter(BC).print(dbgs(), Mask);
dbgs() << "\n";
}
template <typename T> static void iterateOverInstrs(BinaryFunction &BF, T Fn) {
if (BF.hasCFG()) {
for (BinaryBasicBlock &BB : BF)
for (int64_t I = 0, E = BB.size(); I < E; ++I)
Fn(MCInstReference(BB, I));
} else {
for (auto I = BF.instrs().begin(), E = BF.instrs().end(); I != E; ++I)
Fn(MCInstReference(BF, I));
}
}
class TrackedRegisters {
static constexpr uint16_t NoIndex = -1;
const std::vector<MCPhysReg> Registers;
std::vector<uint16_t> RegToIndexMapping;
static size_t getMappingSize(ArrayRef<MCPhysReg> RegsToTrack) {
if (RegsToTrack.empty())
return 0;
return 1 + *llvm::max_element(RegsToTrack);
}
public:
TrackedRegisters(ArrayRef<MCPhysReg> RegsToTrack)
: Registers(RegsToTrack),
RegToIndexMapping(getMappingSize(RegsToTrack), NoIndex) {
for (auto [MappedIndex, Reg] : llvm::enumerate(RegsToTrack))
RegToIndexMapping[Reg] = MappedIndex;
}
ArrayRef<MCPhysReg> getRegisters() const { return Registers; }
size_t getNumTrackedRegisters() const { return Registers.size(); }
bool empty() const { return Registers.empty(); }
bool isTracked(MCPhysReg Reg) const {
bool IsTracked = (unsigned)Reg < RegToIndexMapping.size() &&
RegToIndexMapping[Reg] != NoIndex;
assert(IsTracked == llvm::is_contained(Registers, Reg));
return IsTracked;
}
unsigned getIndex(MCPhysReg Reg) const {
assert(isTracked(Reg) && "Register is not tracked");
return RegToIndexMapping[Reg];
}
};
typedef SmallPtrSet<const MCInst *, 4> SetOfRelatedInsts;
struct SrcState {
BitVector SafeToDerefRegs;
BitVector TrustedRegs;
std::vector<SetOfRelatedInsts> LastInstWritingReg;
SrcState() {}
SrcState(unsigned NumRegs, unsigned NumRegsToTrack)
: SafeToDerefRegs(NumRegs), TrustedRegs(NumRegs),
LastInstWritingReg(NumRegsToTrack) {}
SrcState &merge(const SrcState &StateIn) {
if (StateIn.empty())
return *this;
if (empty())
return (*this = StateIn);
SafeToDerefRegs &= StateIn.SafeToDerefRegs;
TrustedRegs &= StateIn.TrustedRegs;
for (auto [ThisSet, OtherSet] :
llvm::zip_equal(LastInstWritingReg, StateIn.LastInstWritingReg))
ThisSet.insert_range(OtherSet);
return *this;
}
bool empty() const { return SafeToDerefRegs.empty(); }
bool operator==(const SrcState &RHS) const {
return SafeToDerefRegs == RHS.SafeToDerefRegs &&
TrustedRegs == RHS.TrustedRegs &&
LastInstWritingReg == RHS.LastInstWritingReg;
}
bool operator!=(const SrcState &RHS) const { return !((*this) == RHS); }
};
static void printInstsShort(raw_ostream &OS,
ArrayRef<SetOfRelatedInsts> Insts) {
OS << "Insts: ";
for (auto [I, PtrSet] : llvm::enumerate(Insts)) {
OS << "[" << I << "](";
interleave(PtrSet, OS, " ");
OS << ")";
}
}
static raw_ostream &operator<<(raw_ostream &OS, const SrcState &S) {
OS << "src-state<";
if (S.empty()) {
OS << "empty";
} else {
OS << "SafeToDerefRegs: " << S.SafeToDerefRegs << ", ";
OS << "TrustedRegs: " << S.TrustedRegs << ", ";
printInstsShort(OS, S.LastInstWritingReg);
}
OS << ">";
return OS;
}
class SrcStatePrinter {
public:
void print(raw_ostream &OS, const SrcState &State) const;
explicit SrcStatePrinter(const BinaryContext &BC) : BC(BC) {}
private:
const BinaryContext &BC;
};
void SrcStatePrinter::print(raw_ostream &OS, const SrcState &S) const {
RegStatePrinter RegStatePrinter(BC);
OS << "src-state<";
if (S.empty()) {
assert(S.SafeToDerefRegs.empty());
assert(S.TrustedRegs.empty());
assert(S.LastInstWritingReg.empty());
OS << "empty";
} else {
OS << "SafeToDerefRegs: ";
RegStatePrinter.print(OS, S.SafeToDerefRegs);
OS << ", TrustedRegs: ";
RegStatePrinter.print(OS, S.TrustedRegs);
OS << ", ";
printInstsShort(OS, S.LastInstWritingReg);
}
OS << ">";
}
class SrcSafetyAnalysis {
public:
SrcSafetyAnalysis(BinaryFunction &BF, ArrayRef<MCPhysReg> RegsToTrackInstsFor)
: BC(BF.getBinaryContext()), NumRegs(BC.MRI->getNumRegs()),
RegsToTrackInstsFor(RegsToTrackInstsFor) {}
virtual ~SrcSafetyAnalysis() {}
static std::shared_ptr<SrcSafetyAnalysis>
create(BinaryFunction &BF, MCPlusBuilder::AllocatorIdTy AllocId,
ArrayRef<MCPhysReg> RegsToTrackInstsFor);
virtual void run() = 0;
virtual const SrcState &getStateBefore(const MCInst &Inst) const = 0;
protected:
BinaryContext &BC;
const unsigned NumRegs;
const TrackedRegisters RegsToTrackInstsFor;
DenseMap<const MCInst *, std::pair<MCPhysReg, const MCInst *>>
CheckerSequenceInfo;
SetOfRelatedInsts &lastWritingInsts(SrcState &S, MCPhysReg Reg) const {
unsigned Index = RegsToTrackInstsFor.getIndex(Reg);
return S.LastInstWritingReg[Index];
}
const SetOfRelatedInsts &lastWritingInsts(const SrcState &S,
MCPhysReg Reg) const {
unsigned Index = RegsToTrackInstsFor.getIndex(Reg);
return S.LastInstWritingReg[Index];
}
SrcState createEntryState() {
SrcState S(NumRegs, RegsToTrackInstsFor.getNumTrackedRegisters());
for (MCPhysReg Reg : BC.MIB->getTrustedLiveInRegs())
S.TrustedRegs |= BC.MIB->getAliases(Reg, true);
S.SafeToDerefRegs = S.TrustedRegs;
return S;
}
SrcState computePessimisticState(BinaryFunction &BF) {
BitVector ClobberedRegs(NumRegs);
iterateOverInstrs(BF, [&](MCInstReference Inst) {
BC.MIB->getClobberedRegs(Inst, ClobberedRegs);
if (BC.MIB->isCall(Inst) && !BC.MIB->isTailCall(Inst))
ClobberedRegs.set();
});
SrcState S = createEntryState();
S.SafeToDerefRegs.reset(ClobberedRegs);
S.TrustedRegs.reset(ClobberedRegs);
return S;
}
BitVector getClobberedRegs(const MCInst &Point) const {
BitVector Clobbered(NumRegs);
if (BC.MIB->isCall(Point))
Clobbered.set();
else
BC.MIB->getClobberedRegs(Point, Clobbered);
return Clobbered;
}
std::optional<MCPhysReg> getRegMadeTrustedByChecking(const MCInst &Inst,
SrcState Cur) const {
std::optional<MCPhysReg> RegCheckedByInst =
BC.MIB->getAuthCheckedReg(Inst, 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;
const SrcState &StateBeforeChecker = getStateBefore(*FirstCheckerInst);
if (!StateBeforeChecker.SafeToDerefRegs[RegCheckedBySequence])
return std::nullopt;
return RegCheckedBySequence;
}
SmallVector<MCPhysReg> getRegsMadeSafeToDeref(const MCInst &Point,
const SrcState &Cur) const {
SmallVector<MCPhysReg> Regs;
bool Dummy = false;
if (auto AutReg = BC.MIB->getWrittenAuthenticatedReg(Point, Dummy))
Regs.push_back(*AutReg);
if (auto NewAddrReg = BC.MIB->getMaterializedAddressRegForPtrAuth(Point))
Regs.push_back(*NewAddrReg);
if (auto DstAndSrc = BC.MIB->analyzeAddressArithmeticsForPtrAuth(Point)) {
auto [DstReg, SrcReg] = *DstAndSrc;
if (Cur.SafeToDerefRegs[SrcReg])
Regs.push_back(DstReg);
}
if (auto CheckedReg = getRegMadeTrustedByChecking(Point, Cur))
Regs.push_back(*CheckedReg);
return Regs;
}
SmallVector<MCPhysReg> getRegsMadeTrusted(const MCInst &Point,
const SrcState &Cur) const {
assert(!AuthTrapsOnFailure && "Use getRegsMadeSafeToDeref instead");
SmallVector<MCPhysReg> Regs;
if (auto CheckedReg = getRegMadeTrustedByChecking(Point, Cur))
Regs.push_back(*CheckedReg);
bool IsChecked = false;
std::optional<MCPhysReg> AutReg =
BC.MIB->getWrittenAuthenticatedReg(Point, IsChecked);
if (AutReg && IsChecked)
Regs.push_back(*AutReg);
if (auto NewAddrReg = BC.MIB->getMaterializedAddressRegForPtrAuth(Point))
Regs.push_back(*NewAddrReg);
if (auto DstAndSrc = BC.MIB->analyzeAddressArithmeticsForPtrAuth(Point)) {
auto [DstReg, SrcReg] = *DstAndSrc;
if (Cur.TrustedRegs[SrcReg])
Regs.push_back(DstReg);
}
return Regs;
}
SrcState computeNext(const MCInst &Point, const SrcState &Cur) {
if (BC.MIB->isCFI(Point))
return Cur;
SrcStatePrinter P(BC);
LLVM_DEBUG({
dbgs() << " SrcSafetyAnalysis::ComputeNext(";
BC.InstPrinter->printInst(&Point, 0, "", *BC.STI, dbgs());
dbgs() << ", ";
P.print(dbgs(), Cur);
dbgs() << ")\n";
});
if (Cur.empty()) {
LLVM_DEBUG(
{ dbgs() << "Skipping computeNext(Point, Cur) as Cur is empty.\n"; });
return SrcState();
}
BitVector Clobbered = getClobberedRegs(Point);
SmallVector<MCPhysReg> NewSafeToDerefRegs =
getRegsMadeSafeToDeref(Point, Cur);
SmallVector<MCPhysReg> NewTrustedRegs =
AuthTrapsOnFailure ? NewSafeToDerefRegs
: getRegsMadeTrusted(Point, Cur);
SrcState Next = Cur;
Next.SafeToDerefRegs.reset(Clobbered);
Next.TrustedRegs.reset(Clobbered);
for (MCPhysReg Reg : RegsToTrackInstsFor.getRegisters())
if (Clobbered[Reg])
lastWritingInsts(Next, Reg) = {&Point};
BitVector NewSafeSubregs(NumRegs);
for (MCPhysReg SafeReg : NewSafeToDerefRegs)
NewSafeSubregs |= BC.MIB->getAliases(SafeReg, true);
for (MCPhysReg Reg : NewSafeSubregs.set_bits()) {
Next.SafeToDerefRegs.set(Reg);
if (RegsToTrackInstsFor.isTracked(Reg))
lastWritingInsts(Next, Reg).clear();
}
for (MCPhysReg TrustedReg : NewTrustedRegs)
Next.TrustedRegs |= BC.MIB->getAliases(TrustedReg, true);
LLVM_DEBUG({
dbgs() << " .. result: (";
P.print(dbgs(), Next);
dbgs() << ")\n";
});
assert(!Next.TrustedRegs.test(Next.SafeToDerefRegs) &&
"SafeToDerefRegs should contain all TrustedRegs");
return Next;
}
public:
std::vector<MCInstReference>
getLastClobberingInsts(const MCInst &Inst, BinaryFunction &BF,
MCPhysReg ClobberedReg) const {
const SrcState &S = getStateBefore(Inst);
std::vector<MCInstReference> Result;
for (const MCInst *Inst : lastWritingInsts(S, ClobberedReg))
Result.push_back(MCInstReference::get(*Inst, BF));
return Result;
}
};
class DataflowSrcSafetyAnalysis
: public SrcSafetyAnalysis,
public DataflowAnalysis<DataflowSrcSafetyAnalysis, SrcState,
false, SrcStatePrinter> {
using DFParent = DataflowAnalysis<DataflowSrcSafetyAnalysis, SrcState, false,
SrcStatePrinter>;
friend DFParent;
using SrcSafetyAnalysis::BC;
using SrcSafetyAnalysis::computeNext;
SrcState PessimisticState;
public:
DataflowSrcSafetyAnalysis(BinaryFunction &BF,
MCPlusBuilder::AllocatorIdTy AllocId,
ArrayRef<MCPhysReg> RegsToTrackInstsFor)
: SrcSafetyAnalysis(BF, RegsToTrackInstsFor), DFParent(BF, AllocId) {}
const SrcState &getStateBefore(const MCInst &Inst) const override {
return DFParent::getStateBefore(Inst).get();
}
void run() override {
for (BinaryBasicBlock &BB : Func) {
if (auto CheckerInfo = BC.MIB->getAuthCheckedReg(BB)) {
MCPhysReg CheckedReg = CheckerInfo->first;
MCInst &FirstInst = *CheckerInfo->second;
MCInst &LastInst = *BB.getLastNonPseudoInstr();
LLVM_DEBUG({
dbgs() << "Found pointer checking sequence in " << BB.getName()
<< ":\n";
traceReg(BC, "Checked register", CheckedReg);
traceInst(BC, "First instruction", FirstInst);
traceInst(BC, "Last instruction", LastInst);
});
(void)CheckedReg;
(void)FirstInst;
assert(llvm::any_of(BB, [&](MCInst &I) { return &I == &FirstInst; }) &&
"Data-flow analysis expects the checker not to cross BBs");
CheckerSequenceInfo[&LastInst] = *CheckerInfo;
}
}
DFParent::run();
}
protected:
void preflight() {}
SrcState getStartingStateAtBB(const BinaryBasicBlock &BB) {
if (BB.isEntryPoint())
return createEntryState();
if (BB.pred_empty()) {
if (PessimisticState.empty())
PessimisticState = computePessimisticState(*BB.getParent());
return PessimisticState;
}
return SrcState();
}
SrcState getStartingStateAtPoint(const MCInst &Point) { return SrcState(); }
void doConfluence(SrcState &StateOut, const SrcState &StateIn) {
SrcStatePrinter P(BC);
LLVM_DEBUG({
dbgs() << " DataflowSrcSafetyAnalysis::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 "DataflowSrcSafetyAnalysis"; }
};
template <typename StateTy> class CFGUnawareAnalysis {
BinaryContext &BC;
BinaryFunction &BF;
MCPlusBuilder::AllocatorIdTy AllocId;
unsigned StateAnnotationIndex;
void cleanStateAnnotations() {
for (auto &I : BF.instrs())
BC.MIB->removeAnnotation(I.second, StateAnnotationIndex);
}
protected:
CFGUnawareAnalysis(BinaryFunction &BF, MCPlusBuilder::AllocatorIdTy AllocId,
StringRef AnnotationName)
: BC(BF.getBinaryContext()), BF(BF), AllocId(AllocId) {
StateAnnotationIndex = BC.MIB->getOrCreateAnnotationIndex(AnnotationName);
}
void setState(MCInst &Inst, const StateTy &S) {
if (BC.MIB->hasAnnotation(Inst, StateAnnotationIndex))
BC.MIB->removeAnnotation(Inst, StateAnnotationIndex);
BC.MIB->addAnnotation(Inst, StateAnnotationIndex, S, AllocId);
}
const StateTy &getState(const MCInst &Inst) const {
return BC.MIB->getAnnotationAs<StateTy>(Inst, StateAnnotationIndex);
}
virtual ~CFGUnawareAnalysis() { cleanStateAnnotations(); }
};
class CFGUnawareSrcSafetyAnalysis : public SrcSafetyAnalysis,
public CFGUnawareAnalysis<SrcState> {
using SrcSafetyAnalysis::BC;
BinaryFunction &BF;
public:
CFGUnawareSrcSafetyAnalysis(BinaryFunction &BF,
MCPlusBuilder::AllocatorIdTy AllocId,
ArrayRef<MCPhysReg> RegsToTrackInstsFor)
: SrcSafetyAnalysis(BF, RegsToTrackInstsFor),
CFGUnawareAnalysis(BF, AllocId, "CFGUnawareSrcSafetyAnalysis"), BF(BF) {
}
void run() override {
const SrcState DefaultState = computePessimisticState(BF);
SrcState S = createEntryState();
for (auto &I : BF.instrs()) {
MCInst &Inst = I.second;
if (BC.MIB->isCFI(Inst))
continue;
if (BF.hasLabelAt(I.first) && &Inst != &BF.instrs().begin()->second) {
LLVM_DEBUG({
traceInst(BC, "Due to label, resetting the state before", Inst);
});
S = DefaultState;
}
setState(Inst, S);
S = computeNext(Inst, S);
}
}
const SrcState &getStateBefore(const MCInst &Inst) const override {
return getState(Inst);
}
};
std::shared_ptr<SrcSafetyAnalysis>
SrcSafetyAnalysis::create(BinaryFunction &BF,
MCPlusBuilder::AllocatorIdTy AllocId,
ArrayRef<MCPhysReg> RegsToTrackInstsFor) {
if (BF.hasCFG())
return std::make_shared<DataflowSrcSafetyAnalysis>(BF, AllocId,
RegsToTrackInstsFor);
return std::make_shared<CFGUnawareSrcSafetyAnalysis>(BF, AllocId,
RegsToTrackInstsFor);
}
struct DstState {
BitVector CannotEscapeUnchecked;
std::vector<SetOfRelatedInsts> FirstInstLeakingReg;
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;
for (auto [ThisSet, OtherSet] :
llvm::zip_equal(FirstInstLeakingReg, StateIn.FirstInstLeakingReg))
ThisSet.insert_range(OtherSet);
return *this;
}
bool empty() const { return CannotEscapeUnchecked.empty(); }
bool operator==(const DstState &RHS) const {
return CannotEscapeUnchecked == RHS.CannotEscapeUnchecked &&
FirstInstLeakingReg == RHS.FirstInstLeakingReg;
}
bool operator!=(const DstState &RHS) const { return !((*this) == RHS); }
};
static raw_ostream &operator<<(raw_ostream &OS, const DstState &S) {
OS << "dst-state<";
if (S.empty()) {
OS << "empty";
} else {
OS << "CannotEscapeUnchecked: " << S.CannotEscapeUnchecked << ", ";
printInstsShort(OS, S.FirstInstLeakingReg);
}
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());
assert(S.FirstInstLeakingReg.empty());
OS << "empty";
} else {
OS << "CannotEscapeUnchecked: ";
RegStatePrinter.print(OS, S.CannotEscapeUnchecked);
OS << ", ";
printInstsShort(OS, S.FirstInstLeakingReg);
}
OS << ">";
}
class DstSafetyAnalysis {
public:
DstSafetyAnalysis(BinaryFunction &BF, 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,
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;
DenseMap<const MCInst *, MCPhysReg> RegCheckedAt;
SetOfRelatedInsts &firstLeakingInsts(DstState &S, MCPhysReg Reg) const {
unsigned Index = RegsToTrackInstsFor.getIndex(Reg);
return S.FirstInstLeakingReg[Index];
}
const SetOfRelatedInsts &firstLeakingInsts(const DstState &S,
MCPhysReg Reg) const {
unsigned Index = RegsToTrackInstsFor.getIndex(Reg);
return S.FirstInstLeakingReg[Index];
}
DstState createUnsafeState() {
return DstState(NumRegs, RegsToTrackInstsFor.getNumTrackedRegisters());
}
BitVector getLeakedRegs(const MCInst &Inst) const {
BitVector Leaked(NumRegs);
if (BC.MIB->isCall(Inst)) {
Leaked.set();
return Leaked;
}
const MCInstrDesc &Desc = BC.MII->get(Inst.getOpcode());
for (MCPhysReg Reg : Desc.implicit_uses())
Leaked |= BC.MIB->getAliases(Reg, false);
for (const MCOperand &Op : BC.MIB->useOperands(Inst)) {
if (Op.isReg())
Leaked |= BC.MIB->getAliases(Op.getReg(), false);
}
return Leaked;
}
SmallVector<MCPhysReg> getRegsMadeProtected(const MCInst &Inst,
const BitVector &LeakedRegs,
const DstState &Cur) const {
SmallVector<MCPhysReg> Regs;
if (auto CheckedReg =
BC.MIB->getAuthCheckedReg(Inst, true))
Regs.push_back(*CheckedReg);
if (RegCheckedAt.contains(&Inst))
Regs.push_back(RegCheckedAt.at(&Inst));
if (BC.MIB->isIndirectBranch(Inst) || BC.MIB->isIndirectCall(Inst)) {
bool IsAuthenticated;
MCPhysReg BranchDestReg =
BC.MIB->getRegUsedAsIndirectBranchDest(Inst, IsAuthenticated);
assert(BranchDestReg != BC.MIB->getNoRegister());
if (!IsAuthenticated)
Regs.push_back(BranchDestReg);
}
if (BC.MIB->isReturn(Inst)) {
bool IsAuthenticated = false;
std::optional<MCPhysReg> RetReg =
BC.MIB->getRegUsedAsRetDest(Inst, IsAuthenticated);
if (RetReg && !IsAuthenticated)
Regs.push_back(*RetReg);
}
if (auto DstAndSrc = BC.MIB->analyzeAddressArithmeticsForPtrAuth(Inst)) {
auto [DstReg, SrcReg] = *DstAndSrc;
if (Cur.CannotEscapeUnchecked[SrcReg] &&
Cur.CannotEscapeUnchecked[DstReg])
Regs.push_back(SrcReg);
}
const MCInstrDesc &Desc = BC.MII->get(Inst.getOpcode());
bool HasExplicitSrcRegs = llvm::any_of(BC.MIB->useOperands(Inst),
[](auto Op) { return Op.isReg(); });
if (!Desc.hasUnmodeledSideEffects() && !HasExplicitSrcRegs &&
Desc.implicit_uses().empty()) {
for (const MCOperand &Def : BC.MIB->defOperands(Inst))
Regs.push_back(Def.getReg());
}
return Regs;
}
DstState computeNext(const MCInst &Point, const DstState &Cur) {
if (BC.MIB->isCFI(Point))
return Cur;
DstStatePrinter P(BC);
LLVM_DEBUG({
dbgs() << " DstSafetyAnalysis::ComputeNext(";
BC.InstPrinter->printInst(&Point, 0, "", *BC.STI, dbgs());
dbgs() << ", ";
P.print(dbgs(), Cur);
dbgs() << ")\n";
});
if (BC.MIB->isTrap(Point)) {
LLVM_DEBUG(traceInst(BC, "Trap instruction found", Point));
DstState Next(NumRegs, RegsToTrackInstsFor.getNumTrackedRegisters());
Next.CannotEscapeUnchecked.set();
return Next;
}
if (Cur.empty()) {
LLVM_DEBUG(
{ dbgs() << "Skipping computeNext(Point, Cur) as Cur is empty.\n"; });
return DstState();
}
BitVector LeakedRegs = getLeakedRegs(Point);
SmallVector<MCPhysReg> NewProtectedRegs =
getRegsMadeProtected(Point, LeakedRegs, Cur);
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, 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,
MCPhysReg LeakedReg) const {
const DstState &S = getStateAfter(Inst);
std::vector<MCInstReference> Result;
for (const MCInst *Inst : firstLeakingInsts(S, LeakedReg))
Result.push_back(MCInstReference::get(*Inst, BF));
return Result;
}
};
class DataflowDstSafetyAnalysis
: public DstSafetyAnalysis,
public DataflowAnalysis<DataflowDstSafetyAnalysis, DstState,
true, DstStatePrinter> {
using DFParent = DataflowAnalysis<DataflowDstSafetyAnalysis, DstState, true,
DstStatePrinter>;
friend DFParent;
using DstSafetyAnalysis::BC;
using DstSafetyAnalysis::computeNext;
public:
DataflowDstSafetyAnalysis(BinaryFunction &BF,
MCPlusBuilder::AllocatorIdTy AllocId,
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 {
assert(!AuthTrapsOnFailure &&
"DstSafetyAnalysis is useless with faulting auth");
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) {
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,
public CFGUnawareAnalysis<DstState> {
using DstSafetyAnalysis::BC;
BinaryFunction &BF;
public:
CFGUnawareDstSafetyAnalysis(BinaryFunction &BF,
MCPlusBuilder::AllocatorIdTy AllocId,
ArrayRef<MCPhysReg> RegsToTrackInstsFor)
: DstSafetyAnalysis(BF, RegsToTrackInstsFor),
CFGUnawareAnalysis(BF, AllocId, "CFGUnawareDstSafetyAnalysis"), BF(BF) {
}
void run() override {
DstState S = createUnsafeState();
for (auto &I : llvm::reverse(BF.instrs())) {
MCInst &Inst = I.second;
if (BC.MIB->isCFI(Inst))
continue;
if (BC.MIB->isCall(Inst) || BC.MIB->isBranch(Inst) ||
BC.MIB->isReturn(Inst)) {
LLVM_DEBUG(traceInst(BC, "Control flow instruction", Inst));
S = createUnsafeState();
}
setState(Inst, S);
S = computeNext(Inst, S);
}
}
const DstState &getStateAfter(const MCInst &Inst) const override {
return getState(Inst);
}
};
std::shared_ptr<DstSafetyAnalysis>
DstSafetyAnalysis::create(BinaryFunction &BF,
MCPlusBuilder::AllocatorIdTy AllocId,
ArrayRef<MCPhysReg> RegsToTrackInstsFor) {
if (BF.hasCFG())
return std::make_shared<DataflowDstSafetyAnalysis>(BF, AllocId,
RegsToTrackInstsFor);
return std::make_shared<CFGUnawareDstSafetyAnalysis>(BF, AllocId,
RegsToTrackInstsFor);
}
static PartialReport<MCPhysReg> make_generic_report(MCInstReference Location,
StringRef Text) {
auto Report = std::make_shared<GenericDiagnostic>(Location, Text);
return PartialReport<MCPhysReg>(Report, std::nullopt);
}
template <typename T>
static PartialReport<T> make_gadget_report(const GadgetKind &Kind,
MCInstReference Location,
T RequestedDetails) {
auto Report = std::make_shared<GadgetDiagnostic>(Kind, Location);
return PartialReport<T>(Report, RequestedDetails);
}
static std::optional<PartialReport<MCPhysReg>>
shouldReportReturnGadget(const BinaryContext &BC, const MCInstReference &Inst,
const SrcState &S) {
static const GadgetKind RetKind("non-protected ret found");
if (!BC.MIB->isReturn(Inst))
return std::nullopt;
bool IsAuthenticated = false;
std::optional<MCPhysReg> RetReg =
BC.MIB->getRegUsedAsRetDest(Inst, IsAuthenticated);
if (!RetReg) {
return make_generic_report(
Inst, "Warning: pac-ret analysis could not analyze this return "
"instruction");
}
if (IsAuthenticated)
return std::nullopt;
LLVM_DEBUG({
traceInst(BC, "Found RET inst", Inst);
traceReg(BC, "RetReg", *RetReg);
traceRegMask(BC, "SafeToDerefRegs", S.SafeToDerefRegs);
});
if (S.SafeToDerefRegs[*RetReg])
return std::nullopt;
return make_gadget_report(RetKind, Inst, *RetReg);
}
static bool shouldAnalyzeTailCallInst(const BinaryContext &BC,
const BinaryFunction &BF,
const MCInstReference &Inst) {
const MCInstrDesc &Desc = BC.MII->get(Inst.getMCInst().getOpcode());
if (!Desc.isBranch())
return false;
if (BC.MIB->isTailCall(Inst))
return true;
bool IsUnknownControlFlow =
BC.MIB->isIndirectBranch(Inst) && !BC.MIB->getJumpTable(Inst);
if (BF.hasCFG() && IsUnknownControlFlow)
return true;
return false;
}
static std::optional<PartialReport<MCPhysReg>>
shouldReportUnsafeTailCall(const BinaryContext &BC, const BinaryFunction &BF,
const MCInstReference &Inst, const SrcState &S) {
static const GadgetKind UntrustedLRKind(
"untrusted link register found before tail call");
if (!shouldAnalyzeTailCallInst(BC, BF, Inst))
return std::nullopt;
SmallVector<MCPhysReg> RegsToCheck = BC.MIB->getTrustedLiveInRegs();
LLVM_DEBUG({
traceInst(BC, "Found tail call inst", Inst);
traceRegMask(BC, "Trusted regs", S.TrustedRegs);
});
if (BC.StartFunctionAddress &&
*BC.StartFunctionAddress == Inst.getFunction()->getAddress()) {
LLVM_DEBUG(dbgs() << " Skipping tail call in ELF entry function.\n");
return std::nullopt;
}
for (auto Reg : RegsToCheck)
if (!S.TrustedRegs[Reg])
return make_gadget_report(UntrustedLRKind, Inst, Reg);
return std::nullopt;
}
static std::optional<PartialReport<MCPhysReg>>
shouldReportCallGadget(const BinaryContext &BC, const MCInstReference &Inst,
const SrcState &S) {
static const GadgetKind CallKind("non-protected call found");
if (!BC.MIB->isIndirectCall(Inst) && !BC.MIB->isIndirectBranch(Inst))
return std::nullopt;
bool IsAuthenticated = false;
MCPhysReg DestReg =
BC.MIB->getRegUsedAsIndirectBranchDest(Inst, IsAuthenticated);
if (IsAuthenticated)
return std::nullopt;
assert(DestReg != BC.MIB->getNoRegister() && "Valid register expected");
LLVM_DEBUG({
traceInst(BC, "Found call inst", Inst);
traceReg(BC, "Call destination reg", DestReg);
traceRegMask(BC, "SafeToDerefRegs", S.SafeToDerefRegs);
});
if (S.SafeToDerefRegs[DestReg])
return std::nullopt;
return make_gadget_report(CallKind, Inst, DestReg);
}
static std::optional<PartialReport<MCPhysReg>>
shouldReportSigningOracle(const BinaryContext &BC, const MCInstReference &Inst,
const SrcState &S) {
static const GadgetKind SigningOracleKind("signing oracle found");
std::optional<MCPhysReg> SignedReg = BC.MIB->getSignedReg(Inst);
if (!SignedReg)
return std::nullopt;
LLVM_DEBUG({
traceInst(BC, "Found sign inst", Inst);
traceReg(BC, "Signed reg", *SignedReg);
traceRegMask(BC, "TrustedRegs", S.TrustedRegs);
});
if (S.TrustedRegs[*SignedReg])
return std::nullopt;
return make_gadget_report(SigningOracleKind, Inst, *SignedReg);
}
static std::optional<PartialReport<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;
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_gadget_report(AuthOracleKind, Inst, *AuthReg);
}
static SmallVector<MCPhysReg>
collectRegsToTrack(ArrayRef<PartialReport<MCPhysReg>> Reports) {
SmallSet<MCPhysReg, 4> RegsToTrack;
for (auto Report : Reports)
if (Report.RequestedDetails)
RegsToTrack.insert(*Report.RequestedDetails);
return SmallVector<MCPhysReg>(RegsToTrack.begin(), RegsToTrack.end());
}
void FunctionAnalysisContext::findUnsafeUses(
SmallVector<PartialReport<MCPhysReg>> &Reports) {
auto Analysis = SrcSafetyAnalysis::create(BF, AllocatorId, {});
LLVM_DEBUG(dbgs() << "Running src register safety analysis...\n");
Analysis->run();
LLVM_DEBUG({
dbgs() << "After src register safety analysis:\n";
BF.dump();
});
bool UnreachableBBReported = false;
if (BF.hasCFG()) {
for (BinaryBasicBlock &BB : BF) {
MCInst *FirstInst = BB.getFirstNonPseudoInstr();
if (!FirstInst)
continue;
bool IsDirectlyUnreachable = BB.pred_empty() && !BB.isEntryPoint();
bool HasNoStateComputed = Analysis->getStateBefore(*FirstInst).empty();
if (!IsDirectlyUnreachable && !HasNoStateComputed)
continue;
Reports.push_back(make_generic_report(
MCInstReference(BB, *FirstInst),
"Warning: possibly imprecise CFG, the analysis quality may be "
"degraded in this function. According to BOLT, unreachable code is "
"found" ));
UnreachableBBReported = true;
break;
}
}
iterateOverInstrs(BF, [&](MCInstReference Inst) {
if (BC.MIB->isCFI(Inst))
return;
const SrcState &S = Analysis->getStateBefore(Inst);
if (S.empty()) {
LLVM_DEBUG(traceInst(BC, "Instruction has no state, skipping", Inst));
assert(UnreachableBBReported && "Should be reported at least once");
(void)UnreachableBBReported;
return;
}
if (auto Report = shouldReportReturnGadget(BC, Inst, S))
Reports.push_back(*Report);
if (PacRetGadgetsOnly)
return;
if (auto Report = shouldReportUnsafeTailCall(BC, BF, Inst, S))
Reports.push_back(*Report);
if (auto Report = shouldReportCallGadget(BC, Inst, S))
Reports.push_back(*Report);
if (auto Report = shouldReportSigningOracle(BC, Inst, S))
Reports.push_back(*Report);
});
}
void FunctionAnalysisContext::augmentUnsafeUseReports(
ArrayRef<PartialReport<MCPhysReg>> Reports) {
SmallVector<MCPhysReg> RegsToTrack = collectRegsToTrack(Reports);
auto Analysis = SrcSafetyAnalysis::create(BF, AllocatorId, RegsToTrack);
LLVM_DEBUG(dbgs() << "\nRunning detailed src register safety analysis...\n");
Analysis->run();
LLVM_DEBUG({
dbgs() << "After detailed src register safety analysis:\n";
BF.dump();
});
for (auto &Report : Reports) {
MCInstReference Location = Report.Issue->Location;
LLVM_DEBUG(traceInst(BC, "Attaching clobbering info to", Location));
assert(Report.RequestedDetails &&
"Should be removed by handleSimpleReports");
auto DetailedInfo =
std::make_shared<ClobberingInfo>(Analysis->getLastClobberingInsts(
Location, BF, *Report.RequestedDetails));
Result.Diagnostics.emplace_back(Report.Issue, DetailedInfo);
}
}
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");
Analysis->run();
LLVM_DEBUG({
dbgs() << "After dst register safety analysis:\n";
BF.dump();
});
iterateOverInstrs(BF, [&](MCInstReference Inst) {
if (BC.MIB->isCFI(Inst))
return;
const DstState &S = Analysis->getStateAfter(Inst);
if (auto Report = shouldReportAuthOracle(BC, Inst, S))
Reports.push_back(*Report);
});
}
void FunctionAnalysisContext::augmentUnsafeDefReports(
ArrayRef<PartialReport<MCPhysReg>> Reports) {
SmallVector<MCPhysReg> RegsToTrack = collectRegsToTrack(Reports);
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();
});
for (auto &Report : Reports) {
MCInstReference Location = Report.Issue->Location;
LLVM_DEBUG(traceInst(BC, "Attaching leakage info to", Location));
assert(Report.RequestedDetails &&
"Should be removed by handleSimpleReports");
auto DetailedInfo = std::make_shared<LeakageInfo>(
Analysis->getLeakingInsts(Location, BF, *Report.RequestedDetails));
Result.Diagnostics.emplace_back(Report.Issue, DetailedInfo);
}
}
void FunctionAnalysisContext::handleSimpleReports(
SmallVector<PartialReport<MCPhysReg>> &Reports) {
for (auto &Report : Reports) {
if (!Report.RequestedDetails)
Result.Diagnostics.emplace_back(Report.Issue, nullptr);
}
llvm::erase_if(Reports, [](const auto &R) { return !R.RequestedDetails; });
}
void FunctionAnalysisContext::run() {
LLVM_DEBUG({
dbgs() << "Analyzing function " << BF.getPrintName()
<< ", AllocatorId = " << AllocatorId << "\n";
BF.dump();
});
SmallVector<PartialReport<MCPhysReg>> UnsafeUses;
findUnsafeUses(UnsafeUses);
handleSimpleReports(UnsafeUses);
if (!UnsafeUses.empty())
augmentUnsafeUseReports(UnsafeUses);
SmallVector<PartialReport<MCPhysReg>> UnsafeDefs;
findUnsafeDefs(UnsafeDefs);
handleSimpleReports(UnsafeDefs);
if (!UnsafeDefs.empty())
augmentUnsafeDefReports(UnsafeDefs);
}
void Analysis::runOnFunction(BinaryFunction &BF,
MCPlusBuilder::AllocatorIdTy AllocatorId) {
FunctionAnalysisContext FA(BF, AllocatorId, PacRetGadgetsOnly);
FA.run();
const FunctionAnalysisResult &FAR = FA.getResult();
if (FAR.Diagnostics.empty())
return;
{
std::lock_guard<std::mutex> Lock(AnalysisResultsMutex);
AnalysisResults[&BF] = FAR;
}
}
static void printBB(const BinaryContext &BC, const BinaryBasicBlock &BB,
size_t StartIndex = 0, size_t EndIndex = -1) {
if (EndIndex == (size_t)-1)
EndIndex = BB.size() - 1;
const BinaryFunction *BF = BB.getFunction();
for (unsigned I = StartIndex; I <= EndIndex; ++I) {
MCInstReference Inst(BB, I);
if (BC.MIB->isCFI(Inst))
continue;
BC.printInstruction(outs(), Inst, Inst.computeAddress(), BF);
}
}
static void reportFoundGadgetInSingleBBSingleRelatedInst(
raw_ostream &OS, const BinaryContext &BC, const MCInstReference RelatedInst,
const MCInstReference Location) {
const BinaryBasicBlock *BB = Location.getBasicBlock();
assert(RelatedInst.hasCFG());
assert(Location.hasCFG());
if (BB == RelatedInst.getBasicBlock()) {
OS << " This happens in the following basic block:\n";
printBB(BC, *BB);
}
}
void Diagnostic::printBasicInfo(raw_ostream &OS, const BinaryContext &BC,
StringRef IssueKind) const {
const BinaryBasicBlock *BB = Location.getBasicBlock();
const BinaryFunction *BF = Location.getFunction();
const uint64_t Address = Location.computeAddress();
OS << "\nGS-PAUTH: " << IssueKind;
OS << " in function " << BF->getPrintName();
if (BB)
OS << ", basic block " << BB->getName();
OS << ", at address " << llvm::format("%x", Address) << "\n";
OS << " The instruction is ";
BC.printInstruction(OS, Location, Address, BF);
}
void GadgetDiagnostic::generateReport(raw_ostream &OS,
const BinaryContext &BC) const {
printBasicInfo(OS, BC, Kind.getDescription());
}
static void printRelatedInstrs(raw_ostream &OS, const MCInstReference Location,
ArrayRef<MCInstReference> RelatedInstrs) {
const BinaryFunction &BF = *Location.getFunction();
const BinaryContext &BC = BF.getBinaryContext();
SmallVector<std::pair<uint64_t, MCInstReference>> RI;
for (auto &InstRef : RelatedInstrs)
RI.push_back(std::make_pair(InstRef.computeAddress(), InstRef));
llvm::sort(RI, [](auto A, auto B) { return A.first < B.first; });
for (unsigned I = 0; I < RI.size(); ++I) {
auto [Address, InstRef] = RI[I];
OS << " " << (I + 1) << ". ";
BC.printInstruction(OS, InstRef, Address, &BF);
};
if (RelatedInstrs.size() == 1) {
const MCInstReference RelatedInst = RelatedInstrs[0];
if (RelatedInst.hasCFG())
reportFoundGadgetInSingleBBSingleRelatedInst(OS, BC, RelatedInst,
Location);
}
}
void ClobberingInfo::print(raw_ostream &OS,
const MCInstReference Location) const {
OS << " The " << ClobberingInstrs.size()
<< " instructions that write to the affected registers after any "
"authentication are:\n";
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 GenericDiagnostic::generateReport(raw_ostream &OS,
const BinaryContext &BC) const {
printBasicInfo(OS, BC, Text);
}
Error Analysis::runOnFunctions(BinaryContext &BC) {
ParallelUtilities::WorkFuncWithAllocTy WorkFun =
[&](BinaryFunction &BF, MCPlusBuilder::AllocatorIdTy AllocatorId) {
runOnFunction(BF, AllocatorId);
};
ParallelUtilities::PredicateTy SkipFunc = [&](const BinaryFunction &BF) {
return false;
};
ParallelUtilities::runOnEachFunctionWithUniqueAllocId(
BC, ParallelUtilities::SchedulingPolicy::SP_INST_LINEAR, WorkFun,
SkipFunc, "PAuthGadgetScanner");
for (BinaryFunction *BF : BC.getAllBinaryFunctions()) {
if (!AnalysisResults.count(BF))
continue;
for (const FinalReport &R : AnalysisResults[BF].Diagnostics) {
R.Issue->generateReport(outs(), BC);
if (R.Details)
R.Details->print(outs(), R.Issue->Location);
}
}
return Error::success();
}
}
}
}