Skip to content

Commit 7212b2d

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 87a88e4 commit 7212b2d

File tree

8 files changed

+318
-227
lines changed

8 files changed

+318
-227
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 << ": ";
@@ -363,6 +369,34 @@ class SrcSafetyAnalysis {
363369
return Clobbered;
364370
}
365371

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

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

391446
// Returns all registers made trusted by this instruction.
392447
SmallVector<MCPhysReg> getRegsMadeTrusted(const MCInst &Point,
393448
const SrcState &Cur) const {
449+
assert(!AuthTrapsOnFailure && "Use getRegsMadeSafeToDeref instead");
394450
SmallVector<MCPhysReg> Regs;
395451

396452
// An authenticated pointer can be checked, or
397-
std::optional<MCPhysReg> CheckedReg =
398-
BC.MIB->getAuthCheckedReg(Point, /*MayOverwrite=*/false);
399-
if (CheckedReg && Cur.SafeToDerefRegs[*CheckedReg])
453+
if (auto CheckedReg = getRegMadeTrustedByChecking(Point, Cur))
400454
Regs.push_back(*CheckedReg);
401455

402456
// ... a pointer can be authenticated by an instruction that always checks
@@ -407,19 +461,6 @@ class SrcSafetyAnalysis {
407461
if (AutReg && IsChecked)
408462
Regs.push_back(*AutReg);
409463

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

488512
// Then, compute the state after this instruction is executed.
489513
SrcState Next = Cur;
@@ -520,6 +544,11 @@ class SrcSafetyAnalysis {
520544
dbgs() << ")\n";
521545
});
522546

547+
// Being trusted is a strictly stronger property than being
548+
// safe-to-dereference.
549+
assert(!Next.TrustedRegs.test(Next.SafeToDerefRegs) &&
550+
"SafeToDerefRegs should contain all TrustedRegs");
551+
523552
return Next;
524553
}
525554

@@ -1106,6 +1135,11 @@ class DataflowDstSafetyAnalysis
11061135
}
11071136

11081137
void run() override {
1138+
// As long as DstSafetyAnalysis is only computed to detect authentication
1139+
// oracles, it is a waste of time to compute it when authentication
1140+
// instructions are known to always trap on failure.
1141+
assert(!AuthTrapsOnFailure &&
1142+
"DstSafetyAnalysis is useless with faulting auth");
11091143
for (BinaryBasicBlock &BB : Func) {
11101144
if (auto CheckerInfo = BC.MIB->getAuthCheckedReg(BB)) {
11111145
LLVM_DEBUG({
@@ -1550,6 +1584,8 @@ void FunctionAnalysisContext::findUnsafeDefs(
15501584
SmallVector<PartialReport<MCPhysReg>> &Reports) {
15511585
if (PacRetGadgetsOnly)
15521586
return;
1587+
if (AuthTrapsOnFailure)
1588+
return;
15531589

15541590
auto Analysis = DstSafetyAnalysis::create(BF, AllocatorId, {});
15551591
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)