[compiler-rt] [compiler-rt][sanitizer] Strip PAC from return addresses unconditionally on aarch64 (PR #218259)

via llvm-commits llvm-commits at lists.llvm.org
Sun Aug 23 09:17:01 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-compiler-rt-sanitizer

Author: Avi Kivity (avikivity)

<details>
<summary>Changes</summary>

Fixes #<!-- -->218255.

`sanitizer_ptrauth.h` gates `STRIP_PAC_PC` on `__ARM_FEATURE_PAC_DEFAULT`, i.e. on whether the sanitizer runtime itself was compiled with `-mbranch-protection`. Its only real consumer 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, so whether it carries a PAC is a property of *that* code, not of the runtime. The two are separate binaries and routinely disagree — a distro that builds userspace with `-mbranch-protection=standard` (Fedora does for aarch64) while compiler-rt is built from source with default flags gets a runtime with the strip compiled out, unwinding an application whose frame records are all signed. The fast unwinder then records garbage for every allocation: nonsense frames in reports, and LSan suppressions that silently never match.

The header's own comment already describes 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"* — so this just makes the code agree with it, and rewrites the comment to say why the condition cannot be tested for here.

`XPACLRI` is `HINT #<!-- -->7`: a NOP on cores without FEAT_PAuth, and the identity on an unsigned canonical address, so it is safe to run unconditionally.

### Effect

Compiling the header's only real use, cross-compiled with no `-mbranch-protection`:

```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; }
```

Before:

```
0000000000000000 <next_pc>:
       0: 91001000      add     x0, x0, #<!-- -->0x4
       4: d65f03c0      ret
```

After:

```
0000000000000000 <next_pc>:
       0: f81f0ffe      str     x30, [sp, #-0x10]!
       4: aa0003fe      mov     x30, x0
       8: d50320ff      xpaclri
       c: aa1e03e8      mov     x8, x30
      10: aa1f03fe      mov     x30, xzr
      14: 91001100      add     x0, x8, #<!-- -->0x4
      18: f84107fe      ldr     x30, [sp], #<!-- -->0x10
      1c: d65f03c0      ret
```

Apple targets are unaffected: `__has_feature(ptrauth_intrinsics)` still takes precedence, and the `!defined(__APPLE__)` guard is kept.

No test — reproducing the failure needs an aarch64 host with FEAT_PAuth running a PAC-built process under ASan, which the sanitizer_common test suite has no way to require. The change is visible in codegen as shown above.


---
Full diff: https://github.com/llvm/llvm-project/pull/218259.diff


1 Files Affected:

- (modified) compiler-rt/lib/sanitizer_common/sanitizer_ptrauth.h (+8-5) 


``````````diff
diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_ptrauth.h b/compiler-rt/lib/sanitizer_common/sanitizer_ptrauth.h
index 265a9925a15a0..ea16a032047ab 100644
--- a/compiler-rt/lib/sanitizer_common/sanitizer_ptrauth.h
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_ptrauth.h
@@ -11,11 +11,14 @@
 
 #if __has_feature(ptrauth_intrinsics)
 #  include <ptrauth.h>
-#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.
+#elif defined(__aarch64__) && !defined(__APPLE__)
+// On the stack the link register is protected with a Pointer Authentication
+// Code when the code that spilled it was compiled with -mbranch-protection.
+// That is a property of the code being unwound, not of this runtime, so it
+// cannot be tested for here (__ARM_FEATURE_PAC_DEFAULT would describe the
+// wrong binary). Strip unconditionally instead: xpaclri is in the NOP space,
+// so it does nothing where pointer authentication is not enabled or not
+// available, and it is the identity on an unsigned canonical address.
 #  define ptrauth_strip(__value, __key) \
     ({                                  \
       __typeof(__value) ret;            \

``````````

</details>


https://github.com/llvm/llvm-project/pull/218259


More information about the llvm-commits mailing list