Implementing Pointer Authentication (PAC) for Return Address Protection on ARM64
Learn how to implement ARM64 Pointer Authentication (PAC) to mitigate ROP attacks by signing return addresses using GCC/Clang and ARMv8.3-A hardware.
19 Feb 2026, 11:30 UTC

The Problem: Return-Oriented Programming (ROP)
In standard ARM64 execution, return addresses are stored on the stack in plain text. If a buffer overflow occurs, an attacker can overwrite the return address to redirect execution to a malicious gadget, a technique known as Return-Oriented Programming (ROP). While stack canaries provide some detection, they do not prevent the overwrite itself.
The Takeaway: Pointer Authentication (PAC), introduced in ARMv8.3-A, cryptographically signs pointers using a secret key and a context value. If an attacker modifies a signed return address, the authentication check fails during the function return, triggering a fault before the hijacked address is ever executed.
Prerequisites
- Hardware: An ARMv8.3-A (or newer) CPU. You can verify support by checking the
ID_AA64PFR0_EL1register; the API field must indicate PAC implementation. - Kernel: A Linux kernel compiled with
CONFIG_ARM64_PTR_AUTH=y. The kernel is responsible for managing the PAC keys for each process. - Toolchain: GCC 8+ or Clang 6+ targeting the
aarch64architecture.
Implementation Procedure
To protect return addresses, you must instruct the compiler to emit signing instructions in the function prologue and authentication instructions in the epilogue.
1. Compile with Branch Protection
Use the -mbranch-protection flag during compilation. For return address signing, use the pac-ret option. Adding +leaf ensures that even functions that do not call other functions are protected.
# Run this on your ARM64 build host
aarch64-linux-gnu-gcc -O2 -mbranch-protection=pac-ret+leaf -o secure_app main.c
2. Linker Requirements
The linker must mark the binary as PAC-enabled so the kernel knows how to handle the process. This is typically handled automatically by modern GCC/Clang versions when the branch protection flag is used, adding the GNU_PROPERTY_AARCH64_FEATURE_1_AND note to the ELF header.
Verification and Diagnostics
Because PAC operates at the instruction level, you cannot verify it simply by running the program. You must inspect the binary and the hardware state.
Binary Inspection
First, verify that the ELF binary contains the required GNU property note. Run this on the target system or build host:
readelf -l ./secure_app | grep GNU_PROPERTY
Look for GNU_PROPERTY_AARCH64_FEATURE_1_AND (value 0xc0000002). If this is missing, the kernel may not initialize the PAC keys for the process.
Next, verify that the compiler actually emitted the PAC instructions. Use objdump to search for paciasp (sign) and autiasp (authenticate) instructions:
aarch64-linux-gnu-objdump -d ./secure_app | grep -E 'paciasp|autiasp'
Runtime Validation
To confirm the protection is active, create a test case that deliberately corrupts a return address on the stack.
- Without PAC: The program will attempt to jump to the corrupted address, likely resulting in a
SIGSEGV(Segmentation Fault) at the invalid address. - With PAC: The
autiaspinstruction will detect the signature mismatch and corrupt the pointer further, leading to an immediateSIGILL(Illegal Instruction) orSIGSEGVexactly at the point of authentication, preventing the jump.
Comparison: PAC vs. Stack Canaries
| Feature | Stack Canaries | Pointer Authentication (PAC) |
|---|---|---|
| Mechanism | Value check before return | Cryptographic signature of the pointer |
| Detection Point | End of function | Point of pointer dereference/return |
| Overhead | Negligible | Small (2-5% code size increase) |
| Bypass Method | Canary leak/overwrite | Key leakage or signing oracle |
Limitations and Recovery
Hardware Compatibility: If you deploy a PAC-enabled binary to an ARMv8.0 or v8.1 processor, the CPU will encounter the paciasp instruction and immediately trigger an SIGILL because the instruction is unrecognized.
Performance: While minimal, the extra instructions in every function prologue and epilogue can impact tight loops. If performance is critical in a specific hot-path, you can disable PAC for a specific function using __attribute__((no_instrument_function)) or similar compiler-specific pragmas depending on the toolchain version.
Rollback Procedure
- Recompile the application with
-mbranch-protection=none. - Clean the build artifacts to ensure no cached object files contain PAC instructions.
- Redeploy the binary.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.