Skip to content

Commit b79f18b

Browse files
committed
[BOLT] Gadget scanner: Detect address materialization and arithmetics
In addition to authenticated pointers, consider the contents of a register safe if it was * written by PC-relative address computation * updated by an arithmetic instruction whose input address is safe
1 parent bbb379d commit b79f18b

File tree

6 files changed

+345
-39
lines changed

6 files changed

+345
-39
lines changed

bolt/include/bolt/Core/MCPlusBuilder.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -587,6 +587,22 @@ class MCPlusBuilder {
587587
return getNoRegister();
588588
}
589589

590+
virtual MCPhysReg getSafelyMaterializedAddressReg(const MCInst &Inst) const {
591+
llvm_unreachable("not implemented");
592+
return getNoRegister();
593+
}
594+
595+
/// Analyzes if this instruction can safely perform address arithmetics.
596+
///
597+
/// If the first element of the returned pair is no-register, this instruction
598+
/// is considered unknown. Otherwise, (output, input) pair is returned,
599+
/// so that output is as trusted as input is.
600+
virtual std::pair<MCPhysReg, MCPhysReg>
601+
analyzeSafeAddressArithmetics(const MCInst &Inst) const {
602+
llvm_unreachable("not implemented");
603+
return std::make_pair(getNoRegister(), getNoRegister());
604+
}
605+
590606
virtual bool isTerminator(const MCInst &Inst) const;
591607

592608
virtual bool isNoop(const MCInst &Inst) const {

bolt/lib/Passes/PAuthGadgetScanner.cpp

Lines changed: 69 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -325,18 +325,7 @@ class PacRetAnalysis
325325
});
326326
}
327327

