Deciding Whether to Enable ARM BTI for AArch64 Userspace Builds
BTI constrains indirect branches on ARMv8.5-A, but toolchain and kernel support must be verified before enabling it in C/C++ builds.
17 Nov 2025, 08:42 UTC

Start with the failure mode, not the feature
An attacker who can redirect an indirect branch—through a corrupted function pointer, a vtable, or a code-reuse gadget—can execute existing code without injecting shellcode. On AArch64 systems implementing ARMv8.5-A, Branch Target Identification (BTI) is an architectural feature that requires indirect branches to land on special BTI landing-pad instructions. The intent is to shrink the set of valid indirect-branch targets.
The useful takeaway: BTI is a build-time hardening option, not a memory-safety fix. It constrains where indirect branches can land, but it does not stop buffer overflows, use-after-free, or data-only attacks. Whether you should enable it depends on your toolchain, your target kernel, and whether every native library in the process is rebuilt consistently.
Build configuration: flags and consistency
For GCC and Clang targeting AArch64, BTI is requested with:
gcc -mbranch-protection=bti -Wl,--bti your-app.c -o your-app
Run this on your build host as a normal user. The compiler flag marks eligible object code; the linker flag records the BTI GNU property in the ELF binary. Both are generally needed for userspace enforcement on Linux.
BTI marking is per object file and per shared library. If your executable is built with BTI but loads a prebuilt native library containing unmarked indirect branches, enforcement can be inconsistent or fail at runtime. Rebuild dependent native libraries with the same flags, or audit which libraries remain unmarked.
Toolchain support is not universal. Older GCC, Clang, or binutils releases may reject -mbranch-protection=bti or --bti. Check your compiler version before adopting the flag in a build system.
Worked example: verify the build artifact
Create a minimal C file, for example bti-check.c:
#include <stdio.h>
int add(int a, int b) {
return a + b;
}
int main(void) {
int (*fn)(int, int) = add;
printf("%d\n", fn(2, 3));
return 0;
}
Compile and inspect it:
gcc -mbranch-protection=bti -Wl,--bti bti-check.c -o bti-check
readelf -n bti-check
If the toolchain accepts the flags, the build succeeds. The readelf -n output displays ELF notes; you are looking for confirmation that the AArch64 BTI GNU property is present. Do not assume enforcement from the CPU name alone. If the compiler rejects the flag, your toolchain lacks BTI support for this release.
Required permissions: normal user for compilation and readelf. Risk: adding these flags changes code layout and can expose latent incompatibilities in prebuilt libraries.
Runtime verification on the target
Build support is only half the decision. The target kernel, loader, and C library must also expose and enforce BTI. You can check hardware capability at runtime with getauxval(AT_HWCAP2), testing the BTI bit defined by your target's libc and kernel headers. Macro names and bit positions are version-sensitive, so confirm them against the headers installed on the target image rather than copying them from another distribution.
A practical runtime check is to run the same binary with BTI enabled and disabled on the target kernel. If the BTI-enabled build crashes with SIGILL or SIGSEGV at an indirect branch while the disabled build runs normally, the target is likely missing BTI support, loader opt-in, or has unmarked code in the process.
Trade-offs and limitations
| Factor | Effect |
|---|---|
| Attack scope | Constrains indirect-branch targets only; no protection for direct branches, data flows, or memory corruption. |
| Library consistency | Unmarked native libraries weaken or break enforcement; all native code in the process should be rebuilt. |
| Code layout | BTI landing pads can change binary size and branch alignment. |
| Target dependency | Requires ARMv8.5-A or later silicon plus kernel/loader support; not detectable from uname -m alone. |
Pair BTI with pointer authentication, stack protectors, and memory-safety practices for broader coverage. BTI does not authenticate return addresses.
Actionable closing
- Confirm your compiler accepts
-mbranch-protection=btiand your linker accepts-Wl,--bti. - Build a test object and run
readelf -nto check for the BTI GNU property. - Verify
AT_HWCAP2exposure and BTI enforcement on the exact target image. - Rebuild native dependencies consistently or document which libraries remain unmarked.
- Enable BTI conditionally in your build system so it can be disabled for older targets.
Rollback is straightforward: remove the two flags and rebuild. Treat the decision as target-specific, not architecture-wide.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.