Skip to content

Commit e1328fd

Browse files
authored
[BOLT] Gadget scanner: clarify MCPlusBuilder callbacks interface (llvm#136147)
Clarify the semantics of `getAuthenticatedReg` and remove a redundant `isAuthenticationOfReg` method, as combined auth+something instructions (such as `retaa` on AArch64) should be handled carefully, especially when searching for authentication oracles: usually, such instructions cannot be authentication oracles and only some of them actually write an authenticated pointer to a register (such as "ldra x0, [x1]!"). Use `std::optional<MCPhysReg>` returned type instead of plain MCPhysReg and returning `getNoRegister()` as a "not applicable" indication. Document a few existing methods, add information about preconditions.
1 parent 041d189 commit e1328fd

File tree

5 files changed

+165
-101
lines changed

5 files changed

+165
-101
lines changed

bolt/include/bolt/Core/MCPlusBuilder.h

Lines changed: 44 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -562,35 +562,56 @@ class MCPlusBuilder {
562562
return {};
563563
}
564564

565-
virtual ErrorOr<MCPhysReg> getAuthenticatedReg(const MCInst &Inst) const {
566-
llvm_unreachable("not implemented");
567-
return getNoRegister();
568-
}
569-
570-
virtual bool isAuthenticationOfReg(const MCInst &Inst,
571-
MCPhysReg AuthenticatedReg) const {
565+
/// Returns the register where an authenticated pointer is written to by Inst,
566+
/// or std::nullopt if not authenticating any register.
567+
///
568+
/// Sets IsChecked if the instruction always checks authenticated pointer,
569+
/// i.e. it either writes a successfully authenticated pointer or terminates
570+
/// the program abnormally (such as "ldra x0, [x1]!" on AArch64, which crashes
571+
/// on authentication failure even if FEAT_FPAC is not implemented).
572+
virtual std::optional<MCPhysReg>
573+
getWrittenAuthenticatedReg(const MCInst &Inst, bool &IsChecked) const {
572574
llvm_unreachable("not implemented");
573-
return false;
575+
return std::nullopt;
574576
}
575577

576-
virtual MCPhysReg getSignedReg(const MCInst &Inst) const {
578+
/// Returns the register signed by Inst, or std::nullopt if not signing any
579+
/// register.
580+
///
581+
/// The returned register is assumed to be both input and output operand,
582+
/// as it is done on AArch64.
583+
virtual std::optional<MCPhysReg> getSignedReg(const MCInst &Inst) const {
577584
llvm_unreachable("not implemented");
578-
return getNoRegister();
585+
return std::nullopt;
579586
}
580587

581-
virtual ErrorOr<MCPhysReg> getRegUsedAsRetDest(const MCInst &Inst) const {
588+
/// Returns the register used as a return address. Returns std::nullopt if
589+
/// not applicable, such as reading the return address from a system register
590+
/// or from the stack.
591+
///
592+
/// Sets IsAuthenticatedInternally if the instruction accepts a signed
593+
/// pointer as its operand and authenticates it internally.
594+
///
595+
/// Should only be called when isReturn(Inst) is true.
596+
virtual std::optional<MCPhysReg>
597+
getRegUsedAsRetDest(const MCInst &Inst,
598+
bool &IsAuthenticatedInternally) const {
582599
llvm_unreachable("not implemented");
583-
return getNoRegister();
600+
return std::nullopt;
584601
}
585602

586603
/// Returns the register used as the destination of an indirect branch or call
587604
/// instruction. Sets IsAuthenticatedInternally if the instruction accepts
588605
/// a signed pointer as its operand and authenticates it internally.
606+
///
607+
/// Should only be called if isIndirectCall(Inst) or isIndirectBranch(Inst)
608+
/// returns true.
589609
virtual MCPhysReg
590610
getRegUsedAsIndirectBranchDest(const MCInst &Inst,
591611
bool &IsAuthenticatedInternally) const {
592612
llvm_unreachable("not implemented");
593-
return getNoRegister();
613+
return 0; // Unreachable. A valid register should be returned by the
614+
// target implementation.
594615
}
595616

596617
/// Returns the register containing an address safely materialized by `Inst`
@@ -602,14 +623,14 @@ class MCPlusBuilder {
602623
/// controlled, under the Pointer Authentication threat model.
603624
///
604625
/// If the instruction does not write to any register satisfying the above
605-
/// two conditions, NoRegister is returned.
626+
/// two conditions, std::nullopt is returned.
606627
///
607628
/// The Pointer Authentication threat model assumes an attacker is able to
608629
/// modify any writable memory, but not executable code (due to W^X).
609-
virtual MCPhysReg
630+
virtual std::optional<MCPhysReg>
610631
getMaterializedAddressRegForPtrAuth(const MCInst &Inst) const {
611632
llvm_unreachable("not implemented");
612-
return getNoRegister();
633+
return std::nullopt;
613634
}
614635

615636
/// Analyzes if this instruction can safely perform address arithmetics
@@ -622,10 +643,13 @@ class MCPlusBuilder {
622643
/// controlled, provided InReg and executable code are not. Please note that
623644
/// registers other than InReg as well as the contents of memory which is
624645
/// writable by the process should be considered attacker-controlled.
646+
///
647+
/// The instruction should not write any values derived from InReg anywhere,
648+
/// except for OutReg.
625649
virtual std::optional<std::pair<MCPhysReg, MCPhysReg>>
626650
analyzeAddressArithmeticsForPtrAuth(const MCInst &Inst) const {
627651
llvm_unreachable("not implemented");
628-
return std::make_pair(getNoRegister(), getNoRegister());
652+
return std::nullopt;
629653
}
630654

631655
/// Analyzes if a pointer is checked to be authenticated successfully
@@ -670,10 +694,10 @@ class MCPlusBuilder {
670694
///
671695
/// Use this function for simple, single-instruction patterns instead of
672696
/// its getAuthCheckedReg(BB) counterpart.
673-
virtual MCPhysReg getAuthCheckedReg(const MCInst &Inst,
674-
bool MayOverwrite) const {
697+
virtual std::optional<MCPhysReg> getAuthCheckedReg(const MCInst &Inst,
698+
bool MayOverwrite) const {
675699
llvm_unreachable("not implemented");
676-
return getNoRegister();
700+
return std::nullopt;
677701
}
678702

679703
virtual bool isTerminator(const MCInst &Inst) const;

bolt/lib/Passes/PAuthGadgetScanner.cpp

Lines changed: 38 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -66,14 +66,12 @@ namespace PAuthGadgetScanner {
6666
}
6767

6868
[[maybe_unused]] static void traceReg(const BinaryContext &BC, StringRef Label,
69-
ErrorOr<MCPhysReg> Reg) {
69+
MCPhysReg Reg) {
7070
dbgs() << " " << Label << ": ";
71-
if (Reg.getError())
72-
dbgs() << "(error)";
73-
else if (*Reg == BC.MIB->getNoRegister())
71+
if (Reg == BC.MIB->getNoRegister())
7472
dbgs() << "(none)";
7573
else
76-
dbgs() << BC.MRI->getName(*Reg);
74+
dbgs() << BC.MRI->getName(Reg);
7775
dbgs() << "\n";
7876
}
7977

@@ -365,17 +363,15 @@ class SrcSafetyAnalysis {
365363
SmallVector<MCPhysReg> getRegsMadeSafeToDeref(const MCInst &Point,
366364
const SrcState &Cur) const {
367365
SmallVector<MCPhysReg> Regs;
368-
const MCPhysReg NoReg = BC.MIB->getNoRegister();
369366

370367
// A signed pointer can be authenticated, or
371-
ErrorOr<MCPhysReg> AutReg = BC.MIB->getAuthenticatedReg(Point);
372-
if (AutReg && *AutReg != NoReg)
368+
bool Dummy = false;
369+
if (auto AutReg = BC.MIB->getWrittenAuthenticatedReg(Point, Dummy))
373370
Regs.push_back(*AutReg);
374371

375372
// ... a safe address can be materialized, or
376-
MCPhysReg NewAddrReg = BC.MIB->getMaterializedAddressRegForPtrAuth(Point);
377-
if (NewAddrReg != NoReg)
378-
Regs.push_back(NewAddrReg);
373+
if (auto NewAddrReg = BC.MIB->getMaterializedAddressRegForPtrAuth(Point))
374+
Regs.push_back(*NewAddrReg);
379375

380376
// ... an address can be updated in a safe manner, producing the result
381377
// which is as trusted as the input address.
@@ -391,13 +387,20 @@ class SrcSafetyAnalysis {
391387
SmallVector<MCPhysReg> getRegsMadeTrusted(const MCInst &Point,
392388
const SrcState &Cur) const {
393389
SmallVector<MCPhysReg> Regs;
394-
const MCPhysReg NoReg = BC.MIB->getNoRegister();
395390

396391
// An authenticated pointer can be checked, or
397-
MCPhysReg CheckedReg =
392+
std::optional<MCPhysReg> CheckedReg =
398393
BC.MIB->getAuthCheckedReg(Point, /*MayOverwrite=*/false);
399-
if (CheckedReg != NoReg && Cur.SafeToDerefRegs[CheckedReg])
400-
Regs.push_back(CheckedReg);
394+
if (CheckedReg && Cur.SafeToDerefRegs[*CheckedReg])
395+
Regs.push_back(*CheckedReg);
396+
397+
// ... a pointer can be authenticated by an instruction that always checks
398+
// the pointer, or
399+
bool IsChecked = false;
400+
std::optional<MCPhysReg> AutReg =
401+
BC.MIB->getWrittenAuthenticatedReg(Point, IsChecked);
402+
if (AutReg && IsChecked)
403+
Regs.push_back(*AutReg);
401404

402405
if (CheckerSequenceInfo.contains(&Point)) {
403406
MCPhysReg CheckedReg;
@@ -413,9 +416,8 @@ class SrcSafetyAnalysis {
413416
}
414417

415418
// ... a safe address can be materialized, or
416-
MCPhysReg NewAddrReg = BC.MIB->getMaterializedAddressRegForPtrAuth(Point);
417-
if (NewAddrReg != NoReg)
418-
Regs.push_back(NewAddrReg);
419+
if (auto NewAddrReg = BC.MIB->getMaterializedAddressRegForPtrAuth(Point))
420+
Regs.push_back(*NewAddrReg);
419421

420422
// ... an address can be updated in a safe manner, producing the result
421423
// which is as trusted as the input address.
@@ -738,25 +740,27 @@ shouldReportReturnGadget(const BinaryContext &BC, const MCInstReference &Inst,
738740
if (!BC.MIB->isReturn(Inst))
739741
return std::nullopt;
740742

741-
ErrorOr<MCPhysReg> MaybeRetReg = BC.MIB->getRegUsedAsRetDest(Inst);
742-
if (MaybeRetReg.getError()) {
743+
bool IsAuthenticated = false;
744+
std::optional<MCPhysReg> RetReg =
745+
BC.MIB->getRegUsedAsRetDest(Inst, IsAuthenticated);
746+
if (!RetReg) {
743747
return make_generic_report(
744748
Inst, "Warning: pac-ret analysis could not analyze this return "
745749
"instruction");
746750
}
747-
MCPhysReg RetReg = *MaybeRetReg;
751+
if (IsAuthenticated)
752+
return std::nullopt;
753+
748754
LLVM_DEBUG({
749755
traceInst(BC, "Found RET inst", Inst);
750-
traceReg(BC, "RetReg", RetReg);
751-
traceReg(BC, "Authenticated reg", BC.MIB->getAuthenticatedReg(Inst));
756+
traceReg(BC, "RetReg", *RetReg);
757+
traceRegMask(BC, "SafeToDerefRegs", S.SafeToDerefRegs);
752758
});
753-
if (BC.MIB->isAuthenticationOfReg(Inst, RetReg))
754-
return std::nullopt;
755-
LLVM_DEBUG({ traceRegMask(BC, "SafeToDerefRegs", S.SafeToDerefRegs); });
756-
if (S.SafeToDerefRegs[RetReg])
759+
760+
if (S.SafeToDerefRegs[*RetReg])
757761
return std::nullopt;
758762

759-
return make_gadget_report(RetKind, Inst, RetReg);
763+
return make_gadget_report(RetKind, Inst, *RetReg);
760764
}
761765

762766
static std::optional<PartialReport<MCPhysReg>>
@@ -772,7 +776,7 @@ shouldReportCallGadget(const BinaryContext &BC, const MCInstReference &Inst,
772776
if (IsAuthenticated)
773777
return std::nullopt;
774778

775-
assert(DestReg != BC.MIB->getNoRegister());
779+
assert(DestReg != BC.MIB->getNoRegister() && "Valid register expected");
776780
LLVM_DEBUG({
777781
traceInst(BC, "Found call inst", Inst);
778782
traceReg(BC, "Call destination reg", DestReg);
@@ -789,19 +793,19 @@ shouldReportSigningOracle(const BinaryContext &BC, const MCInstReference &Inst,
789793
const SrcState &S) {
790794
static const GadgetKind SigningOracleKind("signing oracle found");
791795

792-
MCPhysReg SignedReg = BC.MIB->getSignedReg(Inst);
793-
if (SignedReg == BC.MIB->getNoRegister())
796+
std::optional<MCPhysReg> SignedReg = BC.MIB->getSignedReg(Inst);
797+
if (!SignedReg)
794798
return std::nullopt;
795799

796800
LLVM_DEBUG({
797801
traceInst(BC, "Found sign inst", Inst);
798-
traceReg(BC, "Signed reg", SignedReg);
802+
traceReg(BC, "Signed reg", *SignedReg);
799803
traceRegMask(BC, "TrustedRegs", S.TrustedRegs);
800804
});
801-
if (S.TrustedRegs[SignedReg])
805+
if (S.TrustedRegs[*SignedReg])
802806
return std::nullopt;
803807

804-
return make_gadget_report(SigningOracleKind, Inst, SignedReg);
808+
return make_gadget_report(SigningOracleKind, Inst, *SignedReg);
805809
}
806810

807811
template <typename T> static void iterateOverInstrs(BinaryFunction &BF, T Fn) {

0 commit comments

Comments
 (0)