ARM64 Pointer Authentication: Protecting Pointers on aarch64
ARM64 Pointer Authentication (PAC) signs pointers to detect tampering before use. Learn how to enable PAC in GCC/Clang, verify hardware support, and measure its impact on aarch64 systems. The guide includes code, verification steps, and trade‑off analysis.
18 Aug 2025, 15:15 UTC

The Pointer‑Based Exploit Problem
Modern memory‑corruption attacks often hinge on an attacker rewriting a pointer—be it a return address, a function pointer, or a stack pointer—to redirect execution to malicious code. On x86 this is mitigated by techniques such as Control‑Flow Integrity and Address Space Layout Randomization, but on ARM64 the hardware itself can help prevent the pointer from being silently altered. That hardware feature is Pointer Authentication (PAC), introduced in the ARMv8.3‑A architecture.
How PAC Protects Pointers
PAC works by attaching a cryptographic signature to a pointer value. The signature is generated by a small hash function that incorporates a secret key stored in two per‑CPU registers (K0 and K1). When the processor loads a signed pointer, it automatically verifies the signature. If the pointer has been tampered with—by an attacker or a buggy program—the verification fails and the load is aborted, usually raising a fault.
There are two key aspects:
- Signing happens at compile time or runtime. The compiler can automatically sign return addresses, function pointers, and stack pointers using the appropriate key.
- Verification is performed by the CPU on every
LDRinstruction that loads a signed pointer. The instruction checks the signature before the pointer is used.
Using PAC in C/C++ Code
To enable PAC you need a compiler that understands the PAC extensions and a linker that can emit the special .ptrauth section. GCC and Clang provide the -fpie and -fstack-protector-strong flags to request PAC support. The compiler will then sign pointers automatically. You can also manually annotate code with __attribute__((ptrauth)) if you need fine‑grained control.
Below is a minimal example that demonstrates signing a function pointer, storing it, and calling it later. The code will compile with gcc -O2 -fpie -fstack-protector-strong on an ARMv8.3‑A target.
#include <stdio.h>
#include <stdlib.h>
// Define a function pointer type
typedef void (*func_ptr)(void);
// A simple function to be called
void hello(void) {
puts("Hello, PAC!\n");
}
int main(void) {
// Sign the function pointer automatically
func_ptr fp = hello;
// Store the signed pointer somewhere
void *storage = &fp;
// Retrieve and call the function
func_ptr *retrieved = (func_ptr *)storage;
(*retrieved)();
return 0;
}
When compiled with the PAC flags, the compiler will sign fp and the load that fetches it will be verified. If an attacker rewrites the memory at storage to point elsewhere, the CPU will raise a fault instead of executing the tainted code.
Verifying PAC Support on Your System
-
Check CPU features:
cat /proc/cpuinfo | grep -i pacaLook for the string
pacaorPACin theFeaturesline. If absent, your CPU does not support PAC. -
Compile a test program with PAC flags and inspect the binary:
gcc -O2 -fpie -fstack-protector-strong test.c -o test objdump -h test | grep ptrauthA non‑empty
.ptrauthsection indicates the compiler emitted signed pointers. -
Run the program and watch the kernel log:
./test sudo dmesg | tail -n 20You should see messages like
PAC: enabledorPAC: disabled due to missing feature. -
Measure performance impact:
perf stat -e cycles,instructions ./test > /dev/null # Repeat without -fpie -fstack-protector-strong and compare the cycle count.Typical slowdown is 1–3 % for workloads that use many pointers.
Trade‑offs and Limitations
- Hardware requirement: PAC is only available on ARMv8.3‑A or newer. Older CPUs will crash or silently ignore the feature.
- Compiler maturity: GCC and Clang support PAC, but older toolchains may not generate correct signatures. Always test on the target architecture.
- Key management: The K0/K1 keys are per‑CPU and per‑privilege level. If an application runs on multiple CPUs without proper key rotation, a pointer signed on one CPU may fail verification on another.
- Performance overhead: While small, the extra verification adds latency. For real‑time or high‑throughput systems, benchmark the impact before deployment.
- Interop with non‑PAC code: Pointers signed by PAC cannot be safely used by legacy libraries that expect unsigned pointers. Use explicit casts only when you know the target code is PAC‑aware.
Actionable Closing
If you control the compiler toolchain and run on an ARMv8.3‑A CPU, enabling PAC is a low‑cost way to harden against pointer‑based exploits. Add -fpie -fstack-protector-strong to your build flags, verify the .ptrauth section, and benchmark the impact. For maximum security, consider marking critical function pointers with __attribute__((ptrauth)) and rotating the K0/K1 keys at process start. Remember to test on the target device; without a PAC‑capable CPU the flags will have no effect and may even cause crashes.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.