From 7866fb47993d4c82284241d19a5f24b628d5148b Mon Sep 17 00:00:00 2001 From: Rui Ueyama Date: Thu, 8 Oct 2026 22:23:36 +0900 Subject: [PATCH] Start the ARM64 PLT header with `bti c` regardless of BTI PLT entries jump to the PLT header with an indirect branch, so the header needs a landing pad if BTI is enabled. Like the x86-64 PLT header, which starts with endbr64 whether or not IBT is enabled, we now always write `bti c` to it. The header runs only when a symbol is resolved lazily, so the extra instruction doesn't matter. PLT entries are on the hot path and still get one only if BTI is enabled. --- elf/arch/arm64.rs | 22 +++++++++++----------- tests/elf/arch-aarch64-z-force-bti.sh | 5 ++++- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/elf/arch/arm64.rs b/elf/arch/arm64.rs index d9407f58c..08d7d692d 100644 --- a/elf/arch/arm64.rs +++ b/elf/arch/arm64.rs @@ -210,8 +210,13 @@ impl Target for Arm64Target { 32 + idx as u64 * Self::plt_entry_size(ctx) } + // PLT entries jump to the header with an indirect branch, so the header + // starts with a BTI landing pad. We write it even if BTI is disabled, in + // which case `bti c` is a no-op, because the header is executed only when + // a symbol is resolved lazily. fn write_plt_header(ctx: &Context, buf: &mut [u8]) { const INSN: [u32; 8] = [ + 0xd503_245f, // bti c 0xa9bf_7bf0, // stp x16, x30, [sp,#-16]! 0x9000_0010, // adrp x16, .got.plt[2] 0xf940_0211, // ldr x17, [x16, .got.plt[2]] @@ -219,20 +224,15 @@ impl Target for Arm64Target { 0xd61f_0220, // br x17 0xd420_7d00, // brk 0xd420_7d00, // brk - 0xd420_7d00, // brk ]; - // PLT entries jump to the header with an indirect branch. A landing - // pad, if any, replaces one of the trailing brks. - let off = write_landing_pad(buf, note_property::is_bti(ctx)); - let buf = &mut buf[off..32]; - for (loc, &v) in buf.chunks_exact_mut(4).zip(&INSN) { - write_ul32(loc, v); + for (i, &v) in INSN.iter().enumerate() { + write_ul32(&mut buf[i * 4..], v); } let gotplt = ctx.gotplt.shdr.sh_addr.get() + 16; - let plt = ctx.plt.hdr.shdr.sh_addr.get() + off as u64; - write_adrp(&mut buf[4..], page(gotplt).wrapping_sub(page(plt + 4))); - or_insn(&mut buf[8..], (bits(gotplt, 11, 3) << 10) as u32); - or_insn(&mut buf[12..], ((gotplt & 0xfff) << 10) as u32); + let plt = ctx.plt.hdr.shdr.sh_addr.get(); + write_adrp(&mut buf[8..], page(gotplt).wrapping_sub(page(plt + 8))); + or_insn(&mut buf[12..], (bits(gotplt, 11, 3) << 10) as u32); + or_insn(&mut buf[16..], ((gotplt & 0xfff) << 10) as u32); } // A PLT entry is 16 bytes long by default and 24 bytes long if BTI is diff --git a/tests/elf/arch-aarch64-z-force-bti.sh b/tests/elf/arch-aarch64-z-force-bti.sh index 0add3cf7e..229e1f930 100755 --- a/tests/elf/arch-aarch64-z-force-bti.sh +++ b/tests/elf/arch-aarch64-z-force-bti.sh @@ -14,7 +14,10 @@ EOF ./mold -shared -o $t/c.so $t/a.o $t/b.o readelf -n $t/c.so | not grep 'AArch64 feature' -$OBJDUMP -d -j .plt $t/c.so | grep -A1 '<_PROCEDURE_LINKAGE_TABLE_>:' | not grep -w bti +readelf --dynamic $t/c.so | not grep AARCH64_BTI_PLT + +# The PLT header starts with `bti c` even if BTI is disabled. +$OBJDUMP -d -j .plt $t/c.so | grep -A1 '<_PROCEDURE_LINKAGE_TABLE_>:' | grep -w bti ./mold -shared -o $t/c.so $t/a.o $t/b.o -z force-bti 2> $t/log readelf -n $t/c.so | grep 'AArch64 feature: BTI'