Skip to content

Commit ab98447

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 a1e344f commit ab98447

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
@@ -141,6 +141,15 @@ class MCInstReference {
141141
return nullptr;
142142
}
143143

144+
/// Returns the only preceding instruction, or std::nullopt if multiple or no
145+
/// predecessors are possible.
146+
///
147+
/// If CFG information is available, basic block boundary can be crossed,
148+
/// provided there is exactly one predecessor. If CFG is not available, the
149+
/// preceding instruction in the offset order is returned, unless this is the
150+
/// first instruction of the function.
151+
std::optional<MCInstReference> getSinglePredecessor();
152+
144153
raw_ostream &print(raw_ostream &OS) const;
145154
};
146155

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"
@@ -711,6 +712,19 @@ class MCPlusBuilder {
711712
return std::nullopt;
712713
}
713714

715+
/// Tests if BranchInst corresponds to an instruction sequence which is known
716+
/// to be a safe dispatch via jump table.
717+
///
718+
/// The target can decide which instruction sequences to consider "safe" from
719+
/// the Pointer Authentication point of view, such as any jump table dispatch
720+
/// sequence without function calls inside, any sequence which is contiguous,
721+
/// or only some specific well-known sequences.
722+
virtual bool
723+
isSafeJumpTableBranchForPtrAuth(MCInstReference BranchInst) const {
724+
llvm_unreachable("not implemented");
725+
return false;
726+
}
727+
714728
virtual bool isTerminator(const MCInst &Inst) const;
715729

716730
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
@@ -54,3 +54,23 @@ raw_ostream &MCInstReference::print(raw_ostream &OS) const {
5454
OS << ">";
5555
return OS;
5656
}
57+
58+
std::optional<MCInstReference> MCInstReference::getSinglePredecessor() {
59+
if (const RefInBB *Ref = tryGetRefInBB()) {
60+
if (Ref->It != Ref->BB->begin())
61+
return MCInstReference(Ref->BB, &*std::prev(Ref->It));
62+
63+
if (Ref->BB->pred_size() != 1)
64+
return std::nullopt;
65+
66+
BinaryBasicBlock *PredBB = *Ref->BB->pred_begin();
67+
assert(!PredBB->empty() && "Empty basic blocks are not supported yet");
68+
return MCInstReference(PredBB, &*PredBB->rbegin());
69+
}
70+
71+
const RefInBF &Ref = getRefInBF();
72+
if (Ref.It == Ref.BF->instrs().begin())
73+
return std::nullopt;
74+
75+
return MCInstReference(Ref.BF, std::prev(Ref.It));
76+
}

bolt/lib/Passes/PAuthGadgetScanner.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1370,6 +1370,11 @@ shouldReportUnsafeTailCall(const BinaryContext &BC, const BinaryFunction &BF,
13701370
return std::nullopt;
13711371
}
13721372

1373+
if (BC.MIB->isSafeJumpTableBranchForPtrAuth(Inst)) {
1374+
LLVM_DEBUG({ dbgs() << " Safe jump table detected, skipping.\n"; });
1375+
return std::nullopt;
1376+
}
1377+
13731378
// Returns at most one report per instruction - this is probably OK...
13741379
for (auto Reg : RegsToCheck)
13751380
if (!S.TrustedRegs[Reg])
@@ -1400,6 +1405,11 @@ shouldReportCallGadget(const BinaryContext &BC, const MCInstReference &Inst,
14001405
if (S.SafeToDerefRegs[DestReg])
14011406
return std::nullopt;
14021407

1408+
if (BC.MIB->isSafeJumpTableBranchForPtrAuth(Inst)) {
1409+
LLVM_DEBUG({ dbgs() << " Safe jump table detected, skipping.\n"; });
1410+
return std::nullopt;
1411+
}
1412+
14031413
return make_gadget_report(CallKind, Inst, DestReg);
14041414
}
14051415

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)