328-
State computeNext(const MCInst &Point, const State &Cur) {
329-
PacStatePrinter P(BC);
330-
LLVM_DEBUG({
331-
dbgs() << " PacRetAnalysis::ComputeNext(";
332-
BC.InstPrinter->printInst(&const_cast<MCInst &>(Point), 0, "", *BC.STI,
333-
dbgs());
334-
dbgs() << ", ";
335-
P.print(dbgs(), Cur);
336-
dbgs() << ")\n";
337-
});
338-
339-
State Next = Cur;
328+
BitVector getClobberedRegs(const MCInst &Point) const {
340329
BitVector Clobbered(NumRegs, false);
341330
// Assume a call can clobber all registers, including callee-saved
342331
// registers. There's a good chance that callee-saved registers will be
@@ -349,24 +338,81 @@ class PacRetAnalysis
349338
Clobbered.set();
350339
else
351340
BC.MIB->getClobberedRegs(Point, Clobbered);
341+
return Clobbered;
342+
}
343+
344+
// Returns all registers that can be treated as if they are written by an
345+
// authentication instruction.
346+
SmallVector<MCPhysReg> getAuthenticatedRegs(const MCInst &Point,
347+
const State &Cur) const {
348+
SmallVector<MCPhysReg> Regs;
349+
const MCPhysReg NoReg = BC.MIB->getNoRegister();
350+
351+
// A signed pointer can be authenticated, or
352+
ErrorOr<MCPhysReg> AutReg = BC.MIB->getAuthenticatedReg(Point);
353+
if (AutReg && *AutReg != NoReg)
354+
Regs.push_back(*AutReg);
355+
356+
// ... a safe address can be materialized, or
357+
MCPhysReg NewAddrReg = BC.MIB->getSafelyMaterializedAddressReg(Point);
358+
if (NewAddrReg != NoReg)
359+
Regs.push_back(NewAddrReg);
360+
361+
// ... address can be updated in a safe manner, producing the result
362+
// which is as trusted as the input address.
363+
MCPhysReg ArithResult, ArithSrc;
364+
std::tie(ArithResult, ArithSrc) =
365+
BC.MIB->analyzeSafeAddressArithmetics(Point);
366+
if (ArithResult != NoReg && Cur.SafeToDerefRegs[ArithSrc])
367+
Regs.push_back(ArithResult);
368+
369+
return Regs;
370+
}
371+
372+
State computeNext(const MCInst &Point, const State &Cur) {
373+
PacStatePrinter P(BC);
374+
LLVM_DEBUG({
375+
dbgs() << " PacRetAnalysis::ComputeNext(";
376+
BC.InstPrinter->printInst(&const_cast<MCInst &>(Point), 0, "", *BC.STI,
377+
dbgs());
378+
dbgs() << ", ";
379+
P.print(dbgs(), Cur);
380+
dbgs() << ")\n";
381+
});
382+
383+
// First, compute various properties of the instruction, taking the state
384+
// before its execution into account, if necessary.
385+
386+
BitVector Clobbered = getClobberedRegs(Point);
387+
// Compute the set of registers that can be considered as written by
388+
// an authentication instruction. This includes operations that are
389+
// *strictly better* than authentication, such as materializing a
390+
// PC-relative constant.
391+
SmallVector<MCPhysReg> AuthenticatedOrBetter =
392+
getAuthenticatedRegs(Point, Cur);
393+
394+
// Then, compute the state after this instruction is executed.
395+
State Next = Cur;
396+
352397
Next.SafeToDerefRegs.reset(Clobbered);
353398
// Keep track of this instruction if it writes to any of the registers we
354399
// need to track that for:
355400
for (MCPhysReg Reg : RegsToTrackInstsFor.getRegisters())
356401
if (Clobbered[Reg])
357402
lastWritingInsts(Next, Reg) = {&Point};
358403

359-
ErrorOr<MCPhysReg> AutReg = BC.MIB->getAuthenticatedReg(Point);
360-
if (AutReg && *AutReg != BC.MIB->getNoRegister()) {
361-
// The sub-registers of *AutReg are also trusted now, but not its
362-
// super-registers (as they retain untrusted register units).
363-
BitVector AuthenticatedSubregs =
364-
BC.MIB->getAliases(*AutReg, /*OnlySmaller=*/true);
365-
for (MCPhysReg Reg : AuthenticatedSubregs.set_bits()) {
366-
Next.SafeToDerefRegs.set(Reg);
367-
if (RegsToTrackInstsFor.isTracked(Reg))
368-
lastWritingInsts(Next, Reg).clear();
369-
}
404+
// After accounting for clobbered registers in general, override the state
405+
// according to authentication and other *special cases* of clobbering.
406+
407+
// The sub-registers of each authenticated register are also trusted now,
408+
// but not their super-registers (as they retain untrusted register units).
409+
BitVector AuthenticatedSubregs(NumRegs);
410+
for (MCPhysReg AutReg : AuthenticatedOrBetter)
411+
AuthenticatedSubregs |= BC.MIB->getAliases(AutReg, /*OnlySmaller=*/true);
412+
for (MCPhysReg Reg : AuthenticatedSubregs.set_bits()) {
413+
Next.SafeToDerefRegs.set(Reg);
414+
if (RegsToTrackInstsFor.isTracked(Reg))
415+
lastWritingInsts(Next, Reg).clear();
370416
}
371417

372418
LLVM_DEBUG({

bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,36 @@ class AArch64MCPlusBuilder : public MCPlusBuilder {
319319
}
320320
}
321321

322+
MCPhysReg getSafelyMaterializedAddressReg(const MCInst &Inst) const override {
323+
switch (Inst.getOpcode()) {
324+
case AArch64::ADR:
325+
case AArch64::ADRP:
326+
return Inst.getOperand(0).getReg();
327+
default:
328+
return getNoRegister();
329+
}
330+
}
331+
332+
std::pair<MCPhysReg, MCPhysReg>
333+
analyzeSafeAddressArithmetics(const MCInst &Inst) const override {
334+
switch (Inst.getOpcode()) {
335+
default:
336+
return std::make_pair(getNoRegister(), getNoRegister());
337+
case AArch64::ADDXri:
338+
case AArch64::SUBXri:
339+
return std::make_pair(Inst.getOperand(0).getReg(),
340+
Inst.getOperand(1).getReg());
341+
case AArch64::ORRXrs:
342+
// "mov Xd, Xm" is equivalent to "orr Xd, XZR, Xm, lsl #0"
343+
if (Inst.getOperand(1).getReg() != AArch64::XZR ||
344+
Inst.getOperand(3).getImm() != 0)
345+
return std::make_pair(getNoRegister(), getNoRegister());
346+
347+
return std::make_pair(Inst.getOperand(0).getReg(),
348+
Inst.getOperand(2).getReg());
349+
}
350+
}
351+
322352
bool isADRP(const MCInst &Inst) const override {
323353
return Inst.getOpcode() == AArch64::ADRP;
324354
}

bolt/test/binary-analysis/AArch64/gs-pacret-autiasp.s

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -141,24 +141,9 @@ f_nonx30_ret_ok:
141141
stp x29, x30, [sp, #-16]!
142142
mov x29, sp
143143
bl g
144-
add x0, x0, #3
145144
ldp x29, x30, [sp], #16
146-
// FIXME: Should the scanner understand that an authenticated register (below x30,
147-
// after the autiasp instruction), is OK to be moved to another register
148-
// and then that register being used to return?
149-
// This respects that pac-ret hardening intent, but the scanner currently
150-
// will produce a false positive for this.
151-
// Is it worthwhile to make the scanner more complex for this case?
152-
// So far, scanning many millions of instructions across a linux distro,
153-
// I haven't encountered such an example.
154-
// The ".if 0" block below tests this case and currently fails.
155-
.if 0
156145
autiasp
157146
mov x16, x30
158-
.else
159-
mov x16, x30
160-
autia x16, sp
161-
.endif
162147
// CHECK-NOT: function f_nonx30_ret_ok
163148
ret x16
164149
.size f_nonx30_ret_ok, .-f_nonx30_ret_ok

0 commit comments

Comments
 (0)