Skip to content

Commit 27ce9e0

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 ec437a4 commit 27ce9e0

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.
@@ -733,25 +737,28 @@ shouldReportReturnGadget(const BinaryContext &BC, const MCInstReference &Inst,
733737
if (!BC.MIB->isReturn(Inst))
734738
return std::nullopt;
735739

736-
ErrorOr<MCPhysReg> MaybeRetReg = BC.MIB->getRegUsedAsRetDest(Inst);
737-
if (MaybeRetReg.getError()) {
740+
bool IsAuthenticated = false;
741+
std::optional<MCPhysReg> RetReg =
742+
BC.MIB->getRegUsedAsRetDest(Inst, IsAuthenticated);
743+
if (!RetReg) {
738744
return make_generic_report(
739745
Inst, "Warning: pac-ret analysis could not analyze this return "
740746
"instruction");
741747
}
742-
MCPhysReg RetReg = *MaybeRetReg;
748+
if (IsAuthenticated)
749+
return std::nullopt;
750+
751+
assert(*RetReg != BC.MIB->getNoRegister());
743752
LLVM_DEBUG({
744753
traceInst(BC, "Found RET inst", Inst);
745-
traceReg(BC, "RetReg", RetReg);
746-
traceReg(BC, "Authenticated reg", BC.MIB->getAuthenticatedReg(Inst));
754+
traceReg(BC, "RetReg", *RetReg);
755+
traceRegMask(BC, "SafeToDerefRegs", S.SafeToDerefRegs);
747756
});
748-
if (BC.MIB->isAuthenticationOfReg(Inst, RetReg))
749-
return std::nullopt;
750-
LLVM_DEBUG({ traceRegMask(BC, "SafeToDerefRegs", S.SafeToDerefRegs); });
751-
if (S.SafeToDerefRegs[RetReg])
757+
758+
if (S.SafeToDerefRegs[*RetReg])
752759
return std::nullopt;
753760

754-
return make_report(RetKind, Inst, RetReg);
761+
return make_report(RetKind, Inst, *RetReg);
755762
}
756763

757764
static std::optional<BriefReport<MCPhysReg>>
@@ -784,19 +791,20 @@ shouldReportSigningOracle(const BinaryContext &BC, const MCInstReference &Inst,
784791
const SrcState &S) {
785792
static const GadgetKind SigningOracleKind("signing oracle found");
786793

787-
MCPhysReg SignedReg = BC.MIB->getSignedReg(Inst);
788-
if (SignedReg == BC.MIB->getNoRegister())
794+
std::optional<MCPhysReg> SignedReg = BC.MIB->getSignedReg(Inst);
795+
if (!SignedReg)
789796
return std::nullopt;
790797

798+
assert(*SignedReg != BC.MIB->getNoRegister());
791799
LLVM_DEBUG({
792800
traceInst(BC, "Found sign inst", Inst);
793-
traceReg(BC, "Signed reg", SignedReg);
801+
traceReg(BC, "Signed reg", *SignedReg);
794802
traceRegMask(BC, "TrustedRegs", S.TrustedRegs);
795803
});
796-
if (S.TrustedRegs[SignedReg])
804+
if (S.TrustedRegs[*SignedReg])
797805
return std::nullopt;
798806

799-
return make_report(SigningOracleKind, Inst, SignedReg);
807+
return make_report(SigningOracleKind, Inst, *SignedReg);
800808
}
801809

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

0 commit comments

Comments
 (0)