Skip to content

Commit be7f0d8

Browse files
committed
[BOLT] Gadget scanner: prevent false positives due to jump tables
As part of PAuth hardening, AArch64 LLVM backend can use a special BR_JumpTable pseudo (enabled by -faarch64-jump-table-hardening Clang option) which is expanded in the AsmPrinter into a contiguous sequence without unsafe instructions in the middle. This commit adds another target-specific callback to MCPlusBuilder to make it possible to inhibit false positives for known-safe jump table dispatch sequences. Without special handling, the branch instruction is likely to be reported as a non-protected call (as its destination is not produced by an auth instruction, PC-relative address materialization, etc.) and possibly as a tail call being performed with unsafe link register (as the detection whether the branch instruction is a tail call is an heuristic). For now, only the specific instruction sequence used by the AArch64 LLVM backend is matched.
1 parent f19a1b7 commit be7f0d8

File tree

6 files changed

+829
-0
lines changed

6 files changed

+829
-0
lines changed

bolt/include/bolt/Core/MCInstUtils.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,15 @@ class MCInstReference {
154154
return nullptr;
155155
}
156156

157+
/// Returns the only preceding instruction, or std::nullopt if multiple or no
158+
/// predecessors are possible.
159+
///
160+
/// If CFG information is available, basic block boundary can be crossed,
161+
/// provided there is exactly one predecessor. If CFG is not available, the
162+
/// preceding instruction in the offset order is returned, unless this is the
163+
/// first instruction of the function.
164+
std::optional<MCInstReference> getSinglePredecessor();
165+
157166
raw_ostream &print(raw_ostream &OS) const;
158167
};
159168

bolt/include/bolt/Core/MCPlusBuilder.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#ifndef BOLT_CORE_MCPLUSBUILDER_H
1515
#define BOLT_CORE_MCPLUSBUILDER_H
1616

17+
#include "bolt/Core/MCInstUtils.h"
1718
#include "bolt/Core/MCPlus.h"
1819
#include "bolt/Core/Relocation.h"
1920
#include "llvm/ADT/ArrayRef.h"
@@ -699,6 +700,19 @@ class MCPlusBuilder {
699700
return std::nullopt;
700701
}
701702

703+
/// Tests if BranchInst corresponds to an instruction sequence which is known
704+
/// to be a safe dispatch via jump table.
705+
///
706+
/// The target can decide which instruction sequences to consider "safe" from
707+
/// the Pointer Authentication point of view, such as any jump table dispatch
708+
/// sequence without function calls inside, any sequence which is contiguous,
709+
/// or only some specific well-known sequences.
710+
virtual bool
711+
isSafeJumpTableBranchForPtrAuth(MCInstReference BranchInst) const {
712+
llvm_unreachable("not implemented");
713+
return false;
714+
}
715+
702716
virtual bool isTerminator(const MCInst &Inst) const;
703717

