Skip to content

Commit 5b3ed52

Browse files
committed
[GS/pacret] Also recognize traps as non-returning control flow.
A substantial amount of remaining false positives in the pacret gadget list generated is due to the analyzer not recognizing that brk instructions stop the regular control flow. An example reported gadget false positive is as follows: GS-PACRET: non-protected ret found in function __BOLT_FDE_FUNCate3bb4, basic block .Ltmp12963, at address e3ca8 The return instruction is 000e3ca8: ret # pacret-gadget: pac-ret-gadget<Ret:MCInstBBRef<BB:.Ltmp12963:6>, Overwriting:[MCInstBBRef<BB:.Ltmp12963:1> ]> The 1 instructions that write to the return register after any authentication are: 1. 000e3c94: bl xmalloc@PLT This happens in the following basic block: 000e3c90: mov x0, #0x20 000e3c94: bl xmalloc@PLT 000e3c98: mov x1, #0x0 000e3c9c: str xzr, [x0, #0x18] 000e3ca0: ldr x0, [x1, #0x128] 000e3ca4: brk #0x3e8 000e3ca8: ret # pacret-gadget: pac-ret-gadget<Ret:MCInstBBRef<BB:.Ltmp12963:6>, Overwriting:[MCInstBBRef<BB:.Ltmp12963:1> ]> These brk instructions are typically generated from __builtin_trap(); but might also be generated otherwise. It seems that at least gcc understands that control flow cannot reach the point after the brk instruction, and therefore does not generate the pac-ret authentication instructions. For some reason though, the `ret` instruction is still generated. In order to not generate these false positives, BOLT needs to understand that these "traps" end control flow; similar to as if a non-returning function is called. This commit implements this. Comparing a scan of a well-populated /usr/lib64 directory on Fedora 39, this reduce the number of reported pac-ret gadgets from 46021 to 19776 a reduction with a factor of 2.32! Also, the number of libraries with reported pac-ret gadgets against them reduces from 521 to 137, a reduction with a factor 3.8!
1 parent 3f7b58d commit 5b3ed52

File tree

6 files changed

+59
-9
lines changed

6 files changed

+59
-9
lines changed

bolt/include/bolt/Core/MCPlusBuilder.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1733,6 +1733,12 @@ evaluateStackOffsetExpr(const MCInst &Inst, int64_t &Output,
17331733
return false;
17341734
}
17351735

