How do you enable the Arm Pointer Authentication Code (PAC) on macOS?
I have a MacBook Air with an Apple M1 chip. The CPU implements the Arm architecture version v8.5-A, which includes the Pointer Authentication Code (PAC) instructions. This feature is typically used to prevent malware code injection through ROP chains, typically exploiting a buffer overflow on the stack.
I try to demonstrate the behavior of some PAC instructions through simple code.
On the native macOS, the authentication instructions (PACIA, PACDA, etc) seem to have no effect, as if the feature was not implemented in the CPU. This could be possible since most Arm features are optional. However, after installing a Linux virtual machine on the same MacBook, the same PAC instructions work inside the Linux VM. On the same physical CPU, which consequently supports the PAC feature.
So, there must be some way to disable the behavior of PAC on a process basis: not active in native macOS applications, active in applications running in the Linux VM.
How would you enable PAC on macOS?
The sample code pacia.c
below
- displays an instruction address,
- adds a PAC using PACIA instruction and displays it
- "authenticate" it (restore its original value if the PAC is correct) using AUTIA instruction and displays it.
We expect that the second address has its MSB part altered by the PAC. We expect that the third address is identical to the first one.
#include <stdio.h>
#include <inttypes.h>
// noinline for easier inspection of generated code in main
__attribute__((noinline)) void report(uint64_t value)
{
printf("6" PRIX64 "\n", value);
}
int main(int argc, char* argv[])
{
uint64_t data = (uint64_t)(&&lab);
uint64_t modifier = 2;
lab:
report(data);
asm("pacia %[reg], %[mod]" : [reg] " r" (data) : [mod] "r" (modifier) : );
report(data);
asm("autia %[reg], %[mod]" : [reg] " r" (data) : [mod] "r" (modifier) : );
report( data);
}
Compilation:
cc -O2 -march=armv8.5-a pacia.c -o pacia
On the host system, macOS 13.1, the PACIA instruction does not modify the address to authenticate.
$ ./pacia
00000001028B3F50
00000001028B3F50 <-- not modified, no PAC
00000001028B3F50
$ ./pacia
000000010080FF50
000000010080FF50
000000010080FF50
$ ./pacia
0000000102A7FF50
0000000102A7FF50
0000000102A7FF50
$
On the Ubuntu 22.10 virtual machine, the MSB part of the address is updated with a PAC by PACIA and correctly removed by AUTIA.
$ ./pacia
0000AAAACF3D0680
0043AAAACF3D0680 <-- 0043 PAC added
0000AAAACF3D0680 <-- PAC removed, address restored
$ ./pacia
0000AAAAD7CF0680
0023AAAAD7CF0680
0000AAAAD7CF0680
$ ./pacia
0000AAAAAAE00680
0036AAAAAAE00680
0000AAAAAAE00680
Just to make sure, I inspected the generated code on macOS. The PACIA instruction is actually used.
cc -O2 -march=armv8.5-a pacia.c -S -o pacia.s
Generated code of main()
on macOS with clang 14.0.0:
_main: ; @main
.cfi_startproc
;