assemblyarm64pacbti

How do I need to change my code to make it compatible with PAC/BTI?


I have written some arm64 assembly code following the AAPCS64 calling convention.

Now I would like to integrate this code into a C/C++ project built with PAC and BTI enabled. I first noticed trouble when the linker told me that I was missing the GNU_PROPERTY_AARCH64_FEATURE_1_BTI property:

ld: error: md5block_arm64.o: -z bti-report: file does not have GNU_PROPERTY_AARCH64_FEATURE_1_BTI property

While it would be trivial to just set this property, I am aware that I likely need to change parts of my code to make them PAC/BTI aware. What are the changes needed for this? I am interested in a general answer that can be used as a guide to adapt any code, hence the lack of code to be adapted in this question.


Solution

  • BTI is pretty simple, and doesn't depend on software ABI (beyond BTI being enabled in the first place). In the current version (K.a) of the manual, the behaviour is documented in section "D8.4.5.3 PSTATE.BTYPE". In essence:

    So in order to be compatible with BTI code, you have to:


    PAC is an entirely different beast. That depends on the exact ABI of everything in your stack - gnu vs musl, C vs C++, __attribute__ specifiers in header files, ...

    The AAPCS64 specification does not define a pointer authentication ABI at all. PAUTHELF64 has some definitions for how to encode things in the ELF file format, but crucially it doesn't define how those things are to be chosen.

    Basically any code pointer synthesis, any indirect branch, and potentially even memory loads might need PAC instructions - entirely implementation-defined.

    Now, lucky for you, -mbranch-protection=standard seems to use PAC just barely at all. From the documentation of the flag:

    -mbranch-protection=none|standard|pac-ret[+leaf+b-key]|bti

    Select the branch protection features to use. none is the default and turns off all types of branch protection. standard turns on all types of branch protection features. If a feature has additional tuning options, then standard sets it to its standard level. pac-ret[+leaf] turns on return address signing to its standard level: signing functions that save the return address to memory (non-leaf functions will practically always do this) using the a-key. The optional argument leaf can be used to extend the signing to include leaf functions. The optional argument b-key can be used to sign the functions with the B-key instead of the A-key. bti turns on branch target identification mechanism.

    Basically this only uses PAC to protect stack frames, which does not affect the ABI between different compilation units at all. If you want to uphold the security properties of the option, then all of your functions that spill x30 will need a paciasp before the spill and an autiasp between the reload and the ret. (If your code does not need to be able to run on hardware without PAC support, you can combine the autiasp+ret into a single retaa).