Skip to content

Commit 3dd903c

Browse files
committed
[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.
1 parent eae2c10 commit 3dd903c

File tree

8 files changed

+318
-228
lines changed

8 files changed

+318
-228
lines changed

bolt/lib/Passes/PAuthGadgetScanner.cpp

Lines changed: 74 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "bolt/Passes/PAuthGadgetScanner.h"
1515
#include "bolt/Core/ParallelUtilities.h"
1616
#include "bolt/Passes/DataflowAnalysis.h"
17+
#include "bolt/Utils/CommandLineOpts.h"
1718
#include "llvm/ADT/STLExtras.h"
1819
#include "llvm/ADT/SmallSet.h"
1920
#include "llvm/MC/MCInst.h"
@@ -26,6 +27,11 @@ namespace llvm {
2627
namespace bolt {
2728
namespace PAuthGadgetScanner {
2829

30+
static cl::opt<bool> AuthTrapsOnFailure(
31+
"auth-traps-on-failure",
32+
cl::desc("Assume authentication instructions always trap on failure"),
33+
cl::cat(opts::BinaryAnalysisCategory));
34+
2935
[[maybe_unused]] static void traceInst(const BinaryContext &BC, StringRef Label,
3036
const MCInst &MI) {
3137
dbgs() << " " << Label << ": ";
@@ -365,6 +371,34 @@ class SrcSafetyAnalysis {
365371
return Clobbered;
366372
}
367373

374+
std::optional<MCPhysReg> getRegMadeTrustedByChecking(const MCInst &Inst,
375+
SrcState Cur) const {
376+
// This functions cannot return multiple registers. This is never the case
377+
// on AArch64.
378+
std::optional<MCPhysReg> RegCheckedByInst =
379+
BC.MIB->getAuthCheckedReg(Inst, /*MayOverwrite=*/false);
380+
if (RegCheckedByInst && Cur.SafeToDerefRegs[*RegCheckedByInst])
381+
return *RegCheckedByInst;
382+
383+
auto It = CheckerSequenceInfo.find(&Inst);
384+
if (It == CheckerSequenceInfo.end())
385+
return std::nullopt;
386+
387+
MCPhysReg RegCheckedBySequence = It->second.first;
388+
const MCInst *FirstCheckerInst = It->second.second;
389+
390+
// FirstCheckerInst should belong to the same basic block (see the
391+
// assertion in DataflowSrcSafetyAnalysis::run()), meaning it was
392+
// deterministically processed a few steps before this instruction.
393+
const SrcState &StateBeforeChecker = getStateBefore(*FirstCheckerInst);
394+
395+
// The sequence checks the register, but it should be authenticated before.
396+
if (!StateBeforeChecker.SafeToDerefRegs[RegCheckedBySequence])
397+
return std::nullopt;
398+
399+
return RegCheckedBySequence;
400+
}
401+
368402
// Returns all registers that can be treated as if they are written by an
369403
// authentication instruction.
370404
SmallVector<MCPhysReg> getRegsMadeSafeToDeref(const MCInst &Point,
@@ -387,18 +421,38 @@ class SrcSafetyAnalysis {
387421
Regs.push_back(DstAndSrc->first);
388422
}
389423

424+
// Make sure explicit checker sequence keeps register safe-to-dereference
425+
// when the register would be clobbered according to the regular rules:
426+
//
427+
// ; LR is safe to dereference here
428+
// mov x16, x30 ; start of the sequence, LR is s-t-d right before
429+
// xpaclri ; clobbers LR, LR is not safe anymore
430+
// cmp x30, x16
431+
// b.eq 1f ; end of the sequence: LR is marked as trusted
432+
// brk 0x1234
433+
// 1:
434+
// ; at this point LR would be marked as trusted,
435+
// ; but not safe-to-dereference
436+
//
437+
// or even just
438+
//
439+
// ; X1 is safe to dereference here
440+
// ldr x0, [x1, #8]!
441+
// ; X1 is trusted here, but it was clobbered due to address write-back
442+
if (auto CheckedReg = getRegMadeTrustedByChecking(Point, Cur))
443+
Regs.push_back(*CheckedReg);
444+
390445
return Regs;
391446
}
392447

393448
// Returns all registers made trusted by this instruction.
394449
SmallVector<MCPhysReg> getRegsMadeTrusted(const MCInst &Point,
395450
const SrcState &Cur) const {
451+
assert(!AuthTrapsOnFailure && "Use getRegsMadeSafeToDeref instead");
396452
SmallVector<MCPhysReg> Regs;
397453

398454
// An authenticated pointer can be checked, or
399-
std::optional<MCPhysReg> CheckedReg =
400-
BC.MIB->getAuthCheckedReg(Point, /*MayOverwrite=*/false);
401-
if (CheckedReg && Cur.SafeToDerefRegs[*CheckedReg])
455+
if (auto CheckedReg = getRegMadeTrustedByChecking(Point, Cur))
402456
Regs.push_back(*CheckedReg);
403457

404458
// ... a pointer can be authenticated by an instruction that always checks
@@ -409,19 +463,6 @@ class SrcSafetyAnalysis {
409463
if (AutReg && IsChecked)
410464
Regs.push_back(*AutReg);
411465

412-
if (CheckerSequenceInfo.contains(&Point)) {
413-
MCPhysReg CheckedReg;
414-
const MCInst *FirstCheckerInst;
415-
std::tie(CheckedReg, FirstCheckerInst) = CheckerSequenceInfo.at(&Point);
416-
417-
// FirstCheckerInst should belong to the same basic block (see the
418-
// assertion in DataflowSrcSafetyAnalysis::run()), meaning it was
419-
// deterministically processed a few steps before this instruction.
420-
const SrcState &StateBeforeChecker = getStateBefore(*FirstCheckerInst);
421-
if (StateBeforeChecker.SafeToDerefRegs[CheckedReg])
422-
Regs.push_back(CheckedReg);
423-
}
424-
425466
// ... a safe address can be materialized, or
426467
if (auto NewAddrReg = BC.MIB->getMaterializedAddressRegForPtrAuth(Point))
427468
Regs.push_back(*NewAddrReg);
@@ -465,28 +506,11 @@ class SrcSafetyAnalysis {
465506
BitVector Clobbered = getClobberedRegs(Point);
466507
SmallVector<MCPhysReg> NewSafeToDerefRegs =
467508
getRegsMadeSafeToDeref(Point, Cur);
468-
SmallVector<MCPhysReg> NewTrustedRegs = getRegsMadeTrusted(Point, Cur);
469-
470-
// Ideally, being trusted is a strictly stronger property than being
471-
// safe-to-dereference. To simplify the computation of Next state, enforce
472-
// this for NewSafeToDerefRegs and NewTrustedRegs. Additionally, this
473-
// fixes the properly for "cumulative" register states in tricky cases
474-
// like the following:
475-
//
476-
// ; LR is safe to dereference here
477-
// mov x16, x30 ; start of the sequence, LR is s-t-d right before
478-
// xpaclri ; clobbers LR, LR is not safe anymore
479-
// cmp x30, x16
480-
// b.eq 1f ; end of the sequence: LR is marked as trusted
481-
// brk 0x1234
482-
// 1:
483-
// ; at this point LR would be marked as trusted,
484-
// ; but not safe-to-dereference
485-
//
486-
for (auto TrustedReg : NewTrustedRegs) {
487-
if (!is_contained(NewSafeToDerefRegs, TrustedReg))
488-
NewSafeToDerefRegs.push_back(TrustedReg);
489-
}
509+
// If authentication instructions trap on failure, safe-to-dereference
510+
// registers are always trusted.
511+
SmallVector<MCPhysReg> NewTrustedRegs =
512+
AuthTrapsOnFailure ? NewSafeToDerefRegs
513+
: getRegsMadeTrusted(Point, Cur);
490514

491515
// Then, compute the state after this instruction is executed.
492516
SrcState Next = Cur;
@@ -523,6 +547,11 @@ class SrcSafetyAnalysis {
523547
dbgs() << ")\n";
524548
});
525549

550+
// Being trusted is a strictly stronger property than being
551+
// safe-to-dereference.
552+
assert(!Next.TrustedRegs.test(Next.SafeToDerefRegs) &&
553+
"SafeToDerefRegs should contain all TrustedRegs");
554+
526555
return Next;
527556
}
528557

@@ -1084,6 +1113,11 @@ class DataflowDstSafetyAnalysis
10841113
}
10851114

10861115
void run() override {
1116+
// As long as DstSafetyAnalysis is only computed to detect authentication
1117+
// oracles, it is a waste of time to compute it when authentication
1118+
// instructions are known to always trap on failure.
1119+
assert(!AuthTrapsOnFailure &&
1120+
"DstSafetyAnalysis is useless with faulting auth");
10871121
for (BinaryBasicBlock &BB : Func) {
10881122
if (auto CheckerInfo = BC.MIB->getAuthCheckedReg(BB)) {
10891123
LLVM_DEBUG({
@@ -1543,6 +1577,8 @@ void FunctionAnalysisContext::findUnsafeDefs(
15431577
SmallVector<PartialReport<MCPhysReg>> &Reports) {
15441578
if (PacRetGadgetsOnly)
15451579
return;
1580+
if (AuthTrapsOnFailure)
1581+
return;
15461582

15471583
auto Analysis = DstSafetyAnalysis::create(BF, AllocatorId, {});
15481584
LLVM_DEBUG({ dbgs() << "Running dst register safety analysis...\n"; });

bolt/test/binary-analysis/AArch64/cmdline-args.test

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ HELP-NEXT: OPTIONS:
3232
HELP-EMPTY:
3333
HELP-NEXT: BinaryAnalysis options:
3434
HELP-EMPTY:
35+
HELP-NEXT: --auth-traps-on-failure - Assume authentication instructions always trap on failure
3536
HELP-NEXT: --scanners=<value> - which gadget scanners to run
3637
HELP-NEXT: =pacret - pac-ret: return address protection (subset of "pauth")
3738
HELP-NEXT: =pauth - All Pointer Authentication scanners

bolt/test/binary-analysis/AArch64/gs-pauth-authentication-oracles.s

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
// RUN: %clang %cflags -march=armv8.3-a %s -o %t.exe
2-
// RUN: llvm-bolt-binary-analysis --scanners=pacret %t.exe 2>&1 | FileCheck -check-prefix=PACRET %s
3-
// RUN: llvm-bolt-binary-analysis --scanners=pauth %t.exe 2>&1 | FileCheck %s
2+
// RUN: llvm-bolt-binary-analysis --scanners=pacret %t.exe 2>&1 | FileCheck -check-prefix=PACRET %s
3+
// RUN: llvm-bolt-binary-analysis --scanners=pauth --auth-traps-on-failure %t.exe 2>&1 | FileCheck -check-prefix=FPAC %s
4+
// RUN: llvm-bolt-binary-analysis --scanners=pauth %t.exe 2>&1 | FileCheck %s
45

56
// The detection of compiler-generated explicit pointer checks is tested in
67
// gs-pauth-address-checks.s, for that reason only test here "dummy-load" and
78
// "high-bits-notbi" checkers, as the shortest examples of checkers that are
89
// detected per-instruction and per-BB.
910

1011
// PACRET-NOT: authentication oracle found in function
12+
// FPAC-NOT: authentication oracle found in function
1113

1214
.text
1315

bolt/test/binary-analysis/AArch64/gs-pauth-calls.s

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// RUN: %clang %cflags -march=armv8.3-a %s -o %t.exe
2-
// RUN: llvm-bolt-binary-analysis --scanners=pacret %t.exe 2>&1 | FileCheck -check-prefix=PACRET %s
3-
// RUN: llvm-bolt-binary-analysis --scanners=pauth %t.exe 2>&1 | FileCheck %s
2+
// RUN: llvm-bolt-binary-analysis --scanners=pacret %t.exe 2>&1 | FileCheck -check-prefix=PACRET %s
3+
// RUN: llvm-bolt-binary-analysis --scanners=pauth --auth-traps-on-failure %t.exe 2>&1 | FileCheck %s
4+
// RUN: llvm-bolt-binary-analysis --scanners=pauth %t.exe 2>&1 | FileCheck %s
45

56
// PACRET-NOT: non-protected call found in function
67

0 commit comments

Comments
 (0)