Skip to content

Commit 6e6d83d

Browse files
committed
[BOLT] Gadget scanner: improve handling of unreachable basic blocks
Instead of refusing to analyze an instruction completely, when it is unreachable according to the CFG reconstructed by BOLT, pessimistically assume all registers to be unsafe at the start of basic blocks without any predecessors. Nevertheless, unreachable basic blocks found in optimized code likely means imprecise CFG reconstruction, thus report a warning once per basic block without predecessors.
1 parent eae7596 commit 6e6d83d

File tree

3 files changed

+95
-15
lines changed

3 files changed

+95
-15
lines changed

bolt/lib/Passes/PAuthGadgetScanner.cpp

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,12 @@ class SrcSafetyAnalysis {
341341
return S;
342342
}
343343

344+
/// Creates a state with all registers marked unsafe (not to be confused
345+
/// with empty state).
346+
SrcState createUnsafeState() const {
347+
return SrcState(NumRegs, RegsToTrackInstsFor.getNumTrackedRegisters());
348+
}
349+
344350
BitVector getClobberedRegs(const MCInst &Point) const {
345351
BitVector Clobbered(NumRegs);
346352
// Assume a call can clobber all registers, including callee-saved
@@ -584,6 +590,13 @@ class DataflowSrcSafetyAnalysis
584590
if (BB.isEntryPoint())
585591
return createEntryState();
586592

593+
// If a basic block without any predecessors is found in an optimized code,
594+
// this likely means that some CFG edges were not detected. Pessimistically
595+
// assume all registers to be unsafe before this basic block and warn about
596+
// this fact in FunctionAnalysis::findUnsafeUses().
597+
if (BB.pred_empty())
598+
return createUnsafeState();
599+
587600
return SrcState();
588601
}
589602

@@ -688,12 +701,6 @@ class CFGUnawareSrcSafetyAnalysis : public SrcSafetyAnalysis,
688701
using SrcSafetyAnalysis::BC;
689702
BinaryFunction &BF;
690703

691-
/// Creates a state with all registers marked unsafe (not to be confused
692-
/// with empty state).
693-
SrcState createUnsafeState() const {
694-
return SrcState(NumRegs, RegsToTrackInstsFor.getNumTrackedRegisters());
695-
}
696-
697704
public:
698705
CFGUnawareSrcSafetyAnalysis(BinaryFunction &BF,
699706
MCPlusBuilder::AllocatorIdTy AllocId,
@@ -1350,19 +1357,30 @@ void FunctionAnalysisContext::findUnsafeUses(
13501357
BF.dump();
13511358
});
13521359