704718
virtual bool isNoop(const MCInst &Inst) const {

bolt/lib/Core/MCInstUtils.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,23 @@ raw_ostream &MCInstReference::print(raw_ostream &OS) const {
5555
OS << ">";
5656
return OS;
5757
}
58+
59+
std::optional<MCInstReference> MCInstReference::getSinglePredecessor() {
60+
if (const RefInBB *Ref = tryGetRefInBB()) {
61+
if (Ref->It != Ref->BB->begin())
62+
return MCInstReference(Ref->BB, &*std::prev(Ref->It));
63+
64+
if (Ref->BB->pred_size() != 1)
65+
return std::nullopt;
66+
67+
BinaryBasicBlock *PredBB = *Ref->BB->pred_begin();
68+
assert(!PredBB->empty() && "Empty basic blocks are not supported yet");
69+
return MCInstReference(PredBB, &*PredBB->rbegin());
70+
}
71+
72+
const RefInBF &Ref = getRefInBF();
73+
if (Ref.It == Ref.BF->instrs().begin())
74+
return std::nullopt;
75+
76+
return MCInstReference(Ref.BF, std::prev(Ref.It));
77+
}

bolt/lib/Passes/PAuthGadgetScanner.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1328,6 +1328,11 @@ shouldReportUnsafeTailCall(const BinaryContext &BC, const BinaryFunction &BF,
13281328
return std::nullopt;
13291329
}
13301330

1331+
if (BC.MIB->isSafeJumpTableBranchForPtrAuth(Inst)) {
1332+
LLVM_DEBUG({ dbgs() << " Safe jump table detected, skipping.\n"; });
1333+
return std::nullopt;
1334+
}
1335+
13311336
// Returns at most one report per instruction - this is probably OK...
13321337
for (auto Reg : RegsToCheck)
13331338
if (!S.TrustedRegs[Reg])
@@ -1358,6 +1363,11 @@ shouldReportCallGadget(const BinaryContext &BC, const MCInstReference &Inst,
13581363
if (S.SafeToDerefRegs[DestReg])
13591364
return std::nullopt;
13601365

1366+
if (BC.MIB->isSafeJumpTableBranchForPtrAuth(Inst)) {
1367+
LLVM_DEBUG({ dbgs() << " Safe jump table detected, skipping.\n"; });
1368+
return std::nullopt;
1369+
}
1370+
13611371
return make_gadget_report(CallKind, Inst, DestReg);
13621372
}
13631373

bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -532,6 +532,79 @@ class AArch64MCPlusBuilder : public MCPlusBuilder {
532532
return std::nullopt;
533533
}
534534

535+
bool
536+
isSafeJumpTableBranchForPtrAuth(MCInstReference BranchInst) const override {
537+
MCInstReference CurRef = BranchInst;
538+
auto StepBack = [&]() {
539+
do {
540+
auto PredInst = CurRef.getSinglePredecessor();
541+
if (!PredInst)
542+
return false;
543+
CurRef = *PredInst;
544+
} while (isCFI(CurRef));
545+
546+
return true;
547+
};
548+
549+
// Match this contiguous sequence:
550+
// cmp Xm, #count
551+
// csel Xm, Xm, xzr, ls
552+
// adrp Xn, .LJTIxyz
553+
// add Xn, Xn, :lo12:.LJTIxyz
554+
// ldrsw Xm, [Xn, Xm, lsl #2]
555+
// .Ltmp:
556+
// adr Xn, .Ltmp
557+
// add Xm, Xn, Xm
558+
// br Xm
559+
560+
// FIXME: Check label operands of ADR/ADRP+ADD and #count operand of CMP.
561+
562+
using namespace MCInstMatcher;
563+
Reg Xm, Xn;
564+
565+
if (!matchInst(CurRef, AArch64::BR, Xm) || !StepBack())
566+
return false;
567+
568+
if (!matchInst(CurRef, AArch64::ADDXrs, Xm, Xn, Xm, Imm(0)) || !StepBack())
569+
return false;
570+
571+
if (!matchInst(CurRef, AArch64::ADR, Xn /*, .Ltmp*/) || !StepBack())
572+
return false;
573+
574+
if (!matchInst(CurRef, AArch64::LDRSWroX, Xm, Xn, Xm, Imm(0), Imm(1)) ||
575+
!StepBack())
576+
return false;
577+
578+
if (matchInst(CurRef, AArch64::ADR, Xn /*, .LJTIxyz*/)) {
579+
if (!StepBack())
580+
return false;
581+
if (!matchInst(CurRef, AArch64::HINT, Imm(0)) || !StepBack())
582+
return false;
583+
} else if (matchInst(CurRef, AArch64::ADDXri, Xn,
584+
Xn /*, :lo12:.LJTIxyz*/)) {
585+
if (!StepBack())
586+
return false;
587+
if (!matchInst(CurRef, AArch64::ADRP, Xn /*, .LJTIxyz*/) || !StepBack())
588+
return false;
589+
} else {
590+
return false;
591+
}
592+
593+
if (!matchInst(CurRef, AArch64::CSELXr, Xm, Xm, Reg(AArch64::XZR),
594+
Imm(AArch64CC::LS)) ||
595+
!StepBack())
596+
return false;
597+
598+
if (!matchInst(CurRef, AArch64::SUBSXri, Reg(AArch64::XZR),
599+
Xm /*, #count*/))
600+
return false;
601+
602+
// Some platforms treat X16 and X17 as more protected registers, others
603+
// do not make such distinction. So far, accept any registers as Xm and Xn.
604+
605+
return true;
606+
}
607+
535608
bool isADRP(const MCInst &Inst) const override {
536609
return Inst.getOpcode() == AArch64::ADRP;
537610
}

0 commit comments

Comments
 (0)