[compiler-rt][aarch64] Fast unwinder does not strip PAC unless the sanitizer runtime itself was built with -mbranch-protection
- Dominant language
- LLVM
- Stars
- 40.5k
- Forks
- 18.7k
- PR merge metrics
- PR metrics pending
Description
`sanitizer_ptrauth.h` decides whether to strip a pointer authentication code from a return address based on whether **the sanitizer runtime itself** was compiled with `-mbranch-protection`:
https://github.com/llvm/llvm-project/blob/main/compiler-rt/lib/sanitizer_common/sanitizer_ptrauth.h
```c++
#if __has_feature(ptrauth_intrinsics)
# include
#elif defined(__ARM_FEATURE_PAC_DEFAULT) && !defined(__APPLE__)
// On the stack the link register is protected with Pointer
// Authentication Code when compiled with -mbranch-protection.
// Let's stripping the PAC unconditionally because xpaclri is in
// the NOP space so will do nothing when it is not enabled or not available.
# define ptrauth_strip(__value, __key) ...xpaclri...
#else
# define ptrauth_strip(__value, __key) __value
#endif
```
That is the wrong binary to test. The only consumer that matters is `StackTrace::UnwindFast`:
```c++
uhwptr pc1 = STRIP_PAC_PC((void *)frame[1]);
```
`frame[1]` is a return address spilled into a frame record by **the code being unwound**, not by the runtime. Whether it carries a PAC depends on how *that* code was built. The two are independent binaries and routinely disagree: a distro that builds userspace with `-mbranch-protection=standard` (Fedora does for aarch64 — `/usr/lib/rpm/redhat/rpmrc`) while compiler-rt is built from source with default flags gets a runtime whose strip is compiled out, unwinding an application whose frame records are all signed.
The result is that `fast_unwind_on_malloc=1` (the default) produces garbage backtraces for every allocation. Symptoms are the obvious ones: nonsense frames in ASan reports, and LSan suppressions that silently fail to match because the frames they match on never symbolize. The workaround is `fast_unwind_on_malloc=0`, which is very expensive — every allocation pays a DWARF unwind.
The header's own comment already states the intended behaviour: *"Let's stripping the PAC unconditionally because xpaclri is in the NOP space so will do nothing when it is not enabled or not available."* The `#elif` contradicts it. `XPACLRI` is `HINT #7`, a NOP on cores without FEAT_PAuth, and on an unsigned canonical address it is the identity, so there is nothing to gate.
### Reproducer
Compiling the header's only real use, with and without branch protection on the *runtime*:
```c++
typedef unsigned long uptr;
#include "sanitizer_common/sanitizer_ptrauth.h"
extern "C" uptr next_pc(uptr pc) { return STRIP_PAC_PC((void *)pc) + 4; }
```
```
$ clang++ --target=aarch64-unknown-linux-gnu -O2 -c -Icompiler-rt/lib pac.cpp
$ llvm-objdump -d pac.o
0000000000000000 :
0: 91001000 add x0, x0, #0x4
4: d65f03c0 ret
```
The strip is gone. With `-mbranch-protection=standard` added it appears:
```
0: d503233f paciasp
4: f81f0ffe str x30, [sp, #-0x10]!
8: aa0003fe mov x30, x0
c: d50320ff xpaclri
10: aa1e03e8 mov x8, x30
...
```
The first form is what ships in any compiler-rt built without branch protection; in such a build `libclang_rt.asan.a` contains no `xpaclri` in `UnwindFast` at all.
### Suggested fix
```diff
-#elif defined(__ARM_FEATURE_PAC_DEFAULT) && !defined(__APPLE__)
+#elif defined(__aarch64__) && !defined(__APPLE__)
```
which is what the comment says it is doing. Happy to send a PR.
Contributor guide
Research direction
Start with compiler-rt/lib/sanitizer_common/sanitizer_ptrauth.h and the StackTrace::UnwindFast use of STRIP_PAC_PC. Build the provided aarch64 reproducer with and without runtime branch protection, then inspect the generated assembly. Done means the fast unwinder strips PAC independently of the sanitizer runtime's build flags while preserving identity behavior for unsigned addresses.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 84/100