CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
760 lines
29 KiB
Markdown
760 lines
29 KiB
Markdown
# BOLT-based binary analysis
|
|
|
|
As part of post-link-time optimization, BOLT needs to perform a range of analyses
|
|
on binaries such as reconstructing control flow graphs, and more.
|
|
|
|
The `llvm-bolt-binary-analysis` tool enables running requested analyses
|
|
on binaries, and generating reports. It does this by building on top of the
|
|
analyses implemented in the BOLT libraries.
|
|
|
|
For now, the only analysis implemented is validation of Pointer Authentication
|
|
hardening on AArch64, more types of analyses can be added later.
|
|
|
|
## Contents
|
|
|
|
1. [Background and motivation](#background-and-motivation)
|
|
2. [Usage](#usage)
|
|
3. [Pointer Authentication validator](#pointer-authentication-validator)
|
|
4. [How to add your own analysis](#how-to-add-your-own-analysis)
|
|
|
|
## Background and motivation
|
|
|
|
### 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?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 manually written in assembly
|
|
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.
|
|
|
|
### Pointer Authentication
|
|
|
|
[Pointer Authentication](https://clang.llvm.org/docs/PointerAuthentication.html)
|
|
is intended to make it harder for an attacker to replace pointers at run time.
|
|
This is achieved by making it possible for the compiler or the programmer to
|
|
produce a *signed* pointer from a raw one, and then to probabilistically
|
|
*authenticate the signature* at another site in the program.
|
|
On AArch64 this is achieved by injecting a cryptographic hash, called a
|
|
["Pointer Authentication Code" (PAC)](https://llsoftsec.github.io/llsoftsecbook/#sec:pointer-authentication),
|
|
to the upper bits of the pointer.
|
|
While this approach can be applied to any pointers in the program, the most
|
|
frequent use case, at least in C and C++, is protecting the code pointers.
|
|
The language rules for such pointers are more restrictive, thus allowing the
|
|
compiler to implement various hardenings transparently to the programmer.
|
|
|
|
Probably the most simple variant of hardening based on Pointer Authentication is
|
|
[`pac-ret`](https://llsoftsec.github.io/llsoftsecbook/#sec:pac-ret), a security
|
|
hardening scheme implemented in compilers such as GCC and Clang, which can be
|
|
enabled with a command line option like `-mbranch-protection=pac-ret`.
|
|
On AArch64, `pac-ret` hardening 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
|
|
in a signed form. This makes it substantially harder for attackers to divert
|
|
control flow by overwriting a return address with a different value.
|
|
|
|
It is possible to link object files with different pac-ret hardening modes together,
|
|
as each particular function is responsible for signing the LR value in the prologue
|
|
and authenticating it in the epilogue. Other hardening variants exist that break
|
|
ABI compatibility, see the description of
|
|
[PAuth ABI Extensions](https://github.com/ARM-software/abi-aa/blob/main/pauthabielf64/pauthabielf64.rst)
|
|
for details. While pac-ret hardens (signs and authenticates) return addresses,
|
|
PAuth ABI may harden most other code pointers as well, such as function pointers
|
|
and labels used as the destination of computed goto.
|
|
|
|
The approach to validation of Pointer Authentication hardening implemented in
|
|
`llvm-bolt-binary-analysis` is tracking register properties using dataflow analysis.
|
|
At each program point it is computed whether the particular register can be
|
|
controlled and whether it can be inspected by an attacker under the
|
|
[Pointer Authentication threat model](https://clang.llvm.org/docs/PointerAuthentication.html#theory-of-operation).
|
|
Then, for a number of sensitive instruction kinds (such as function calls and
|
|
pointer signing instructions), the properties of input or output operands are
|
|
inspected to check if the particular instruction is emitted in a safe manner.
|
|
As an example, for a return instruction this usually means that either the link
|
|
register (`x30`, which is also referred to as LR on AArch64) was never clobbered
|
|
in the function, or that it was authenticated at some point and never written to
|
|
since then:
|
|
|
|
```
|
|
foo:
|
|
; x30 is assumed to be trusted on function entry
|
|
cbnz x0, .L1
|
|
; For every possible execution path leading to this point, x30 was never
|
|
; clobbered and is thus safe to be used by `ret`.
|
|
ret
|
|
.L1:
|
|
paciasp
|
|
stp x29, x30, [sp, #-16]!
|
|
mov x29, sp
|
|
|
|
bl bar
|
|
|
|
ldp x29, x30, [sp], #16
|
|
autiasp
|
|
; At this point, x30 was never written to since `autiasp`.
|
|
ret
|
|
```
|
|
|
|
The real rules being used by `llvm-bolt-binary-analysis` are somewhat more
|
|
complex, see the below sections for the detailed explanation.
|
|
|
|
## Usage
|
|
|
|
```
|
|
llvm-bolt-binary-analysis --scanners=<list> [options] <binary>
|
|
```
|
|
|
|
The `--scanners=` option accepts a comma-separated list of analyses to run on
|
|
the provided binary. The binary to be analyzed can be either an ELF executable or
|
|
a shared object. Similar to other BOLT tools, `llvm-bolt-binary-analysis` expects
|
|
`binary` to be unstripped and preferably linked with `--emit-relocs` linker option.
|
|
|
|
In addition to options printed by `llvm-bolt-binary-analysis --help-hidden`,
|
|
other relevant BOLT options can generally be passed, see `llvm-bolt --help-hidden`.
|
|
Incomplete help message is a known issue and is tracked in
|
|
[#176969](https://github.com/llvm/llvm-project/issues/176969).
|
|
|
|
The only analysis which is currently implemented is validation of a number of
|
|
Pointer Authentication-based hardening schemes such as pac-ret and PAuth ABI.
|
|
The specific set of gadget kinds which are searched for depends on command line
|
|
options. Each gadget found by PtrAuth gadget scanner results in a plain text
|
|
report printed at the end of the analysis. Furthermore, an attempt is made to
|
|
provide additional information on the instructions that made the register unsafe.
|
|
Please note that this extra information is provided on a best-effort basis and
|
|
is not expected to be as accurate as the reports themselves.
|
|
|
|
Here is an example of the report:
|
|
|
|
```
|
|
GS-PAUTH: signing oracle found in function function_name, basic block .LBB08, at address 102b8
|
|
The instruction is 000102b8: pacda x0, x1
|
|
The 1 instructions that write to the affected registers after any authentication are:
|
|
1. 000102b4: ldr x0, [x1]
|
|
This happens in the following basic block:
|
|
000102b4: ldr x0, [x1]
|
|
000102b8: pacda x0, x1
|
|
000102bc: ret
|
|
```
|
|
|
|
A similar report without the associated extra information looks like this:
|
|
|
|
```
|
|
GS-PAUTH: signing oracle found in function function_name, basic block .LBB016, at address 10384
|
|
The instruction is 00010384: pacda x0, x1
|
|
The 0 instructions that write to the affected registers after any authentication are:
|
|
```
|
|
|
|
## Pointer Authentication validator
|
|
|
|
Pointer Authentication analysis is able to search for a number of gadget kinds,
|
|
with the specific set depending on command line options:
|
|
* [`ptrauth-pac-ret`](#return-address-protection-ptrauth-pac-ret) -
|
|
non-protected return instruction
|
|
* [`ptrauth-tail-calls`](#return-address-protection-before-tail-call-ptrauth-tail-calls) -
|
|
performing a tail call with an untrusted value in the link register
|
|
* [`ptrauth-forward-cf`](#indirect-branch-call-target-protection-ptrauth-forward-cf) -
|
|
non-protected destination of branch or call instruction
|
|
* [`ptrauth-sign-oracles`](#signing-oracles-ptrauth-sign-oracles) -
|
|
signing of untrusted value (signing oracle)
|
|
* [`ptrauth-auth-oracles`](#authentication-oracles-ptrauth-auth-oracles) -
|
|
revealing the result of authentication without crashing the program on failure
|
|
(authentication oracle)
|
|
|
|
Validation is performed by `llvm-bolt-binary-analysis` on a per-function basis.
|
|
First, the register properties are computed by analyzing the function as a whole.
|
|
Then, the instructions are considered in isolation. For each kind of gadget,
|
|
the set of susceptible instructions is computed. The properties of input or
|
|
output registers of each such instruction are analyzed and reports are produced
|
|
for each detected gadget.
|
|
|
|
Each gadget kind that is searched for can be characterized by the combination of
|
|
* the set of instructions to analyze
|
|
* the properties of input or output operands to check
|
|
|
|
Currently, three properties can be computed for each register at any given
|
|
program point:
|
|
* **"trusted"** - the register is known not to be attacker-controlled, either because
|
|
it successfully passed authentication or because its value was materialized
|
|
using an instruction sequence that an attacker cannot tamper with
|
|
* **"safe-to-dereference"** (sometimes referred to as "s-t-d" below) -
|
|
a weaker property that the register can be controlled by an attacker to some
|
|
extent, but any memory access using a value crafted by an attacker is known
|
|
to result in an access to an unmapped memory ("segmentation fault").
|
|
This makes it possible for authentication instructions to return an invalid
|
|
address on failure as long as it is known to crash the program on accessing
|
|
memory, but may require extra care to be taken when implementing operations
|
|
like re-signing a pointer with a different signing schema without accessing
|
|
that address in-between. If any failed authentication instruction is
|
|
guaranteed to terminate the program abnormally, then "safe-to-dereference"
|
|
and "trusted" properties are equivalent.
|
|
* **"cannot escape unchecked"** - at every possible execution path after this point,
|
|
it is known to be impossible for an attacker to determine that the value is
|
|
a result of a failed authentication operation (for example, the register is
|
|
zeroed, or its value is checked to be valid, so that failure results in
|
|
immediate abnormal program termination).
|
|
|
|
Generally, BOLT strives to reconstruct the control-flow graph of each function,
|
|
which is important for dataflow analysis. However, when BOLT fails to recognize
|
|
some control flow in the particular function, that function ends up being
|
|
represented as a flat list of instructions - in such cases
|
|
`llvm-bolt-binary-analysis` computes register properties using a fallback
|
|
analysis implementation, which is less precise.
|
|
|
|
The below sub-sections describe the particular detectors. Please note that while
|
|
the descriptions refer to AArch64 for simplicity, the implementation of gadget
|
|
detectors in `llvm-bolt-binary-analysis` attempts to be target-neutral by
|
|
isolating AArch64 specifics in target-dependent hooks.
|
|
|
|
### Return address protection (`ptrauth-pac-ret`)
|
|
|
|
**Instructions:** Return instructions without built-in authentication:
|
|
either `ret` (implicit `x30` register) or `ret <reg>`, but not `retaa` and
|
|
similar instructions.
|
|
|
|
**Property:** The register holding the return address must be safe-to-dereference.
|
|
|
|
**Notes:** Cross-exception-level return instructions (`eret`) are not analyzed yet.
|
|
|
|
A report is generated for a return instruction whose destination is possibly
|
|
attacker-controlled.
|
|
|
|
**Examples:**
|
|
```asm
|
|
authenticated_return:
|
|
pacibsp
|
|
; ...
|
|
; ... some code here ...
|
|
; ...
|
|
retab ; Built-in authentication, thus out of scope.
|
|
|
|
good_leaf_function:
|
|
; x30 is implicitly safe-to-dereference (s-t-d) and trusted at function entry.
|
|
mov x0, #42
|
|
; x30 was not written to by this function, thus remains s-t-d.
|
|
ret
|
|
|
|
good_non_leaf_function:
|
|
pacibsp
|
|
|
|
; Spilling signed return address.
|
|
stp x29, x30, [sp, #-16]!
|
|
mov x29, sp
|
|
|
|
bl callee
|
|
|
|
; Re-loading signed return address.
|
|
; LDP writes to x30 and thus resets it to neither s-t-d nor trusted state.
|
|
ldp x29, x30, [sp], #16
|
|
|
|
; Checking that signature is valid.
|
|
; AUTIBSP sets "s-t-d" property of x30, but not "trusted" (unless FEAT_FPAC
|
|
; is known to be implemented).
|
|
autibsp
|
|
|
|
; x30 is s-t-d at this point.
|
|
ret
|
|
|
|
bad_spill:
|
|
; x30 is implicitly s-t-d at function entry.
|
|
stp x29, x30, [sp, #-16]!
|
|
mov x29, sp
|
|
|
|
bl callee ; Spilled x30 may have been overwritten on stack.
|
|
|
|
; Writing to x30 resets its s-t-d property.
|
|
ldp x29, x30, [sp], #16
|
|
; x30 is unsafe by the time it is used by ret, thus generating a report.
|
|
ret
|
|
|
|
bad_clobber:
|
|
pacibsp
|
|
; ...
|
|
; ... some code here ...
|
|
; ...
|
|
autibsp
|
|
mov x30, x1
|
|
; The value in LR is unsafe, even though there was autibsp above.
|
|
ret
|
|
```
|
|
|
|
### Return address protection before tail call (`ptrauth-tail-calls`)
|
|
|
|
**Instructions:** Branch instructions (both direct and indirect, regular or
|
|
with built-in authentication), classified as tail calls either by BOLT or by
|
|
PtrAuth gadget scanner's heuristic.
|
|
|
|
**Property:** link register (`x30` on AArch64) must be trusted.
|
|
|
|
**Notes:** Heuristics are involved to classify instructions either as a tail
|
|
call or as another kind of branch (such as jump table or computed goto).
|
|
|
|
A report is generated if a tail call is performed with an untrusted link register.
|
|
This basically means that the tail-called function would have the link register
|
|
untrusted on its entry (unlike the inherently correct address placed in the link
|
|
register by one of the `bl*` instructions when a non-tail call is performed).
|
|
|
|
```asm
|
|
non_protected_tail_call:
|
|
stp x29, x30, [sp, #-16]!
|
|
mov x29, sp
|
|
bl callee
|
|
ldp x29, x30, [sp], #16
|
|
; x30 is neither trusted nor safe-to-dereference at this point.
|
|
b tail_callee
|
|
|
|
non_checked_tail_call:
|
|
pacibsp
|
|
stp x29, x30, [sp, #-16]!
|
|
mov x29, sp
|
|
bl callee
|
|
ldp x29, x30, [sp], #16
|
|
autibsp
|
|
; x30 is safe-to-dereference, but not fully trusted at this point.
|
|
b tail_callee
|
|
|
|
tail_callee:
|
|
pacibsp
|
|
; ...
|
|
```
|
|
|
|
Even though `x30` is likely to be safe-to-dereference before exit from a function
|
|
(whether via return or tail call) in a consistently pac-ret-protected program,
|
|
with respect to this gadget kind it further must be fully "trusted".
|
|
This corresponds to the way tail calls are emitted when `--aarch64-authenticated-lr-check-method=`
|
|
option is specified with an argument other than `none`, or when such checks are
|
|
requested for the particular subtarget by default.
|
|
|
|
Properly mitigating this issue would usually require inserting an explicit
|
|
check after a regular authentication instruction, which may be either too
|
|
expensive (if a fully-generic XPAC-based sequence is being used) on one hand,
|
|
or not required at all (if `FEAT_FPAC` is known to be implemented) on the other hand.
|
|
|
|
### Indirect branch / call target protection (`ptrauth-forward-cf`)
|
|
|
|
**Instructions:** Indirect call and branch instructions without built-in
|
|
authentication: either `blr <reg>` or `br <reg>`, but not `blraa`, `braa`
|
|
and similar instructions.
|
|
|
|
**Property:** The call or branch target register must be safe-to-dereference.
|
|
|
|
A report is generated for an indirect branch or call instruction whose destination
|
|
is possibly attacker-controlled.
|
|
|
|
**Examples:**
|
|
|
|
```asm
|
|
direct_call:
|
|
; ...
|
|
bl callee ; Direct call, thus out of scope.
|
|
; ...
|
|
|
|
authenticated_call:
|
|
; ...
|
|
ldr x2, [x1]
|
|
blraa x2, x1 ; Built-in authentication, thus out of scope.
|
|
; ...
|
|
|
|
good_call:
|
|
; ...
|
|
ldr x2, [x1]
|
|
autia x2, x1
|
|
blr x2
|
|
; ...
|
|
|
|
bad_call:
|
|
; ...
|
|
ldr x2, [x1]
|
|
autia x2, x1
|
|
; Store unprotected address.
|
|
str x2, [x3]
|
|
; ...
|
|
; The callee address may have been overwritten in memory.
|
|
ldr x2, [x3]
|
|
blr x2
|
|
; ...
|
|
|
|
good_call_dataflow:
|
|
cbz x0, .L1
|
|
ldr x2, [x1]
|
|
autia x2, x1
|
|
b .L2
|
|
.L1:
|
|
adrp x2, callee
|
|
add x2, x2, :lo12:callee
|
|
.L2:
|
|
; Dataflow analysis can deduce that x2 is s-t-d on any possible execution
|
|
; path leading to the below "br x2" instruction.
|
|
br x2
|
|
|
|
bad_call_dataflow:
|
|
cbz x0, .L3
|
|
adrp x2, callee
|
|
add x2, x2, :lo12:callee
|
|
.L3:
|
|
; x2 is untrusted if x0 is 0.
|
|
br x2
|
|
|
|
```
|
|
|
|
### Signing oracles (`ptrauth-sign-oracles`)
|
|
|
|
**Instructions:** Address-signing instructions.
|
|
|
|
**Property:** The address being signed must be trusted.
|
|
|
|
Reports signing of untrusted values, as this could make arbitrary and possibly
|
|
attacker-controlled values indistinguishable from perfectly trusted and protected ones.
|
|
|
|
Note that in absence of `--auth-traps-on-failure` command line option, this detector
|
|
reports auth+sign pairs unless an explicit check is emitted to make sure the
|
|
authentication operation succeeded. This corresponds to the way LLVM emits
|
|
instructions on AArch64.
|
|
|
|
**Examples:**
|
|
|
|
```asm
|
|
good_sign_constant:
|
|
; ...
|
|
adrp x0, sym
|
|
add x0, x0, :lo12:sym
|
|
pacda x0, x1
|
|
; ...
|
|
|
|
good_resign:
|
|
; ...
|
|
autda x0, x1
|
|
; x0 is s-t-d here.
|
|
ldr x2, [x0]
|
|
; If we got here without crashing on the above ldr, x0 is fully trusted.
|
|
pacdb x0, x1
|
|
; ...
|
|
|
|
bad_resign_if_not_fpac:
|
|
; ...
|
|
autda x0, x1
|
|
; x0 is only s-t-d, but not trusted here, unless autda raises an error on failure.
|
|
pacdb x0, x1
|
|
; ...
|
|
|
|
very_bad_function:
|
|
pacda x0, x1
|
|
ret
|
|
```
|
|
|
|
### Authentication oracles (`ptrauth-auth-oracles`)
|
|
|
|
**Instructions:** Standalone authentication instructions: `autda`, `autdb`, etc.
|
|
(i.e. not those built into corresponding memory-accessing instructions, such as
|
|
`ldraa` or `blraa`).
|
|
|
|
**Property:** The **result** of authentication must be written to a register
|
|
that cannot escape unchecked.
|
|
|
|
Reports authentication instructions, whose result (success or failure) can be
|
|
observed by the attacker and used to guess the correct PAC field by trial-and-error.
|
|
|
|
The authentication oracles searched for by this detector are impossible if all
|
|
authentication instructions are known to generate an irrecoverable error on
|
|
failure. On AArch64 this is the case if CPU is known to implement `FEAT_FPAC`
|
|
(though recoverability depends on the OS: for example, on Linux such errors
|
|
result in signals that can be handled if configured accordingly).
|
|
This check is disabled by `--auth-traps-on-failure` command line option.
|
|
|
|
**Examples:**
|
|
|
|
```asm
|
|
; The descriptions assume FEAT_FPAC is not implemented.
|
|
|
|
good_auth_call:
|
|
paciasp
|
|
stp x29, x30, [sp, #-16]!
|
|
mov x29, sp
|
|
|
|
; The result of authentication is inevitably dereferenced by blr.
|
|
; If autia returns a non-canonical address due to incorrect signature,
|
|
; the program crashes when the resulting address is jumped-to by blr.
|
|
cbz x2, .L1
|
|
autia x0, x1
|
|
blr x0
|
|
|
|
.L1:
|
|
ldp x29, x30, [sp], #16
|
|
autiasp
|
|
ret
|
|
|
|
bad_auth_call:
|
|
paciasp
|
|
stp x29, x30, [sp, #-16]!
|
|
mov x29, sp
|
|
|
|
; x0 may be observed by the caller of bad_auth_call if x2 is 0.
|
|
autia x0, x1
|
|
cbz x2, .L2
|
|
blr x0
|
|
|
|
.L2:
|
|
ldp x29, x30, [sp], #16
|
|
autiasp
|
|
ret
|
|
|
|
bad_leaks_to_callee:
|
|
paciasp
|
|
stp x29, x30, [sp, #-16]!
|
|
mov x29, sp
|
|
|
|
ldr x20, [x0]
|
|
autda x20, x0
|
|
; The result of authentication is leaked to the called function.
|
|
bl callee
|
|
; The below ldr instruction would properly check x20 if placed above the call.
|
|
ldr x0, [x20]
|
|
|
|
ldp x29, x30, [sp], #16
|
|
autiasp
|
|
ret
|
|
```
|
|
|
|
### Known issues and missing features
|
|
|
|
#### Control-flow graph availability
|
|
|
|
The analysis quality is degraded if BOLT is unable to reconstruct control-flow
|
|
graph of the function correctly.
|
|
|
|
When reconstructing CFG for a particular function, it is possible that BOLT
|
|
finds a code pattern that it is unable to handle. In that case PtrAuth validator
|
|
processes a function containing a flat list of instructions (as opposed to
|
|
interlinked basic blocks) and the reports (if any) are printed without the
|
|
`, basic block <name>` part in the first line.
|
|
|
|
Furthermore, it is possible that CFG information is returned by BOLT for a
|
|
function, even though the graph is imprecise. Inaccurate CFG information may
|
|
result in false positives and false negatives, thus PtrAuth gadget scanner
|
|
produces warning messages (at most once per function) for non-entry basic blocks
|
|
without any predecessors in CFG: while unreachable basic blocks are technically
|
|
correct, truly unreachable blocks are unlikely to exist in an optimized code.
|
|
|
|
Known gadget scanner issues related to CFG reconstruction are tracked in
|
|
[#177761](https://github.com/llvm/llvm-project/issues/177761),
|
|
[#178058](https://github.com/llvm/llvm-project/issues/178058),
|
|
[#178232](https://github.com/llvm/llvm-project/issues/178232).
|
|
|
|
#### Last writing instructions
|
|
|
|
Some of the reports contain extra information along these lines
|
|
|
|
```
|
|
The 1 instructions that write to the affected registers after any authentication are:
|
|
1. 000102b4: ldr x0, [x1]
|
|
This happens in the following basic block:
|
|
000102b4: ldr x0, [x1]
|
|
000102b8: pacda x0, x1
|
|
000102bc: ret
|
|
```
|
|
|
|
This information is provided on a best-effort basis and is less reliable than
|
|
gadget reports themselves.
|
|
|
|
#### Feature: scan for unsafe computation of discriminators
|
|
|
|
On AArch64, signing and authentication operations are parameterized by the pair
|
|
of the key identifier and a 64-bit *discriminator* value. The key is one of
|
|
`IA`, `IB`, `DA`, or `DB`, and its identifier is directly encoded into the
|
|
instruction (the `GA` key differs significantly and is thus out of scope here).
|
|
The discriminator, on the other hand, is computed at run-time and is intended
|
|
to be [derived independently](https://clang.llvm.org/docs/PointerAuthentication.html#discriminators)
|
|
at the signing and authentication sites.
|
|
|
|
There is a common pattern on AArch64 to compute the discriminator as a blend
|
|
of an address and an integer modifier by inserting a compile-time constant
|
|
value into the top 16 bits of the storage address. It is not necessarily
|
|
possible to prevent an attacker from modifying the storage address, but the
|
|
insertion of 16-bit constant modifier can always be performed immediately before
|
|
the discriminator value is used by signing or authentication instruction.
|
|
|
|
While not as bad as signing an arbitrary untrusted pointer, spilling a
|
|
"ready-to-use" discriminator and then reloading a potentially modified value
|
|
later is something we would rather avoid. On the other hand, using an arbitrary
|
|
value as the discriminator should probably be allowed, making it hard to
|
|
distinguish the below patterns:
|
|
|
|
```asm
|
|
; Valid and not reported.
|
|
good_store_with_address_and_constant_diversity:
|
|
mov x16, x1
|
|
movk x16, #1234, lsl #48
|
|
pacda x0, x16
|
|
str x0, [x1]
|
|
ret
|
|
|
|
; Valid and not reported.
|
|
good_store_with_address_diversity:
|
|
pacda x0, x1
|
|
str x0, [x1]
|
|
ret
|
|
|
|
; Spilled discriminator. Not critically wrong, but could rather be avoided.
|
|
; Not reported, but probably should (false negative).
|
|
bad_spilling:
|
|
mov x16, x1
|
|
movk x16, #1234, lsl #48
|
|
|
|
; Spilling the discriminator to memory.
|
|
str x16, [x2]
|
|
; Reloaded value could have been modified by an attacker.
|
|
ldr x16, [x2]
|
|
|
|
pacda x0, x16
|
|
str x0, [x1]
|
|
ret
|
|
|
|
; Not reported (and should probably not).
|
|
better_spilling:
|
|
mov x16, x1
|
|
str x16, [x2]
|
|
; Reloaded value could have been modified by an attacker.
|
|
ldr x16, [x2]
|
|
|
|
movk x16, #1234, lsl #48
|
|
pacda x0, x16
|
|
str x0, [x1]
|
|
ret
|
|
```
|
|
|
|
#### Handling of constants
|
|
|
|
While (PC-relative) address constants are tracked as "trusted" register state
|
|
by `SrcSafetyAnalysis`, constant values are not generally accounted for.
|
|
|
|
This results in false-positives like reporting
|
|
|
|
```asm
|
|
; Let assume FEAT_FPAC is implemented.
|
|
autda x16, x22
|
|
mov x17, #0x128
|
|
add x16, x16, x17
|
|
pacda x16, x22
|
|
```
|
|
|
|
as a signing oracle, even though
|
|
|
|
```asm
|
|
; Let assume FEAT_FPAC is implemented.
|
|
autda x16, x22
|
|
add x16, x16, #8
|
|
pacda x16, x22
|
|
```
|
|
|
|
is not reported, because `add Xdst, Xsrc, #imm` is recognized as a safe address
|
|
computation.
|
|
|
|
As an example of false-negative, it is possible that an instruction like
|
|
`add x0, x0, #1` could be called in a loop with an attacker-controlled number
|
|
of iterations, making it technically possible for an attacker to replace a
|
|
valid pointer with a pointer to an arbitrary address.
|
|
|
|
#### Tail call detection
|
|
|
|
`ptrauth-tail-calls` detector uses a heuristic to guess which branch instructions
|
|
(both direct and indirect) correspond to performing tail calls, as opposed to
|
|
other kinds of control flow.
|
|
|
|
Most other parts of BOLT should not break code when rewriting. Unlike them,
|
|
this analyzer tries to keep reasonable balance between false positive reports
|
|
and missed issues. For this reason, it inspects some other branch instructions
|
|
in addition to those BOLT is sure about.
|
|
|
|
While it should provide reasonable balance between false positives and false
|
|
negatives on general code, this may not be the case for some specific patterns.
|
|
An example of a generally uncommon code pattern that is likely to yield false
|
|
positives is [labels-as-values GCC extension](https://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.html)
|
|
which is also supported by Clang.
|
|
|
|
#### Other known issues
|
|
|
|
* No lightweight variant of [`ptrauth-tail-calls`](#return-address-protection-before-tail-call-ptrauth-tail-calls),
|
|
see issue [#186204](https://github.com/llvm/llvm-project/issues/186204)
|
|
for the details.
|
|
* 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.
|
|
* Scanning of binaries compiled by Clang at `-Oz` optimization level produces a
|
|
lot of reports due to outlining. Many such reports could probably be considered
|
|
false negatives as long as an attacker is unable to call `OUTLINED_FUNCTION`s
|
|
as ROP gadgets in the first place.
|
|
* False positives are possible due to multi-instruction pointer-checking sequences
|
|
not being detected without CFG.
|
|
* While obviously "checking" the result of pointer authentication, store
|
|
instructions do not transition their address operand register from safe-to-
|
|
dereference to trusted state yet. This does not affect scanning regular code
|
|
hardened neither by `pac-ret`, nor by `arm64e` or `pauthtest`.
|
|
|
|
## How to add your own analysis
|
|
|
|
### Pointer Authentication validator
|
|
|
|
To implement the detection of a new gadget kind, add new
|
|
`shouldReport*Gadget` function to `bolt/lib/Passes/PAuthGadgetScanner.cpp` and
|
|
call it either from `FunctionAnalysisContext::findUnsafeUses` or
|
|
`FunctionAnalysisContext::findUnsafeDefs`.
|
|
|
|
To improve overall analysis quality by better computing register properties,
|
|
either modify one of `*SafetyAnalysis` classes in `PAuthGadgetScanner.cpp`
|
|
(if the improvement is target-neutral), or one of target-specific hooks in
|
|
the subclass of `MCPlusBuilder` corresponding to your target (if an analysis of
|
|
target-specific instruction patterns is to be improved).
|
|
|
|
To add support for a new target, if one eventually implements similar pointer
|
|
protection technique, implement PtrAuth-related hooks in the subclass of
|
|
`MCPlusBuilder` corresponding to your target. Ideally, no changes should be
|
|
needed in `PAuthGadgetScanner.cpp`, as it is intended to be reasonably target-
|
|
independent, though it is possible that some amount of further generalization
|
|
may be required.
|
|
|
|
### New types of analyses
|
|
|
|
_TODO: this section needs to be written. Ideally, we should have a simple
|
|
"example" or "template" analysis that can be the starting point for implementing
|
|
custom analyses_
|