| # 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`](#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. | |
| (ptrauth-forward-cf)= | |
| ### 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_ |