1736+
/// Returns true if Inst is a trap instruction.
1737+
virtual bool isTrap(const MCInst &Inst) const {
1738+
llvm_unreachable("not implemented");
1739+
return false;
1740+
}
1741+
17361742
/// Creates an instruction to bump the stack pointer just like a call.
17371743
virtual bool createStackPointerIncrement(MCInst &Inst, int Size = 8,
17381744
bool NoFlagsClobber = false) const {

bolt/lib/Core/BinaryFunction.cpp

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2059,7 +2059,8 @@ bool BinaryFunction::buildCFG(MCPlusBuilder::AllocatorIdTy AllocatorId) {
20592059
addCFIPlaceholders(Offset, InsertBB);
20602060
}
20612061

2062-
const bool IsBlockEnd = MIB->isTerminator(Instr) || MIB->isNoReturnCall(Instr);
2062+
const bool IsBlockEnd = MIB->isTerminator(Instr) ||
2063+
MIB->isNoReturnCall(Instr) || MIB->isTrap(Instr);
20632064
IsLastInstrNop = MIB->isNoop(Instr);
20642065
if (!IsLastInstrNop)
20652066
LastInstrOffset = Offset;
@@ -2138,7 +2139,7 @@ bool BinaryFunction::buildCFG(MCPlusBuilder::AllocatorIdTy AllocatorId) {
21382139
//
21392140
// Conditional tail call is a special case since we don't add a taken
21402141
// branch successor for it.
2141-
if (MIB->isNoReturnCall(*LastInstr))
2142+
if (MIB->isNoReturnCall(*LastInstr) || MIB->isTrap(*LastInstr))
21422143
IsPrevFT = false;
21432144
else
21442145
IsPrevFT = !MIB->isTerminator(*LastInstr) ||
@@ -3408,15 +3409,16 @@ void BinaryFunction::postProcessBranches() {
34083409
continue;
34093410

34103411
// If it's the basic block that does not end up with a terminator - we
3411-
// insert a return instruction unless it's a call instruction.
3412+
// insert a return instruction unless it's a call instruction or a trap.
34123413
if (LastInstrRI == BB.rend()) {
34133414
LLVM_DEBUG(
34143415
dbgs() << "BOLT-DEBUG: at least one instruction expected in BB "
34153416
<< BB.getName() << " in function " << *this << '\n');
34163417
continue;
34173418
}
34183419
if (!BC.MIB->isTerminator(*LastInstrRI) &&
3419-
!BC.MIB->isCall(*LastInstrRI)) {
3420+
!BC.MIB->isCall(*LastInstrRI) &&
3421+
!BC.MIB->isTrap(*LastInstrRI)) {
34203422
LLVM_DEBUG(dbgs() << "BOLT-DEBUG: adding return to basic block "
34213423
<< BB.getName() << " in function " << *this << '\n');
34223424
MCInst ReturnInstr;

bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1449,6 +1449,10 @@ class AArch64MCPlusBuilder : public MCPlusBuilder {
14491449
return true;
14501450
}
14511451

1452+
bool isTrap(const MCInst &Inst) const override {
1453+
return Inst.getOpcode() == AArch64::BRK;
1454+
}
1455+
14521456
bool convertJmpToTailCall(MCInst &Inst) override {
14531457
setTailCall(Inst);
14541458
return true;

bolt/lib/Target/RISCV/RISCVMCPlusBuilder.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,11 @@ class RISCVMCPlusBuilder : public MCPlusBuilder {
219219
return true;
220220
}
221221

222+
bool isTrap(const MCInst &Inst) const override {
223+
return false;
224+
}
225+
226+
222227
bool createReturn(MCInst &Inst) const override {
223228
// TODO "c.jr ra" when RVC is enabled
224229
Inst.setOpcode(RISCV::JALR);

bolt/lib/Target/X86/X86MCPlusBuilder.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2763,6 +2763,10 @@ class X86MCPlusBuilder : public MCPlusBuilder {
27632763
return true;
27642764
}
27652765

2766+
bool isTrap(const MCInst &Inst) const override {
2767+
return Inst.getOpcode() == X86::TRAP;
2768+
}
2769+
27662770
bool reverseBranchCondition(MCInst &Inst, const MCSymbol *TBB,
27672771
MCContext *Ctx) const override {
27682772
unsigned InvCC = getInvertedCondCode(getCondCode(Inst));

bolt/test/gadget-scanner/gs-pacret-noreturn.s

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Check that there are no false positives related to no-return functions.
22

33
// RUN: %clang %cflags -march=armv8.3-a -mbranch-protection=pac-ret %s %p/../Inputs/asm_main.c -o %t.exe
4-
// RUN: llvm-bolt-gadget-scanner --scanners=pacret %t.exe --noreturnfuncs="doesnotreturn/1" 2>&1 | FileCheck -check-prefix=CHECK --allow-empty %s
4+
// RUN: llvm-bolt-gadget-scanner --scanners=pacret %t.exe --noreturnfuncs="doesnotreturn1/1" 2>&1 | FileCheck -check-prefix=CHECK --allow-empty %s
55

66

77
// Verify that we can also detect gadgets across basic blocks
@@ -17,15 +17,44 @@ f_call_returning:
1717
// CHECK-NEXT: The 1 instructions that write to the return register after any authentication are:
1818
// CHECK-NEXT: 1. {{[0-9a-f]+}}: bl call_returning
1919

20-
.type doesnotreturn,@function
21-
doesnotreturn:
20+
.type doesnotreturn1,@function
21+
doesnotreturn1:
22+
stp x29, x30, [sp, #-16]!
23+
ldp x29, x30, [sp], #16
2224
brk 1
23-
.size doesnotreturn, .-doesnotreturn
25+
ret
26+
.size doesnotreturn1, .-doesnotreturn1
27+
// CHECK-NOT: function doesnotreturn1
28+
29+
.type doesnotreturn2,@function
30+
doesnotreturn2:
31+
cmp x0, x1
32+
bgt .L1
33+
bl memcpy@PLT
34+
.L1:
35+
brk 1
36+
// BOLT used to insert an artificial ret here. This test case checks that no longer happens.
37+
ret
38+
.size doesnotreturn2, .-doesnotreturn2
39+
// CHECK-NOT: function doesnotreturn2
40+
41+
.type gadget_entirely_in_dead_code,@function
42+
gadget_entirely_in_dead_code:
43+
stp x29, x30, [sp, #-16]!
44+
brk 1
45+
ldp x29, x30, [sp], #16
46+
ret
47+
.size gadget_entirely_in_dead_code, .-gadget_entirely_in_dead_code
48+
// CHECK-LABEL: GS-PACRET: non-protected ret found in function gadget_entirely_in_dead_code{{(/[0-9])?}}, basic block .L{{[^,]+}}, at address
49+
// CHECK-NEXT: The return instruction is {{[0-9a-f]+}}: ret
50+
// CHECK-NEXT: The 1 instructions that write to the return register after any authentication are:
51+
// CHECK-NEXT: 1. {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10
52+
2453

2554
.globl f_call_noreturn
2655
.type f_call_noreturn,@function
2756
f_call_noreturn:
28-
bl doesnotreturn
57+
bl doesnotreturn1
2958
ret
3059
.size f_call_noreturn, .-f_call_noreturn
3160
// CHECK-NOT: function f_call_noreturn

0 commit comments

Comments
 (0)