Skip to content

[BOLT][binary-analysis] Add initial pac-ret gadget scanner #122304

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 22 commits into from
Feb 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
0b0137b
[BOLT][binary-analysis] Add initial pac-ret gadget scanner
kbeyls Jan 9, 2025
35e1c44
format cleanup
kbeyls Jan 9, 2025
3b1bd24
Address first round of review feedback by Peter Smith
kbeyls Jan 10, 2025
39d0e54
Address first round of review feedback by @atrosinenko
kbeyls Jan 10, 2025
6edaa01
doTrackReg -> isTrackingReg
kbeyls Jan 10, 2025
dd20d44
ParentKind->Kind; CurrentLocation->ParentKind
kbeyls Jan 13, 2025
a30116d
Address the easy-to-address feedback by Jacob
kbeyls Jan 13, 2025
f44f9bf
Handle erets by producing diagnostic that analysis cannot analyze the…
kbeyls Jan 13, 2025
89d9994
Improve comment related to pauthabi checking
kbeyls Jan 15, 2025
833ef9f
Make a few trivial-to-apply improvements suggested by @atrosinenko
kbeyls Jan 15, 2025
fcd288c
Add regression tests for the instructions added in pauth-lr: AUTI{A,B…
kbeyls Jan 16, 2025
0480ce7
Introduce lastWritingInsts helper function to improve readability
kbeyls Jan 16, 2025
a05ae62
Add a few more regression tests
kbeyls Jan 16, 2025
35dfd8d
Add a test for correct uses of non-LR return. This also adds a FIXME …
kbeyls Jan 17, 2025
c1103cf
Assume a call may clobber all registers.
kbeyls Jan 17, 2025
8d4ac8e
Make many class names much more readable by introducing namespace to …
kbeyls Jan 17, 2025
b5117a0
Improve comment + fix formatting issue
kbeyls Jan 20, 2025
6f8b615
Many small improvements
kbeyls Jan 20, 2025
80c4b8f
More small improvements
kbeyls Jan 22, 2025
7a12544
Add FIXME test cases for passing potentially attacker-controlled regi…
kbeyls Jan 27, 2025
2e22a1d
More nitpicks based on feedback from atrosinenko
kbeyls Feb 13, 2025
66d94c6
Add 2 FIXME comments
kbeyls Feb 19, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
177 changes: 175 additions & 2 deletions bolt/docs/BinaryAnalysis.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,182 @@ analyses implemented in the BOLT libraries.

## Which binary analyses are implemented?

At the moment, no binary analyses are implemented.
* [Security scanners](#security-scanners)
* [pac-ret analysis](#pac-ret-analysis)

The goal is to make it easy using a plug-in framework to add your own analyses.
### Security scanners

For the past 25 years, a large numbers of exploits have been built and used in
the wild to undermine computer security. The majority of these exploits abuse
memory vulnerabilities in programs, see evidence from
[Microsoft](https://youtu.be/PjbGojjnBZQ?si=oCHCa0SHgaSNr6Gr&t=836),
[Chromium](https://www.chromium.org/Home/chromium-security/memory-safety/) and
[Android](https://security.googleblog.com/2021/01/data-driven-security-hardening-in.html).

It is not surprising therefore, that a large number of mitigations have been
added to instruction sets and toolchains to make it harder to build an exploit
using a memory vulnerability. Examples are: stack canaries, stack clash,
pac-ret, shadow stacks, arm64e, and many more.

These mitigations guarantee a so-called "security property" on the binaries they
produce. For example, for stack canaries, the security property is roughly that
a canary is located on the stack between the set of saved registers and the set
of local variables. For pac-ret, it is roughly that either the return address is
never stored/retrieved to/from memory; or, there are no writes to the register
containing the return address between an instruction authenticating it and a
return instruction using it.

From time to time, however, a bug gets found in the implementation of such
mitigations in toolchains. Also, code that is written in assembler by hand
requires the developer to ensure these security properties by hand.

In short, it is sometimes found that a few places in the binary code are not
protected as well as expected given the requested mitigations. Attackers could
make use of those places (sometimes called gadgets) to circumvent the protection
that the mitigation should give.

One of the reasons that such gadgets, or holes in the mitigation implementation,
exist is that typically the amount of testing and verification for these
security properties is limited to checking results on specific examples.

In comparison, for testing functional correctness, or for testing performance,
toolchain and software in general typically get tested with large test suites
and benchmarks. In contrast, this typically does not get done for testing the
security properties of binary code.

Unlike functional correctness where compilation errors result in test failures,
and performance where speed and size differences are measurable, broken security
properties cannot be easily observed using existing testing and benchmarking
tools.

The security scanners implemented in `llvm-bolt-binary-analysis` aim to enable
the testing of security hardening in arbitrary programs and not just specific
examples.


#### pac-ret analysis

`pac-ret` protection is a security hardening scheme implemented in compilers
such as GCC and Clang, using the command line option
`-mbranch-protection=pac-ret`. This option is enabled by default on most widely
used Linux distributions.

The hardening scheme mitigates
[Return-Oriented Programming (ROP)](https://llsoftsec.github.io/llsoftsecbook/#return-oriented-programming)
attacks by making sure that return addresses are only ever stored to memory with
a cryptographic hash, called a
["Pointer Authentication Code" (PAC)](https://llsoftsec.github.io/llsoftsecbook/#pointer-authentication),
in the upper bits of the pointer. This makes it substantially harder for
attackers to divert control flow by overwriting a return address with a
different value.

The hardening scheme relies on compilers producing appropriate code sequences when
processing return addresses, especially when these are stored to and retrieved
from memory.

The `pac-ret` binary analysis can be invoked using the command line option
`--scanners=pac-ret`. It makes `llvm-bolt-binary-analysis` scan through the
provided binary, checking each function for the following security property:

> For each procedure and exception return instruction, the destination register
> must have one of the following properties:
>
> 1. be immutable within the function, or
> 2. the last write to the register must be by an authenticating instruction. This
> includes combined authentication and return instructions such as `RETAA`.

##### Example 1

For example, a typical non-pac-ret-protected function looks as follows:

```
stp x29, x30, [sp, #-0x10]!
mov x29, sp
bl g@PLT
add x0, x0, #0x3
ldp x29, x30, [sp], #0x10
ret
```

The return instruction `ret` implicitly uses register `x30` as the address to
return to. Register `x30` was last written by instruction `ldp`, which is not an
authenticating instruction. `llvm-bolt-binary-analysis --scanners=pac-ret` will
report this as follows:

```
GS-PACRET: non-protected ret found in function f1, basic block .LBB00, at address 10310
The return instruction is 00010310: ret # pacret-gadget: pac-ret-gadget<Ret:MCInstBBRef<BB:.LBB00:6>, Overwriting:[MCInstBBRef<BB:.LBB00:5> ]>
The 1 instructions that write to the return register after any authentication are:
1. 0001030c: ldp x29, x30, [sp], #0x10
This happens in the following basic block:
000102fc: stp x29, x30, [sp, #-0x10]!
00010300: mov x29, sp
00010304: bl g@PLT
00010308: add x0, x0, #0x3
0001030c: ldp x29, x30, [sp], #0x10
00010310: ret # pacret-gadget: pac-ret-gadget<Ret:MCInstBBRef<BB:.LBB00:6>, Overwriting:[MCInstBBRef<BB:.LBB00:5> ]>
```

The exact format of how `llvm-bolt-binary-analysis` reports this is expected to
evolve over time.

##### Example 2: multiple "last-overwriting" instructions

A simple example that shows how there can be a set of "last overwriting"
instructions of a register follows:

```
paciasp
stp x29, x30, [sp, #-0x10]!
ldp x29, x30, [sp], #0x10
cbnz x0, 1f
autiasp
1:
ret
```

This will produce the following diagnostic:

```
GS-PACRET: non-protected ret found in function f_crossbb1, basic block .Ltmp0, at address 102dc
The return instruction is 000102dc: ret # pacret-gadget: pac-ret-gadget<Ret:MCInstBBRef<BB:.Ltmp0:0>, Overwriting:[MCInstBBRef<BB:.LFT0:0> MCInstBBRef<BB:.LBB00:2> ]>
The 2 instructions that write to the return register after any authentication are:
1. 000102d0: ldp x29, x30, [sp], #0x10
2. 000102d8: autiasp
```

(Yes, this diagnostic could be improved because the second "overwriting"
instruction, `autiasp`, is an authenticating instruction...)

##### Known false positives or negatives

The following are current known cases of false positives:

1. Not handling "no-return" functions. See issue
[#115154](https://github.com/llvm/llvm-project/issues/115154) for details and
pointers to open PRs to fix this.
2. Not recognizing that a move of a properly authenticated value between registers,
results in the destination register having a properly authenticated value.
For example, the scanner currently produces a false negative for the following
code sequence:
```
autiasp
mov x16, x30
ret x16
```

The following are current known cases of false negatives:

1. Not handling functions for which the CFG cannot be reconstructed by BOLT. The
plan is to implement support for this, picking up the implementation from the
[prototype branch](
https://github.com/llvm/llvm-project/compare/main...kbeyls:llvm-project:bolt-gadget-scanner-prototype).

BOLT cannot currently handle functions with `cfi_negate_ra_state` correctly,
i.e. any binaries built with `-mbranch-protection=pac-ret`. The scanner is meant
to be used on specifically such binaries, so this is a major limitation! Work is
going on in PR [#120064](https://github.com/llvm/llvm-project/pull/120064) to
fix this.

## How to add your own binary analysis

Expand Down
17 changes: 17 additions & 0 deletions bolt/include/bolt/Core/MCPlusBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#include "llvm/MC/MCInstrAnalysis.h"
#include "llvm/MC/MCInstrDesc.h"
#include "llvm/MC/MCInstrInfo.h"
#include "llvm/MC/MCRegister.h"
#include "llvm/Support/Allocator.h"
#include "llvm/Support/Casting.h"
#include "llvm/Support/ErrorHandling.h"
Expand Down Expand Up @@ -550,6 +551,22 @@ class MCPlusBuilder {
return Analysis->isReturn(Inst);
}

virtual ErrorOr<MCPhysReg> getAuthenticatedReg(const MCInst &Inst) const {
llvm_unreachable("not implemented");
return getNoRegister();
}

virtual bool isAuthenticationOfReg(const MCInst &Inst,
MCPhysReg AuthenticatedReg) const {
llvm_unreachable("not implemented");
return false;
}

virtual ErrorOr<MCPhysReg> getRegUsedAsRetDest(const MCInst &Inst) const {
llvm_unreachable("not implemented");
return getNoRegister();
}

virtual bool isTerminator(const MCInst &Inst) const;

virtual bool isNoop(const MCInst &Inst) const {
Expand Down
Loading