Skip to content

Commit c975f5b

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 7324b6a commit c975f5b

File tree

6 files changed

+345
-39
lines changed

6 files changed

+345
-39
lines changed

bolt/include/bolt/Core/MCPlusBuilder.h

+16
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

+69-23
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,50 @@ class PacRetAnalysis
335335
});
336336
}
337337

338+
BitVector getClobberedRegs(const MCInst &Point) const {
339+
BitVector Clobbered(NumRegs, false);
340+
// Assume a call can clobber all registers, including callee-saved
341+
// registers. There's a good chance that callee-saved registers will be
342+
// saved on the stack at some point during execution of the callee.
343+
// Therefore they should also be considered as potentially modified by an
344+
// attacker/written to.
345+
// Also, not all functions may respect the AAPCS ABI rules about
346+
// caller/callee-saved registers.
347+
if (BC.MIB->isCall(Point))
348+
Clobbered.set();
349+
else
350+
BC.MIB->getClobberedRegs(Point, Clobbered);
351+
return Clobbered;
352+
}
353+
354+
// Returns all registers that can be treated as if they are written by an
355+
// authentication instruction.
356+
SmallVector<MCPhysReg> getAuthenticatedRegs(const MCInst &Point,
357+
const State &Cur) const {
358+
SmallVector<MCPhysReg> Regs;
359+
const MCPhysReg NoReg = BC.MIB->getNoRegister();
360+
361+
// A signed pointer can be authenticated, or
362+
ErrorOr<MCPhysReg> AutReg = BC.MIB->getAuthenticatedReg(Point);
363+
if (AutReg && *AutReg != NoReg)
364+
Regs.push_back(*AutReg);
365+
366+
// ... a safe address can be materialized, or
367+
MCPhysReg NewAddrReg = BC.MIB->getSafelyMaterializedAddressReg(Point);
368+
if (NewAddrReg != NoReg)
369+
Regs.push_back(NewAddrReg);
370+
371+
// ... an address can be updated in a safe manner, producing the result
372+
// which is as trusted as the input address.
373+
MCPhysReg ArithResult, ArithSrc;
374+
std::tie(ArithResult, ArithSrc) =
375+
BC.MIB->analyzeSafeAddressArithmetics(Point);
376+
if (ArithResult != NoReg && Cur.SafeToDerefRegs[ArithSrc])
377+
Regs.push_back(ArithResult);
378+
379+
return Regs;
380+
}
381+
338382
State computeNext(const MCInst &Point, const State &Cur) {
339383
PacStatePrinter P(BC);
340384
LLVM_DEBUG({
@@ -355,37 +399,39 @@ class PacRetAnalysis
355399
return State();
356400
}
357401

402+
// First, compute various properties of the instruction, taking the state
403+
// before its execution into account, if necessary.
404+
405+
BitVector Clobbered = getClobberedRegs(Point);
406+
// Compute the set of registers that can be considered as written by
407+
// an authentication instruction. This includes operations that are
408+
// *strictly better* than authentication, such as materializing a
409+
// PC-relative constant.
410+
SmallVector<MCPhysReg> AuthenticatedOrBetter =
411+
getAuthenticatedRegs(Point, Cur);
412+
413+
// Then, compute the state after this instruction is executed.
358414
State Next = Cur;
359-
BitVector Clobbered(NumRegs, false);
360-
// Assume a call can clobber all registers, including callee-saved
361-
// registers. There's a good chance that callee-saved registers will be
362-
// saved on the stack at some point during execution of the callee.
363-
// Therefore they should also be considered as potentially modified by an
364-
// attacker/written to.
365-
// Also, not all functions may respect the AAPCS ABI rules about
366-
// caller/callee-saved registers.
367-
if (BC.MIB->isCall(Point))
368-
Clobbered.set();
369-
else
370-
BC.MIB->getClobberedRegs(Point, Clobbered);
415+
371416
Next.SafeToDerefRegs.reset(Clobbered);
372417
// Keep track of this instruction if it writes to any of the registers we
373418
// need to track that for:
374419
for (MCPhysReg Reg : RegsToTrackInstsFor.getRegisters())
375420
if (Clobbered[Reg])
376421
lastWritingInsts(Next, Reg) = {&Point};
377422

378-
ErrorOr<MCPhysReg> AutReg = BC.MIB->getAuthenticatedReg(Point);
379-
if (AutReg && *AutReg != BC.MIB->getNoRegister()) {
380-
// The sub-registers of *AutReg are also trusted now, but not its
381-
// super-registers (as they retain untrusted register units).
382-
BitVector AuthenticatedSubregs =
383-
BC.MIB->getAliases(*AutReg, /*OnlySmaller=*/true);
384-
for (MCPhysReg Reg : AuthenticatedSubregs.set_bits()) {
385-
Next.SafeToDerefRegs.set(Reg);
386-
if (RegsToTrackInstsFor.isTracked(Reg))
387-
lastWritingInsts(Next, Reg).clear();
388-
}
423+
// After accounting for clobbered registers in general, override the state
424+
// according to authentication and other *special cases* of clobbering.
425+
426+
// The sub-registers of each authenticated register are also trusted now,
427+
// but not their super-registers (as they retain untrusted register units).
428+
BitVector AuthenticatedSubregs(NumRegs);
429+
for (MCPhysReg AutReg : AuthenticatedOrBetter)
430+
AuthenticatedSubregs |= BC.MIB->getAliases(AutReg, /*OnlySmaller=*/true);
431+
for (MCPhysReg Reg : AuthenticatedSubregs.set_bits()) {
432+
Next.SafeToDerefRegs.set(Reg);
433+
if (RegsToTrackInstsFor.isTracked(Reg))
434+
lastWritingInsts(Next, Reg).clear();
389435
}
390436

391437
LLVM_DEBUG({

bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp

+30
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,36 @@ class AArch64MCPlusBuilder : public MCPlusBuilder {
304304
}
305305
}
306306

307+
MCPhysReg getSafelyMaterializedAddressReg(const MCInst &Inst) const override {
308+
switch (Inst.getOpcode()) {
309+
case AArch64::ADR:
310+
case AArch64::ADRP:
311+
return Inst.getOperand(0).getReg();
312+
default:
313+
return getNoRegister();
314+
}
315+
}
316+
317+
std::pair<MCPhysReg, MCPhysReg>
318+
analyzeSafeAddressArithmetics(const MCInst &Inst) const override {
319+
switch (Inst.getOpcode()) {
320+
default:
321+
return std::make_pair(getNoRegister(), getNoRegister());
322+
case AArch64::ADDXri:
323+
case AArch64::SUBXri:
324+
return std::make_pair(Inst.getOperand(0).getReg(),
325+
Inst.getOperand(1).getReg());
326+
case AArch64::ORRXrs:
327+
// "mov Xd, Xm" is equivalent to "orr Xd, XZR, Xm, lsl #0"
328+
if (Inst.getOperand(1).getReg() != AArch64::XZR ||
329+
Inst.getOperand(3).getImm() != 0)
330+
return std::make_pair(getNoRegister(), getNoRegister());
331+
332+
return std::make_pair(Inst.getOperand(0).getReg(),
333+
Inst.getOperand(2).getReg());
334+
}
335+
}
336+
307337
bool isADRP(const MCInst &Inst) const override {
308338
return Inst.getOpcode() == AArch64::ADRP;
309339
}

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

-15
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)