Skip to content

Commit 72d1058

Browse files
authored
[BOLT] Gadget scanner: refactor analysis of RET instructions (#131897)
In preparation for implementing detection of more gadget kinds, refactor checking for non-protected return instructions.
1 parent 5afa0fa commit 72d1058

File tree

3 files changed

+124
-77
lines changed

3 files changed

+124
-77
lines changed

bolt/include/bolt/Passes/PAuthGadgetScanner.h

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -199,19 +199,40 @@ struct Report {
199199
virtual void generateReport(raw_ostream &OS,
200200
const BinaryContext &BC) const = 0;
201201

202+
// The two methods below are called by Analysis::computeDetailedInfo when
203+
// iterating over the reports.
204+
virtual const ArrayRef<MCPhysReg> getAffectedRegisters() const { return {}; }
205+
virtual void setOverwritingInstrs(const ArrayRef<MCInstReference> Instrs) {}
206+
202207
void printBasicInfo(raw_ostream &OS, const BinaryContext &BC,
203208
StringRef IssueKind) const;
204209
};
205210

206211
struct GadgetReport : public Report {
212+
// The particular kind of gadget that is detected.
207213
const GadgetKind &Kind;
208-
std::vector<MCInstReference> OverwritingInstrs;
214+
// The set of registers related to this gadget report (possibly empty).
215+
SmallVector<MCPhysReg> AffectedRegisters;
216+
// The instructions that clobber the affected registers.
217+
// There is no one-to-one correspondence with AffectedRegisters: for example,
218+
// the same register can be overwritten by different instructions in different
219+
// preceding basic blocks.
220+
SmallVector<MCInstReference> OverwritingInstrs;
209221

210222
GadgetReport(const GadgetKind &Kind, MCInstReference Location,
211-
std::vector<MCInstReference> OverwritingInstrs)
212-
: Report(Location), Kind(Kind), OverwritingInstrs(OverwritingInstrs) {}
223+
const BitVector &AffectedRegisters)
224+
: Report(Location), Kind(Kind),
225+
AffectedRegisters(AffectedRegisters.set_bits()) {}
213226

214227
void generateReport(raw_ostream &OS, const BinaryContext &BC) const override;
228+
229+
const ArrayRef<MCPhysReg> getAffectedRegisters() const override {
230+
return AffectedRegisters;
231+
}
232+
233+
void setOverwritingInstrs(const ArrayRef<MCInstReference> Instrs) override {
234+
OverwritingInstrs.assign(Instrs.begin(), Instrs.end());
235+
}
215236
};
216237

217238
/// Report with a free-form message attached.
@@ -224,16 +245,18 @@ struct GenericReport : public Report {
224245
};
225246

226247
struct FunctionAnalysisResult {
227-
SmallSet<MCPhysReg, 1> RegistersAffected;
228248
std::vector<std::shared_ptr<Report>> Diagnostics;
229249
};
230250