1360+
if (BF.hasCFG()) {
1361+
// Warn on basic blocks being unreachable according to BOLT, as this
1362+
// likely means CFG is imprecise.
1363+
for (BinaryBasicBlock &BB : BF) {
1364+
if (!BB.pred_empty() || BB.isEntryPoint())
1365+
continue;
1366+
// Arbitrarily attach the report to the first instruction of BB.
1367+
MCInst *InstToReport = BB.getFirstNonPseudoInstr();
1368+
if (!InstToReport)
1369+
continue; // BB has no real instructions
1370+
1371+
Reports.push_back(
1372+
make_generic_report(MCInstReference::get(InstToReport, BF),
1373+
"Warning: no predecessor basic blocks detected "
1374+
"(possibly incomplete CFG)"));
1375+
}
1376+
}
1377+
13531378
iterateOverInstrs(BF, [&](MCInstReference Inst) {
13541379
if (BC.MIB->isCFI(Inst))
13551380
return;
13561381

13571382
const SrcState &S = Analysis->getStateBefore(Inst);
1358-
1359-
// If non-empty state was never propagated from the entry basic block
1360-
// to Inst, assume it to be unreachable and report a warning.
1361-
if (S.empty()) {
1362-
Reports.push_back(
1363-
make_generic_report(Inst, "Warning: unreachable instruction found"));
1364-
return;
1365-
}
1383+
assert(!S.empty() && "Instruction has no associated state");
13661384

13671385
if (auto Report = shouldReportReturnGadget(BC, Inst, S))
13681386
Reports.push_back(*Report);

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,12 +215,17 @@ f_callclobbered_calleesaved:
215215
.globl f_unreachable_instruction
216216
.type f_unreachable_instruction,@function
217217
f_unreachable_instruction:
218-
// CHECK-LABEL: GS-PAUTH: Warning: unreachable instruction found in function f_unreachable_instruction, basic block {{[0-9a-zA-Z.]+}}, at address
218+
// CHECK-LABEL: GS-PAUTH: Warning: no predecessor basic blocks detected (possibly incomplete CFG) in function f_unreachable_instruction, basic block {{[0-9a-zA-Z.]+}}, at address
219219
// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: add x0, x1, x2
220220
// CHECK-NOT: instructions that write to the affected registers after any authentication are:
221+
// CHECK-LABEL: GS-PAUTH: non-protected ret found in function f_unreachable_instruction, basic block {{[0-9a-zA-Z.]+}}, at address
222+
// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: ret
223+
// CHECK-NEXT: The 0 instructions that write to the affected registers after any authentication are:
221224
b 1f
222225
add x0, x1, x2
223226
1:
227+
// "ret" is reported as unprotected, as LR is pessimistically assumed
228+
// unsafe at "add x0, x1, x2", thus it is unsafe at "ret" as well.
224229
ret
225230
.size f_unreachable_instruction, .-f_unreachable_instruction
226231

bolt/test/binary-analysis/AArch64/gs-pauth-calls.s

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1428,6 +1428,63 @@ printed_instrs_nocfg:
14281428
br x0
14291429
.size printed_instrs_nocfg, .-printed_instrs_nocfg
14301430

1431+
// Test handling of unreachable basic blocks.
1432+
//
1433+
// Basic blocks without any predecessors were observed in real-world optimized
1434+
// code. At least sometimes they were actually reachable via jump table, which
1435+
// was not detected, but the function was processed as if its CFG was
1436+
// reconstructed successfully.
1437+
//
1438+
// As a more predictable model example, let's use really unreachable code
1439+
// for testing.
1440+
1441+
.globl bad_unreachable_call
1442+
.type bad_unreachable_call,@function
1443+
bad_unreachable_call:
1444+
// CHECK-LABEL: GS-PAUTH: Warning: no predecessor basic blocks detected (possibly incomplete CFG) in function bad_unreachable_call, basic block {{[^,]+}}, at address
1445+
// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: blr x0
1446+
// CHECK-NOT: instructions that write to the affected registers after any authentication are:
1447+
// CHECK-LABEL: GS-PAUTH: non-protected call found in function bad_unreachable_call, basic block {{[^,]+}}, at address
1448+
// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: blr x0
1449+
// CHECK-NEXT: The 0 instructions that write to the affected registers after any authentication are:
1450+
paciasp
1451+
stp x29, x30, [sp, #-16]!
1452+
mov x29, sp
1453+
1454+
b 1f
1455+
// unreachable basic block:
1456+
blr x0
1457+
1458+
1: // reachable basic block:
1459+
ldp x29, x30, [sp], #16
1460+
autiasp
1461+
ret
1462+
.size bad_unreachable_call, .-bad_unreachable_call
1463+
1464+
.globl good_unreachable_call
1465+
.type good_unreachable_call,@function
1466+
good_unreachable_call:
1467+
// CHECK-NOT: non-protected call{{.*}}good_unreachable_call
1468+
// CHECK-LABEL: GS-PAUTH: Warning: no predecessor basic blocks detected (possibly incomplete CFG) in function good_unreachable_call, basic block {{[^,]+}}, at address
1469+
// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: autia x0, x1
1470+
// CHECK-NOT: instructions that write to the affected registers after any authentication are:
1471+
// CHECK-NOT: non-protected call{{.*}}good_unreachable_call
1472+
paciasp
1473+
stp x29, x30, [sp, #-16]!
1474+
mov x29, sp
1475+
1476+
b 1f
1477+
// unreachable basic block:
1478+
autia x0, x1
1479+
blr x0 // <-- this call is definitely protected provided at least
1480+
// basic block boundaries are detected correctly
1481+
1482+
1: // reachable basic block:
1483+
ldp x29, x30, [sp], #16
1484+
autiasp
1485+
ret
1486+
.size good_unreachable_call, .-good_unreachable_call
1487+
14311488
.globl main
14321489
.type main,@function
14331490
main:

0 commit comments

Comments
 (0)