Skip to content

Commit e86dd8f

Browse files
committed
[BOLT] Gadget scanner: clarify MCPlusBuilder callbacks interface
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 bbec9f8 commit e86dd8f

File tree

5 files changed

+130
-94
lines changed

5 files changed

+130
-94
lines changed

bolt/include/bolt/Core/MCPlusBuilder.h

Lines changed: 42 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -562,30 +562,50 @@ 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 returns 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 {
@@ -602,14 +622,14 @@ class MCPlusBuilder {
602622
/// controlled, under the Pointer Authentication threat model.
603623
///
604624
/// If the instruction does not write to any register satisfying the above
605-
/// two conditions, NoRegister is returned.
625+
/// two conditions, std::nullopt is returned.
606626
///
607627
/// The Pointer Authentication threat model assumes an attacker is able to
608628
/// modify any writable memory, but not executable code (due to W^X).
609-
virtual MCPhysReg
629+
virtual std::optional<MCPhysReg>
610630
getMaterializedAddressRegForPtrAuth(const MCInst &Inst) const {
611631
llvm_unreachable("not implemented");
612-
return getNoRegister();
632+
return std::nullopt;
613633
}
614634

615635
/// Analyzes if this instruction can safely perform address arithmetics
@@ -622,10 +642,13 @@ class MCPlusBuilder {
622642
/// controlled, provided InReg and executable code are not. Please note that
623643
/// registers other than InReg as well as the contents of memory which is
624644
/// writable by the process should be considered attacker-controlled.
645+
///
646+
/// The instruction should not write any values derived from InReg anywhere,
647+
/// except for OutReg.
625648
virtual std::optional<std::pair<MCPhysReg, MCPhysReg>>
626649
analyzeAddressArithmeticsForPtrAuth(const MCInst &Inst) const {
627650
llvm_unreachable("not implemented");
628-
return std::make_pair(getNoRegister(), getNoRegister());
651+
return std::nullopt;
629652
}
630653

631654
/// Analyzes if a pointer is checked to be authenticated successfully
@@ -670,10 +693,10 @@ class MCPlusBuilder {
670693
///
671694
/// Use this function for simple, single-instruction patterns instead of
672695
/// its getAuthCheckedReg(BB) counterpart.
673-
virtual MCPhysReg getAuthCheckedReg(const MCInst &Inst,
674-
bool MayOverwrite) const {
696+
virtual std::optional<MCPhysReg> getAuthCheckedReg(const MCInst &Inst,
697+
bool MayOverwrite) const {
675698
llvm_unreachable("not implemented");
676-
return getNoRegister();
699+
return std::nullopt;
677700
}
678701

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

bolt/lib/Passes/PAuthGadgetScanner.cpp

Lines changed: 36 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -367,17 +367,15 @@ class SrcSafetyAnalysis {
367367
SmallVector<MCPhysReg> getRegsMadeSafeToDeref(const MCInst &Point,
368368
const SrcState &Cur) const {
369369
SmallVector<MCPhysReg> Regs;
370-
const MCPhysReg NoReg = BC.MIB->getNoRegister();
371370

372371
// A signed pointer can be authenticated, or
373-
ErrorOr<MCPhysReg> AutReg = BC.MIB->getAuthenticatedReg(Point);
374-
if (AutReg && *AutReg != NoReg)
372+
bool Dummy = false;
373+
if (auto AutReg = BC.MIB->getWrittenAuthenticatedReg(Point, Dummy))
375374
Regs.push_back(*AutReg);
376375

377376
// ... a safe address can be materialized, or
378-
MCPhysReg NewAddrReg = BC.MIB->getMaterializedAddressRegForPtrAuth(Point);
379-
if (NewAddrReg != NoReg)
380-
Regs.push_back(NewAddrReg);
377+
if (auto NewAddrReg = BC.MIB->getMaterializedAddressRegForPtrAuth(Point))
378+
Regs.push_back(*NewAddrReg);
381379

382380
// ... an address can be updated in a safe manner, producing the result
383381
// which is as trusted as the input address.
@@ -393,13 +391,20 @@ class SrcSafetyAnalysis {
393391
SmallVector<MCPhysReg> getRegsMadeTrusted(const MCInst &Point,
394392
const SrcState &Cur) const {
395393
SmallVector<MCPhysReg> Regs;
396-
const MCPhysReg NoReg = BC.MIB->getNoRegister();
397394

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

404409
if (CheckerSequenceInfo.contains(&Point)) {
405410
MCPhysReg CheckedReg;
@@ -414,9 +419,8 @@ class SrcSafetyAnalysis {
414419
}
415420

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

421425
// ... an address can be updated in a safe manner, producing the result
422426
// which is as trusted as the input address.
@@ -731,25 +735,28 @@ shouldReportReturnGadget(const BinaryContext &BC, const MCInstReference &Inst,
731735
if (!BC.MIB->isReturn(Inst))
732736
return std::nullopt;
733737

734-
ErrorOr<MCPhysReg> MaybeRetReg = BC.MIB->getRegUsedAsRetDest(Inst);
735-
if (MaybeRetReg.getError()) {
738+
bool IsAuthenticated = false;
739+
std::optional<MCPhysReg> RetReg =
740+
BC.MIB->getRegUsedAsRetDest(Inst, IsAuthenticated);
741+
if (!RetReg) {
736742
return make_generic_report(
737743
Inst, "Warning: pac-ret analysis could not analyze this return "
738744
"instruction");
739745
}
740-
MCPhysReg RetReg = *MaybeRetReg;
746+
if (IsAuthenticated)
747+
return std::nullopt;
748+
749+
assert(*RetReg != BC.MIB->getNoRegister());
741750
LLVM_DEBUG({
742751
traceInst(BC, "Found RET inst", Inst);
743-
traceReg(BC, "RetReg", RetReg);
744-
traceReg(BC, "Authenticated reg", BC.MIB->getAuthenticatedReg(Inst));
752+
traceReg(BC, "RetReg", *RetReg);
753+
traceRegMask(BC, "SafeToDerefRegs", S.SafeToDerefRegs);
745754
});
746-
if (BC.MIB->isAuthenticationOfReg(Inst, RetReg))
747-
return std::nullopt;
748-
LLVM_DEBUG({ traceRegMask(BC, "SafeToDerefRegs", S.SafeToDerefRegs); });
749-
if (S.SafeToDerefRegs[RetReg])
755+
756+
if (S.SafeToDerefRegs[*RetReg])
750757
return std::nullopt;
751758

752-
return make_report(RetKind, Inst, RetReg);
759+
return make_report(RetKind, Inst, *RetReg);
753760
}
754761

755762
static std::optional<BriefReport<MCPhysReg>>
@@ -782,19 +789,20 @@ shouldReportSigningOracle(const BinaryContext &BC, const MCInstReference &Inst,
782789
const SrcState &S) {
783790
static const GadgetKind SigningOracleKind("signing oracle found");
784791

785-
MCPhysReg SignedReg = BC.MIB->getSignedReg(Inst);
786-
if (SignedReg == BC.MIB->getNoRegister())
792+
std::optional<MCPhysReg> SignedReg = BC.MIB->getSignedReg(Inst);
793+
if (!SignedReg)
787794
return std::nullopt;
788795

796+
assert(*SignedReg != BC.MIB->getNoRegister());
789797
LLVM_DEBUG({
790798
traceInst(BC, "Found sign inst", Inst);
791-
traceReg(BC, "Signed reg", SignedReg);
799+
traceReg(BC, "Signed reg", *SignedReg);
792800
traceRegMask(BC, "TrustedRegs", S.TrustedRegs);
793801
});
794-
if (S.TrustedRegs[SignedReg])
802+
if (S.TrustedRegs[*SignedReg])
795803
return std::nullopt;
796804

797-
return make_report(SigningOracleKind, Inst, SignedReg);
805+
return make_report(SigningOracleKind, Inst, *SignedReg);
798806
}
799807

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

0 commit comments

Comments
 (0)