231251
class Analysis : public BinaryFunctionPass {
232252
void runOnFunction(BinaryFunction &Function,
233253
MCPlusBuilder::AllocatorIdTy AllocatorId);
234-
FunctionAnalysisResult
235-
computeDfState(PacRetAnalysis &PRA, BinaryFunction &BF,
236-
MCPlusBuilder::AllocatorIdTy AllocatorId);
254+
FunctionAnalysisResult findGadgets(BinaryFunction &BF,
255+
MCPlusBuilder::AllocatorIdTy AllocatorId);
256+
257+
void computeDetailedInfo(BinaryFunction &BF,
258+
MCPlusBuilder::AllocatorIdTy AllocatorId,
259+
FunctionAnalysisResult &Result);
237260

238261
std::map<const BinaryFunction *, FunctionAnalysisResult> AnalysisResults;
239262
std::mutex AnalysisResultsMutex;

bolt/lib/Passes/PAuthGadgetScanner.cpp

Lines changed: 91 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,7 @@ class PacRetAnalysis
353353
public:
354354
std::vector<MCInstReference>
355355
getLastClobberingInsts(const MCInst Ret, BinaryFunction &BF,
356-
const BitVector &UsedDirtyRegs) const {
356+
const ArrayRef<MCPhysReg> UsedDirtyRegs) const {
357357
if (RegsToTrackInstsFor.empty())
358358
return {};
359359
auto MaybeState = getStateAt(Ret);
@@ -362,7 +362,7 @@ class PacRetAnalysis
362362
const State &S = *MaybeState;
363363
// Due to aliasing registers, multiple registers may have been tracked.
364364
std::set<const MCInst *> LastWritingInsts;
365-
for (MCPhysReg TrackedReg : UsedDirtyRegs.set_bits()) {
365+
for (MCPhysReg TrackedReg : UsedDirtyRegs) {
366366
for (const MCInst *Inst : lastWritingInsts(S, TrackedReg))
367367
LastWritingInsts.insert(Inst);
368368
}
@@ -376,60 +376,90 @@ class PacRetAnalysis
376376
}
377377
};
378378

379+
static std::shared_ptr<Report>
380+
shouldReportReturnGadget(const BinaryContext &BC, const MCInstReference &Inst,
381+
const State &S) {
382+
static const GadgetKind RetKind("non-protected ret found");
383+
if (!BC.MIB->isReturn(Inst))
384+
return nullptr;
385+
386+
ErrorOr<MCPhysReg> MaybeRetReg = BC.MIB->getRegUsedAsRetDest(Inst);
387+
if (MaybeRetReg.getError()) {
388+
return std::make_shared<GenericReport>(
389+
Inst, "Warning: pac-ret analysis could not analyze this return "
390+
"instruction");
391+
}
392+
MCPhysReg RetReg = *MaybeRetReg;
393+
LLVM_DEBUG({
394+
traceInst(BC, "Found RET inst", Inst);
395+
traceReg(BC, "RetReg", RetReg);
396+
traceReg(BC, "Authenticated reg", BC.MIB->getAuthenticatedReg(Inst));
397+
});
398+
if (BC.MIB->isAuthenticationOfReg(Inst, RetReg))
399+
return nullptr;
400+
BitVector UsedDirtyRegs = S.NonAutClobRegs;
401+
LLVM_DEBUG({ traceRegMask(BC, "NonAutClobRegs at Ret", UsedDirtyRegs); });
402+
UsedDirtyRegs &= BC.MIB->getAliases(RetReg, /*OnlySmaller=*/true);
403+
LLVM_DEBUG({ traceRegMask(BC, "Intersection with RetReg", UsedDirtyRegs); });
404+
if (!UsedDirtyRegs.any())
405+
return nullptr;
406+
407+
return std::make_shared<GadgetReport>(RetKind, Inst, UsedDirtyRegs);
408+
}
409+
379410
FunctionAnalysisResult
380-
Analysis::computeDfState(PacRetAnalysis &PRA, BinaryFunction &BF,
381-
MCPlusBuilder::AllocatorIdTy AllocatorId) {
411+
Analysis::findGadgets(BinaryFunction &BF,
412+
MCPlusBuilder::AllocatorIdTy AllocatorId) {
413+
FunctionAnalysisResult Result;
414+
415+
PacRetAnalysis PRA(BF, AllocatorId, {});
382416
PRA.run();
383417
LLVM_DEBUG({
384418
dbgs() << " After PacRetAnalysis:\n";
385419
BF.dump();
386420
});
387421

388-
FunctionAnalysisResult Result;
389-
// Now scan the CFG for non-authenticating return instructions that use an
390-
// overwritten, non-authenticated register as return address.
391422
BinaryContext &BC = BF.getBinaryContext();
392423
for (BinaryBasicBlock &BB : BF) {
393-
for (int64_t I = BB.size() - 1; I >= 0; --I) {
394-
MCInst &Inst = BB.getInstructionAtIndex(I);
395-
if (BC.MIB->isReturn(Inst)) {
396-
ErrorOr<MCPhysReg> MaybeRetReg = BC.MIB->getRegUsedAsRetDest(Inst);
397-
if (MaybeRetReg.getError()) {
398-
Result.Diagnostics.push_back(std::make_shared<GenericReport>(
399-
MCInstInBBReference(&BB, I),
400-
"Warning: pac-ret analysis could not analyze this return "
401-
"instruction"));
402-
continue;
403-
}
404-
MCPhysReg RetReg = *MaybeRetReg;
405-
LLVM_DEBUG({
406-
traceInst(BC, "Found RET inst", Inst);
407-
traceReg(BC, "RetReg", RetReg);
408-
traceReg(BC, "Authenticated reg", BC.MIB->getAuthenticatedReg(Inst));
409-
});
410-
if (BC.MIB->isAuthenticationOfReg(Inst, RetReg))
411-
break;
412-
BitVector UsedDirtyRegs = PRA.getStateAt(Inst)->NonAutClobRegs;
413-
LLVM_DEBUG(
414-
{ traceRegMask(BC, "NonAutClobRegs at Ret", UsedDirtyRegs); });
415-
UsedDirtyRegs &= BC.MIB->getAliases(RetReg, /*OnlySmaller=*/true);
416-
LLVM_DEBUG(
417-
{ traceRegMask(BC, "Intersection with RetReg", UsedDirtyRegs); });
418-
if (UsedDirtyRegs.any()) {
419-
static const GadgetKind RetKind("non-protected ret found");
420-
// This return instruction needs to be reported
421-
Result.Diagnostics.push_back(std::make_shared<GadgetReport>(
422-
RetKind, MCInstInBBReference(&BB, I),
423-
PRA.getLastClobberingInsts(Inst, BF, UsedDirtyRegs)));
424-
for (MCPhysReg RetRegWithGadget : UsedDirtyRegs.set_bits())
425-
Result.RegistersAffected.insert(RetRegWithGadget);
426-
}
427-
}
424+
for (int64_t I = 0, E = BB.size(); I < E; ++I) {
425+
MCInstReference Inst(&BB, I);
426+
const State &S = *PRA.getStateAt(Inst);
427+
428+
if (auto Report = shouldReportReturnGadget(BC, Inst, S))
429+
Result.Diagnostics.push_back(Report);
428430
}
429431
}
430432
return Result;
431433
}
432434

435+
void Analysis::computeDetailedInfo(BinaryFunction &BF,
436+
MCPlusBuilder::AllocatorIdTy AllocatorId,
437+
FunctionAnalysisResult &Result) {
438+
BinaryContext &BC = BF.getBinaryContext();
439+
440+
// Collect the affected registers across all gadgets found in this function.
441+
SmallSet<MCPhysReg, 4> RegsToTrack;
442+
for (auto Report : Result.Diagnostics)
443+
RegsToTrack.insert_range(Report->getAffectedRegisters());
444+
std::vector<MCPhysReg> RegsToTrackVec(RegsToTrack.begin(), RegsToTrack.end());
445+
446+
// Re-compute the analysis with register tracking.
447+
PacRetAnalysis PRWIA(BF, AllocatorId, RegsToTrackVec);
448+
PRWIA.run();
449+
LLVM_DEBUG({
450+
dbgs() << " After detailed PacRetAnalysis:\n";
451+
BF.dump();
452+
});
453+
454+
// Augment gadget reports.
455+
for (auto Report : Result.Diagnostics) {
456+
LLVM_DEBUG(
457+
{ traceInst(BC, "Attaching clobbering info to", Report->Location); });
458+
Report->setOverwritingInstrs(PRWIA.getLastClobberingInsts(
459+
Report->Location, BF, Report->getAffectedRegisters()));
460+
}
461+
}
462+
433463
void Analysis::runOnFunction(BinaryFunction &BF,
434464
MCPlusBuilder::AllocatorIdTy AllocatorId) {
435465
LLVM_DEBUG({
@@ -438,27 +468,25 @@ void Analysis::runOnFunction(BinaryFunction &BF,
438468
BF.dump();
439469
});
440470

441-
if (BF.hasCFG()) {
442-
PacRetAnalysis PRA(BF, AllocatorId, {});
443-
FunctionAnalysisResult FAR = computeDfState(PRA, BF, AllocatorId);
444-
if (!FAR.RegistersAffected.empty()) {
445-
// Redo the analysis, but now also track which instructions last wrote
446-
// to any of the registers in RetRegsWithGadgets, so that better
447-
// diagnostics can be produced.
448-
std::vector<MCPhysReg> RegsToTrack;
449-
for (MCPhysReg R : FAR.RegistersAffected)
450-
RegsToTrack.push_back(R);
451-
PacRetAnalysis PRWIA(BF, AllocatorId, RegsToTrack);
452-
FAR = computeDfState(PRWIA, BF, AllocatorId);
453-
}
471+
if (!BF.hasCFG())
472+
return;
454473

455-
// `runOnFunction` is typically getting called from multiple threads in
456-
// parallel. Therefore, use a lock to avoid data races when storing the
457-
// result of the analysis in the `AnalysisResults` map.
458-
{
459-
std::lock_guard<std::mutex> Lock(AnalysisResultsMutex);
460-
AnalysisResults[&BF] = FAR;
461-
}
474+
FunctionAnalysisResult FAR = findGadgets(BF, AllocatorId);
475+
if (FAR.Diagnostics.empty())
476+
return;
477+
478+
// Redo the analysis, but now also track which instructions last wrote
479+
// to any of the registers in RetRegsWithGadgets, so that better
480+
// diagnostics can be produced.
481+
482+
computeDetailedInfo(BF, AllocatorId, FAR);
483+
484+
// `runOnFunction` is typically getting called from multiple threads in
485+
// parallel. Therefore, use a lock to avoid data races when storing the
486+
// result of the analysis in the `AnalysisResults` map.
487+
{
488+
std::lock_guard<std::mutex> Lock(AnalysisResultsMutex);
489+
AnalysisResults[&BF] = FAR;
462490
}
463491
}
464492

@@ -517,7 +545,7 @@ void GadgetReport::generateReport(raw_ostream &OS,
517545
<< " instructions that write to the affected registers after any "
518546
"authentication are:\n";
519547
// Sort by address to ensure output is deterministic.
520-
std::vector<MCInstReference> OI = OverwritingInstrs;
548+
SmallVector<MCInstReference> OI = OverwritingInstrs;
521549
llvm::sort(OI, [](const MCInstReference &A, const MCInstReference &B) {
522550
return A.getAddress() < B.getAddress();
523551
});

bolt/test/binary-analysis/AArch64/gs-pauth-debug-output.s

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -118,20 +118,16 @@ clobber:
118118
// CHECK-NEXT: .. result: (pacret-state<NonAutClobRegs: W30 , Insts: [0](0x{{[0-9a-f]+}} )>)
119119
// CHECK-NEXT: PacRetAnalysis::ComputeNext( ret x30, pacret-state<NonAutClobRegs: W30 , Insts: [0](0x{{[0-9a-f]+}} )>)
120120
// CHECK-NEXT: .. result: (pacret-state<NonAutClobRegs: W30 , Insts: [0](0x{{[0-9a-f]+}} )>)
121-
// CHECK-NEXT: After PacRetAnalysis:
121+
// CHECK-NEXT: After detailed PacRetAnalysis:
122122
// CHECK-NEXT: Binary Function "clobber" {
123123
// ...
124124
// CHECK: End of Function "clobber"
125125

126126
// The analysis was re-computed with register tracking, as an issue was found in this function.
127-
// Re-checking the instructions:
127+
// Iterating over the reports and attaching clobbering info:
128128

129129
// CHECK-EMPTY:
130-
// CHECK-NEXT: Found RET inst: 00000000: ret # PacRetAnalysis: pacret-state<NonAutClobRegs: BitVector, Insts: [0](0x{{[0-9a-f]+}} )>
131-
// CHECK-NEXT: RetReg: LR
132-
// CHECK-NEXT: Authenticated reg: (none)
133-
// CHECK-NEXT: NonAutClobRegs at Ret: W30
134-
// CHECK-NEXT: Intersection with RetReg: W30
130+
// CHECK-NEXT: Attaching clobbering info to: 00000000: ret # PacRetAnalysis: pacret-state<NonAutClobRegs: BitVector, Insts: [0](0x{{[0-9a-f]+}} )>
135131

136132

137133
// CHECK-LABEL:Analyzing in function main, AllocatorId 1

0 commit comments

Comments
 (0)