Skip to content

Commit d7167e8

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 4e08d36 commit d7167e8

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"
@@ -700,6 +701,19 @@ class MCPlusBuilder {
700701
return std::nullopt;
701702
}
702703

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

705719
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
@@ -1351,6 +1351,11 @@ shouldReportUnsafeTailCall(const BinaryContext &BC, const BinaryFunction &BF,
13511351
return std::nullopt;
13521352
}
13531353

1354+
if (BC.MIB->isSafeJumpTableBranchForPtrAuth(Inst)) {
1355+
LLVM_DEBUG({ dbgs() << " Safe jump table detected, skipping.\n"; });
1356+
return std::nullopt;
1357+
}
1358+
13541359
// Returns at most one report per instruction - this is probably OK...
13551360
for (auto Reg : RegsToCheck)
13561361
if (!S.TrustedRegs[Reg])
@@ -1381,6 +1386,11 @@ shouldReportCallGadget(const BinaryContext &BC, const MCInstReference &Inst,
13811386
if (S.SafeToDerefRegs[DestReg])
13821387
return std::nullopt;
13831388

1389+
if (BC.MIB->isSafeJumpTableBranchForPtrAuth(Inst)) {
1390+
LLVM_DEBUG({ dbgs() << " Safe jump table detected, skipping.\n"; });
1391+
return std::nullopt;
1392+
}
1393+
13841394
return make_gadget_report(CallKind, Inst, DestReg);
13851395
}
13861396

bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,79 @@ class AArch64MCPlusBuilder : public MCPlusBuilder {
536536
return std::nullopt;
537537
}
538538

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

0 commit comments

Comments
 (0)