Skip to content

Commit b4e9bd6

Browse files
committed
[BOLT] Gadget scanner: refactor analysis of RET instructions
In preparation for implementing detection of more gadget kinds, refactor checking for non-protected return instructions.
1 parent cf9baa5 commit b4e9bd6

File tree

2 files changed

+95
-66
lines changed

2 files changed

+95
-66
lines changed

bolt/include/bolt/Passes/PAuthGadgetScanner.h

+18-5
Original file line numberDiff line numberDiff line change
@@ -199,19 +199,34 @@ struct Report {
199199
virtual void generateReport(raw_ostream &OS,
200200
const BinaryContext &BC) const = 0;
201201

202+
virtual const ArrayRef<MCPhysReg> getAffectedRegisters() const { return {}; }
203+
virtual void
204+
setOverwritingInstrs(const std::vector<MCInstReference> &Instrs) {}
205+
202206
void printBasicInfo(raw_ostream &OS, const BinaryContext &BC,
203207
StringRef IssueKind) const;
204208
};
205209

206210
struct GadgetReport : public Report {
207211
const GadgetKind &Kind;
212+
SmallVector<MCPhysReg> AffectedRegisters;
208213
std::vector<MCInstReference> OverwritingInstrs;
209214

210215
GadgetReport(const GadgetKind &Kind, MCInstReference Location,
211-
std::vector<MCInstReference> OverwritingInstrs)
212-
: Report(Location), Kind(Kind), OverwritingInstrs(OverwritingInstrs) {}
216+
const BitVector &AffectedRegisters)
217+
: Report(Location), Kind(Kind),
218+
AffectedRegisters(AffectedRegisters.set_bits()) {}
213219

214220
void generateReport(raw_ostream &OS, const BinaryContext &BC) const override;
221+
222+
const ArrayRef<MCPhysReg> getAffectedRegisters() const override {
223+
return AffectedRegisters;
224+
}
225+
226+
void
227+
setOverwritingInstrs(const std::vector<MCInstReference> &Instrs) override {
228+
OverwritingInstrs = Instrs;
229+
}
215230
};
216231

217232
/// Report with a free-form message attached.
@@ -224,16 +239,14 @@ struct GenericReport : public Report {
224239
};
225240

226241
struct FunctionAnalysisResult {
227-
SmallSet<MCPhysReg, 1> RegistersAffected;
228242
std::vector<std::shared_ptr<Report>> Diagnostics;
229243
};
230244

231245
class Analysis : public BinaryFunctionPass {
232246
void runOnFunction(BinaryFunction &Function,
233247
MCPlusBuilder::AllocatorIdTy AllocatorId);
234248
FunctionAnalysisResult
235-
computeDfState(PacRetAnalysis &PRA, BinaryFunction &BF,
236-
MCPlusBuilder::AllocatorIdTy AllocatorId);
249+
computeDfState(BinaryFunction &BF, MCPlusBuilder::AllocatorIdTy AllocatorId);
237250

238251
std::map<const BinaryFunction *, FunctionAnalysisResult> AnalysisResults;
239252
std::mutex AnalysisResultsMutex;

bolt/lib/Passes/PAuthGadgetScanner.cpp

+77-61
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,57 +376,81 @@ class PacRetAnalysis
376376
}
377377
};
378378

379+
static std::shared_ptr<Report> tryCheckReturn(const BinaryContext &BC,
380+
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,
411+
Analysis::computeDfState(BinaryFunction &BF,
381412
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 = tryCheckReturn(BC, Inst, S))
429+
Result.Diagnostics.push_back(Report);
428430
}
429431
}
432+
433+
if (Result.Diagnostics.empty())
434+
return Result;
435+
436+
// Redo the analysis, but now also track which instructions last wrote
437+
// to any of the registers in RetRegsWithGadgets, so that better
438+
// diagnostics can be produced.
439+
440+
SmallSet<MCPhysReg, 4> RegsToTrack;
441+
for (auto Report : Result.Diagnostics)
442+
for (MCPhysReg Reg : Report->getAffectedRegisters())
443+
RegsToTrack.insert(Reg);
444+
445+
std::vector<MCPhysReg> RegsToTrackVec(RegsToTrack.begin(), RegsToTrack.end());
446+
447+
PacRetAnalysis PRWIA(BF, AllocatorId, RegsToTrackVec);
448+
PRWIA.run();
449+
for (auto Report : Result.Diagnostics) {
450+
Report->setOverwritingInstrs(PRWIA.getLastClobberingInsts(
451+
Report->Location, BF, Report->getAffectedRegisters()));
452+
}
453+
430454
return Result;
431455
}
432456

@@ -438,27 +462,19 @@ void Analysis::runOnFunction(BinaryFunction &BF,
438462
BF.dump();
439463
});
440464

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-
}
465+
if (!BF.hasCFG())
466+
return;
454467

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-
}
468+
FunctionAnalysisResult FAR = computeDfState(BF, AllocatorId);
469+
if (FAR.Diagnostics.empty())
470+
return;
471+
472+
// `runOnFunction` is typically getting called from multiple threads in
473+
// parallel. Therefore, use a lock to avoid data races when storing the
474+
// result of the analysis in the `AnalysisResults` map.
475+
{
476+
std::lock_guard<std::mutex> Lock(AnalysisResultsMutex);
477+
AnalysisResults[&BF] = FAR;
462478
}
463479
}
464480

0 commit comments

Comments
 (0)