Skip to content

Commit 3cb5667

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 b7db0ca commit 3cb5667

8 files changed

+318
-228
lines changed

bolt/lib/Passes/PAuthGadgetScanner.cpp

+74-38
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 << ": ";
@@ -332,6 +338,34 @@ class SrcSafetyAnalysis {
332338
return Clobbered;
333339
}
334340

341+
std::optional<MCPhysReg> getRegMadeTrustedByChecking(const MCInst &Inst,
342+
SrcState Cur) const {
343+
// This functions cannot return multiple registers. This is never the case
344+
// on AArch64.
345+
std::optional<MCPhysReg> RegCheckedByInst =
346+
BC.MIB->getAuthCheckedReg(Inst, /*MayOverwrite=*/false);
347+
if (RegCheckedByInst && Cur.SafeToDerefRegs[*RegCheckedByInst])
348+
return *RegCheckedByInst;
349+
350+
auto It = CheckerSequenceInfo.find(&Inst);
351+
if (It == CheckerSequenceInfo.end())
352+
return std::nullopt;
353+
354+
MCPhysReg RegCheckedBySequence = It->second.first;
355+
const MCInst *FirstCheckerInst = It->second.second;
356+
357+
// FirstCheckerInst should belong to the same basic block (see the
358+
// assertion in DataflowSrcSafetyAnalysis::run()), meaning it was
359+
// deterministically processed a few steps before this instruction.
360+
const SrcState &StateBeforeChecker = getStateBefore(*FirstCheckerInst);
361+
362+
// The sequence checks the register, but it should be authenticated before.
363+
if (!StateBeforeChecker.SafeToDerefRegs[RegCheckedBySequence])
364+
return std::nullopt;
365+
366+
return RegCheckedBySequence;
367+
}
368+
335369
// Returns all registers that can be treated as if they are written by an
336370
// authentication instruction.
337371
SmallVector<MCPhysReg> getRegsMadeSafeToDeref(const MCInst &Point,
@@ -354,18 +388,38 @@ class SrcSafetyAnalysis {
354388
Regs.push_back(DstAndSrc->first);
355389
}
356390

391+
// Make sure explicit checker sequence keeps register safe-to-dereference
392+
// when the register would be clobbered according to the regular rules:
393+
//
394+
// ; LR is safe to dereference here
395+
// mov x16, x30 ; start of the sequence, LR is s-t-d right before
396+
// xpaclri ; clobbers LR, LR is not safe anymore
397+
// cmp x30, x16
398+
// b.eq 1f ; end of the sequence: LR is marked as trusted
399+
// brk 0x1234
400+
// 1:
401+
// ; at this point LR would be marked as trusted,
402+
// ; but not safe-to-dereference
403+
//
404+
// or even just
405+
//
406+
// ; X1 is safe to dereference here
407+
// ldr x0, [x1, #8]!
408+
// ; X1 is trusted here, but it was clobbered due to address write-back
409+
if (auto CheckedReg = getRegMadeTrustedByChecking(Point, Cur))
410+
Regs.push_back(*CheckedReg);
411+
357412
return Regs;
358413
}
359414

