Description
Summary
On macOS, Arm64, the combination of options -arch arm64e -mbranch-protection=standard
generates invalid code (redundant pointer authentication) leading to application crash on return.
Platform
Demonstrated on a Macbook with M1 chip, macOS 13.1, arm64e API enabled, Apple clang 14.0.0. This is the clang version coming with the latest "Command Line Tools for Xcode 14.2" from December 13, 2022.
$ sw_vers
ProductName: macOS
ProductVersion: 13.1
BuildVersion: 22C65
$
$ uname -a
Darwin mactest 22.2.0 Darwin Kernel Version 22.2.0: Fri Nov 11 02:04:44 PST 2022; root:xnu-8792.61.2~4/RELEASE_ARM64_T8103 arm64
$
$ nvram boot-args
boot-args -arm64e_preview_abi
$
$ clang --version
Apple clang version 14.0.0 (clang-1400.0.29.202)
Target: arm64-apple-darwin22.2.0
Thread model: posix
InstalledDir: /Library/Developer/CommandLineTools/usr/bin
$
Demonstration
This simple "hello world" code crashes with -arch arm64e -mbranch-protection=standard
. It does not crash without -mbranch-protection
or with -mbranch-protection=bti
only.
$ cat hi.c
#include <stdio.h>
int main(int argc, char* argv[])
{
printf("hi\n");
}
$
$ clang -O2 -march=armv8.5-a -arch arm64e hi.c -o hi
$ ./hi
hi
$ clang -O2 -march=armv8.5-a -arch arm64e hi.c -o hi -mbranch-protection=bti
$ ./hi
hi
$ clang -O2 -march=armv8.5-a -arch arm64e hi.c -o hi -mbranch-protection=standard
$ ./hi
hi
Segmentation fault: 11
$
Details
The combination -arch arm64e -mbranch-protection=standard
is fatal. The same problem is seen with -mbranch-protection=pac-ret
instead of standard
(the latter includes the former).
-mbranch-protection=pac-ret
or=standard
authenticates the caller's return address usingpacia x30, sp
, updating x30 with a pointer authentication code.-arch arm64e
authenticates the caller's return address usingpacibsp
(same aspacib x30, sp
). This second instruction trashes the PAC in x30, recomputing a PAC with key B.- The return sequence is
autibsp
andretaa
. Theautibsp
removes the PAC from x30. Whenretaa
authenticates x30, there is no longer any PAC, the authentication fails and the program crashes.
Solution: There must be only one authentication sequence. Using -mbranch-protection=pac-ret
or =standard
shall not add PACIA instructions when -arch arm64e
is specified since pointer authentication is already used. Alternatively, an error message may report the incompatible options. But no invalid code should be generated.
Generated code below:
$ clang -O2 -march=armv8.5-a -arch arm64e hi.c -mbranch-protection=standard -S -o -
.section __TEXT,__text,regular,pure_instructions
.build_version macos, 13, 0 sdk_version 13, 1
.ptrauth_abi_version 0
.globl _main ; -- Begin function main
.p2align 2
_main: ; @main
.cfi_startproc
; %bb.0:
bti c
pacia x30, sp <------------ generated by -mbranch-protection=standard
.cfi_negate_ra_state
pacibsp <------------ generated by -arch arm64e
stp x29, x30, [sp, #-16]! ; 16-byte Folded Spill
mov x29, sp
.cfi_def_cfa w29, 16
.cfi_offset w30, -8
.cfi_offset w29, -16
Lloh0:
adrp x0, l_str@PAGE
Lloh1:
add x0, x0, l_str@PAGEOFF
bl _puts
mov w0, #0
ldp x29, x30, [sp], #16 ; 16-byte Folded Reload
autibsp <------------ generated by -arch arm64e
retaa <------------ generated by -mbranch-protection=standard
.loh AdrpAdd Lloh0, Lloh1
.cfi_endproc
; -- End function
.section __TEXT,__cstring,cstring_literals
l_str: ; @str
.asciz "hi"
.subsections_via_symbols
$