Skip to content

Commit 5c1edeb

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 5a3b71a commit 5c1edeb

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

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

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
@@ -533,6 +533,79 @@ class AArch64MCPlusBuilder : public MCPlusBuilder {
533533
return std::nullopt;
534534
}
535535

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

0 commit comments

Comments
 (0)