360415
// Returns all registers made trusted by this instruction.
361416
SmallVector<MCPhysReg> getRegsMadeTrusted(const MCInst &Point,
362417
const SrcState &Cur) const {
418+
assert(!AuthTrapsOnFailure && "Use getRegsMadeSafeToDeref instead");
363419
SmallVector<MCPhysReg> Regs;
364420

365421
// An authenticated pointer can be checked, or
366-
std::optional<MCPhysReg> CheckedReg =
367-
BC.MIB->getAuthCheckedReg(Point, /*MayOverwrite=*/false);
368-
if (CheckedReg && Cur.SafeToDerefRegs[*CheckedReg])
422+
if (auto CheckedReg = getRegMadeTrustedByChecking(Point, Cur))
369423
Regs.push_back(*CheckedReg);
370424

371425
// ... a pointer can be authenticated by an instruction that always checks
@@ -376,19 +430,6 @@ class SrcSafetyAnalysis {
376430
if (AutReg && IsChecked)
377431
Regs.push_back(*AutReg);
378432

379-
if (CheckerSequenceInfo.contains(&Point)) {
380-
MCPhysReg CheckedReg;
381-
const MCInst *FirstCheckerInst;
382-
std::tie(CheckedReg, FirstCheckerInst) = CheckerSequenceInfo.at(&Point);
383-
384-
// FirstCheckerInst should belong to the same basic block (see the
385-
// assertion in DataflowSrcSafetyAnalysis::run()), meaning it was
386-
// deterministically processed a few steps before this instruction.
387-
const SrcState &StateBeforeChecker = getStateBefore(*FirstCheckerInst);
388-
if (StateBeforeChecker.SafeToDerefRegs[CheckedReg])
389-
Regs.push_back(CheckedReg);
390-
}
391-
392433
// ... a safe address can be materialized, or
393434
if (auto NewAddrReg = BC.MIB->getMaterializedAddressRegForPtrAuth(Point))
394435
Regs.push_back(*NewAddrReg);
@@ -432,28 +473,11 @@ class SrcSafetyAnalysis {
432473
BitVector Clobbered = getClobberedRegs(Point);
433474
SmallVector<MCPhysReg> NewSafeToDerefRegs =
434475
getRegsMadeSafeToDeref(Point, Cur);
435-
SmallVector<MCPhysReg> NewTrustedRegs = getRegsMadeTrusted(Point, Cur);
436-
437-
// Ideally, being trusted is a strictly stronger property than being
438-
// safe-to-dereference. To simplify the computation of Next state, enforce
439-
// this for NewSafeToDerefRegs and NewTrustedRegs. Additionally, this
440-
// fixes the properly for "cumulative" register states in tricky cases
441-
// like the following:
442-
//
443-
// ; LR is safe to dereference here
444-
// mov x16, x30 ; start of the sequence, LR is s-t-d right before
445-
// xpaclri ; clobbers LR, LR is not safe anymore
446-
// cmp x30, x16
447-
// b.eq 1f ; end of the sequence: LR is marked as trusted
448-
// brk 0x1234
449-
// 1:
450-
// ; at this point LR would be marked as trusted,
451-
// ; but not safe-to-dereference
452-
//
453-
for (auto TrustedReg : NewTrustedRegs) {
454-
if (!is_contained(NewSafeToDerefRegs, TrustedReg))
455-
NewSafeToDerefRegs.push_back(TrustedReg);
456-
}
476+
// If authentication instructions trap on failure, safe-to-dereference
477+
// registers are always trusted.
478+
SmallVector<MCPhysReg> NewTrustedRegs =
479+
AuthTrapsOnFailure ? NewSafeToDerefRegs
480+
: getRegsMadeTrusted(Point, Cur);
457481

458482
// Then, compute the state after this instruction is executed.
459483
SrcState Next = Cur;
@@ -490,6 +514,11 @@ class SrcSafetyAnalysis {
490514
dbgs() << ")\n";
491515
});
492516

517+
// Being trusted is a strictly stronger property than being
518+
// safe-to-dereference.
519+
assert(!Next.TrustedRegs.test(Next.SafeToDerefRegs) &&
520+
"SafeToDerefRegs should contain all TrustedRegs");
521+
493522
return Next;
494523
}
495524

@@ -1066,6 +1095,11 @@ class DataflowDstSafetyAnalysis
10661095
}
10671096

10681097
void run() override {
1098+
// As long as DstSafetyAnalysis is only computed to detect authentication
1099+
// oracles, it is a waste of time to compute it when authentication
1100+
// instructions are known to always trap on failure.
1101+
assert(!AuthTrapsOnFailure &&
1102+
"DstSafetyAnalysis is useless with faulting auth");
10691103
for (BinaryBasicBlock &BB : Func) {
10701104
if (auto CheckerInfo = BC.MIB->getAuthCheckedReg(BB)) {
10711105
LLVM_DEBUG({
@@ -1536,6 +1570,8 @@ void FunctionAnalysisContext::findUnsafeDefs(
15361570
SmallVector<PartialReport<MCPhysReg>> &Reports) {
15371571
if (PacRetGadgetsOnly)
15381572
return;
1573+
if (AuthTrapsOnFailure)
1574+
return;
15391575

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

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

+1
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

+4-2
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

+3-2
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)