|
|
Message-ID: <20251113200633.833127-2-bill.roberts@arm.com>
Date: Thu, 13 Nov 2025 13:44:26 -0600
From: Bill Roberts <bill.roberts@....com>
To: musl@...ts.openwall.com
Cc: Bill Roberts <bill.roberts@....com>
Subject: [PATCH 1/3] dso/aarch64: add PROT_BTI support for mem maps
This change adds support for setting PROT_BTI on executable mappings
when the corresponding ELF object includes the GNU_PROPERTY_AARCH64_FEATURE_1_BTI
note. This ensures correct Branch Target Identification (BTI) enforcement
as defined in the Arm Architecture Reference Manual (Arm ARM) and the
ELF for the Arm 64-bit Architecture (AArch64) ABI supplement.
When loading shared objects or executables, the dynamic loader now checks
for BTI-related GNU notes and applies the appropriate protection flags
via `mprotect` and `mmap` during mapping. This aligns musl’s behavior with
other modern loaders, improving security and correctness on systems with BTI
enabled hardware.
References:
- Arm Architecture Reference Manual for A-profile architecture (Arm ARM):
https://developer.arm.com/documentation/ddi0487/latest
- ELF for the Arm® 64-bit Architecture (AArch64) ABI supplement:
https://github.com/ARM-software/abi-aa/blob/main/aaelf64/aaelf64.rst
- Arm Community Blog – *Enabling Pointer Authentication and Branch Target Identification*:
1. https://community.arm.com/arm-community-blogs/b/architectures-and-processors-blog/posts/enabling-pac-and-bti-part1
2. https://community.arm.com/arm-community-blogs/b/architectures-and-processors-blog/posts/enabling-pac-and-bti-part2
3. https://community.arm.com/arm-community-blogs/b/architectures-and-processors-blog/posts/enabling-pac-and-bti-part3
Signed-off-by: Bill Roberts <bill.roberts@....com>
---
Makefile | 2 +-
configure | 1 +
include/elf.h | 2 +
ldso/dynlink.c | 50 ++++++++++++++---
src/ldso/aarch64/arch_ldso_hook.c | 90 +++++++++++++++++++++++++++++++
src/ldso/aarch64/arch_ldso_hook.h | 21 ++++++++
6 files changed, 158 insertions(+), 8 deletions(-)
create mode 100644 src/ldso/aarch64/arch_ldso_hook.c
create mode 100644 src/ldso/aarch64/arch_ldso_hook.h
diff --git a/Makefile b/Makefile
index 3ad88b35..a20bafaf 100644
--- a/Makefile
+++ b/Makefile
@@ -47,7 +47,7 @@ CFLAGS_AUTO = -Os -pipe
CFLAGS_C99FSE = -std=c99 -ffreestanding -nostdinc
CFLAGS_ALL = $(CFLAGS_C99FSE)
-CFLAGS_ALL += -D_XOPEN_SOURCE=700 -I$(srcdir)/arch/$(ARCH) -I$(srcdir)/arch/generic -Iobj/src/internal -I$(srcdir)/src/include -I$(srcdir)/src/internal -Iobj/include -I$(srcdir)/include
+CFLAGS_ALL += -D_XOPEN_SOURCE=700 -I$(srcdir)/arch/$(ARCH) -I$(srcdir)/arch/generic -Iobj/src/internal -I$(srcdir)/src/include -I$(srcdir)/src/internal -Iobj/include -I$(srcdir)/include -I$(srcdir)/src/ldso/$(ARCH)
CFLAGS_ALL += $(CPPFLAGS) $(CFLAGS_AUTO) $(CFLAGS)
LDFLAGS_ALL = $(LDFLAGS_AUTO) $(LDFLAGS)
diff --git a/configure b/configure
index bc9fbe48..3809f24c 100755
--- a/configure
+++ b/configure
@@ -671,6 +671,7 @@ fi
if test "$ARCH" = "aarch64" ; then
trycppif __AARCH64EB__ "$t" && SUBARCH=${SUBARCH}_be
+CFLAGS_AUTO="${CFLAGS_AUTO} -DARCH_SUPPORTS_DL_ADD_PROTECTIONS"
fi
if test "$ARCH" = "loongarch64" ; then
diff --git a/include/elf.h b/include/elf.h
index d6ae539a..e40b815d 100644
--- a/include/elf.h
+++ b/include/elf.h
@@ -1110,7 +1110,9 @@ typedef struct {
#define NT_GNU_GOLD_VERSION 4
#define NT_GNU_PROPERTY_TYPE_0 5
+#define GNU_PROPERTY_AARCH64_FEATURE_1_AND 0xc0000000
+#define GNU_PROPERTY_AARCH64_FEATURE_1_BTI (1U << 0)
typedef struct {
Elf32_Xword m_value;
diff --git a/ldso/dynlink.c b/ldso/dynlink.c
index 715948f4..d204f2d5 100644
--- a/ldso/dynlink.c
+++ b/ldso/dynlink.c
@@ -682,6 +682,21 @@ static void unmap_library(struct dso *dso)
}
}
+#ifdef ARCH_SUPPORTS_DL_ADD_PROTECTIONS
+#include <arch_ldso_hook.h>
+#else
+static inline unsigned dl_add_protections(const Elf64_Phdr *ph,
+ const Elf64_Ehdr *eh, int fd, unsigned *prot, unsigned *mask)
+{
+ (void)ph;
+ (void)eh;
+ (void)fd;
+ (void)prot;
+ (void)mask;
+ return 0;
+}
+#endif
+
static void *map_library(int fd, struct dso *dso)
{
Ehdr buf[(896+sizeof(Ehdr))/sizeof(Ehdr)];
@@ -693,7 +708,7 @@ static void *map_library(int fd, struct dso *dso)
off_t off_start;
Ehdr *eh;
Phdr *ph, *ph0;
- unsigned prot;
+ unsigned prot, arch_prot, arch_mask;
unsigned char *map=MAP_FAILED, *base;
size_t dyn=0;
size_t tls_image=0;
@@ -720,6 +735,16 @@ static void *map_library(int fd, struct dso *dso)
} else {
ph = ph0 = (void *)((char *)buf + eh->e_phoff);
}
+
+ /*
+ * some architectures may have additional protection flags embedded in the
+ * ELF section somewhere. Start with those and OR in the extras. Do this
+ * before we need to map any sections.
+ */
+ if(dl_add_protections(ph, eh, fd, &arch_prot, &arch_mask))
+ goto error;
+
+
for (i=eh->e_phnum; i; i--, ph=(void *)((char *)ph+eh->e_phentsize)) {
if (ph->p_type == PT_DYNAMIC) {
dyn = ph->p_vaddr;
@@ -746,6 +771,7 @@ static void *map_library(int fd, struct dso *dso)
prot = (((ph->p_flags&PF_R) ? PROT_READ : 0) |
((ph->p_flags&PF_W) ? PROT_WRITE: 0) |
((ph->p_flags&PF_X) ? PROT_EXEC : 0));
+ prot |= prot&arch_mask ? arch_prot : 0;
}
if (ph->p_vaddr+ph->p_memsz > addr_max) {
addr_max = ph->p_vaddr+ph->p_memsz;
@@ -762,6 +788,7 @@ static void *map_library(int fd, struct dso *dso)
prot = (((ph->p_flags&PF_R) ? PROT_READ : 0) |
((ph->p_flags&PF_W) ? PROT_WRITE: 0) |
((ph->p_flags&PF_X) ? PROT_EXEC : 0));
+ prot |= prot&arch_mask ? arch_prot : 0;
map = mmap(0, ph->p_memsz + (ph->p_vaddr & PAGE_SIZE-1),
prot, MAP_PRIVATE,
fd, ph->p_offset & -PAGE_SIZE);
@@ -780,6 +807,7 @@ static void *map_library(int fd, struct dso *dso)
size_t pgbrk = brk + PAGE_SIZE-1 & -PAGE_SIZE;
size_t pgend = brk + ph->p_memsz - ph->p_filesz
+ PAGE_SIZE-1 & -PAGE_SIZE;
+ /* arch_prot has already been added */
if (pgend > pgbrk && mmap_fixed(map+pgbrk,
pgend-pgbrk, prot,
MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS,
@@ -801,11 +829,16 @@ static void *map_library(int fd, struct dso *dso)
* the length of the file. This is okay because we will not
* use the invalid part; we just need to reserve the right
* amount of virtual address space to map over later. */
- map = DL_NOMMU_SUPPORT
- ? mmap((void *)addr_min, map_len, PROT_READ|PROT_WRITE|PROT_EXEC,
- MAP_PRIVATE|MAP_ANONYMOUS, -1, 0)
- : mmap((void *)addr_min, map_len, prot,
- MAP_PRIVATE, fd, off_start);
+ if (map == DL_NOMMU_SUPPORT) {
+ prot = PROT_READ|PROT_WRITE|PROT_EXEC;
+ prot |= prot&arch_mask ? arch_prot : 0;
+ map = mmap((void *)addr_min, map_len, prot,
+ MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
+ } else {
+ prot = prot & arch_mask ? prot | arch_prot : prot;
+ map = mmap((void *)addr_min, map_len, prot,
+ MAP_PRIVATE, fd, off_start);
+ }
if (map==MAP_FAILED) goto error;
dso->map = map;
dso->map_len = map_len;
@@ -835,6 +868,7 @@ static void *map_library(int fd, struct dso *dso)
prot = (((ph->p_flags&PF_R) ? PROT_READ : 0) |
((ph->p_flags&PF_W) ? PROT_WRITE: 0) |
((ph->p_flags&PF_X) ? PROT_EXEC : 0));
+ prot |= prot&arch_mask ? arch_prot : 0;
/* Reuse the existing mapping for the lowest-address LOAD */
if ((ph->p_vaddr & -PAGE_SIZE) != addr_min || DL_NOMMU_SUPPORT)
if (mmap_fixed(base+this_min, this_max-this_min, prot, MAP_PRIVATE|MAP_FIXED, fd, off_start) == MAP_FAILED)
@@ -849,7 +883,9 @@ static void *map_library(int fd, struct dso *dso)
}
for (i=0; ((size_t *)(base+dyn))[i]; i+=2)
if (((size_t *)(base+dyn))[i]==DT_TEXTREL) {
- if (mprotect(map, map_len, PROT_READ|PROT_WRITE|PROT_EXEC)
+ prot = PROT_READ|PROT_WRITE|PROT_EXEC;
+ prot |= prot&arch_mask ? arch_prot : 0;
+ if (mprotect(map, map_len, prot)
&& errno != ENOSYS)
goto error;
break;
diff --git a/src/ldso/aarch64/arch_ldso_hook.c b/src/ldso/aarch64/arch_ldso_hook.c
new file mode 100644
index 00000000..9a755707
--- /dev/null
+++ b/src/ldso/aarch64/arch_ldso_hook.c
@@ -0,0 +1,90 @@
+#include <elf.h>
+#include <sys/mman.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdio.h>
+
+unsigned dl_add_protections(const Elf64_Phdr *ph, const Elf64_Ehdr *eh, int fd,
+ unsigned *prot, unsigned *mask) {
+ size_t i;
+ char *buf = NULL;
+ unsigned rc = 1;
+
+ /*
+ * Unfortunately, we need to loop through the ELF header, as we need
+ * to get the PROT flags before we map anything in and they could be
+ * anywhere.
+ */
+ for (i=eh->e_phnum; i; i--, ph=(void *)((char *)ph+eh->e_phentsize)) {
+
+ /* skip non note sections */
+ if (ph->p_type != PT_NOTE)
+ continue;
+
+ buf = malloc(ph->p_filesz);
+ if (!buf)
+ return rc;
+
+ if (pread(fd, buf, ph->p_filesz, ph->p_offset) != ph->p_filesz)
+ goto error;
+
+ const char *p = buf;
+ const char *end = buf + ph->p_filesz;
+
+ /* for each ELF note */
+ while (p + sizeof(Elf64_Nhdr) <= end) {
+
+ const Elf64_Nhdr *nh = (const Elf64_Nhdr*) p;
+ p += sizeof(Elf64_Nhdr);
+
+ const char *name = (const char*) p;
+ p += (nh->n_namesz + 3) & ~3; // 4-byte align
+
+ const char *desc = p;
+ p += (nh->n_descsz + 3) & ~3;
+
+ if (p > end)
+ break;
+
+ /* We're only interested in GNU notes and property type 0*/
+ if (nh->n_namesz != 4|| memcmp(name, "GNU", 4)
+ || nh->n_type != NT_GNU_PROPERTY_TYPE_0) {
+ goto out;
+ }
+
+ const char *dp = desc;
+ const char *dend = desc + nh->n_descsz;
+
+ /* for each property in the property list, kind of like TLV record in series */
+ while (dp + 2 * sizeof(uint32_t) <= dend) {
+ uint32_t pr_type = *(const uint32_t*) dp;
+ dp += sizeof(uint32_t);
+ uint32_t pr_datasz = *(const uint32_t*) dp;
+ dp += sizeof(uint32_t);
+
+ if (dp + pr_datasz > dend)
+ break;
+
+ if (pr_type == GNU_PROPERTY_AARCH64_FEATURE_1_AND
+ && pr_datasz >= 4) {
+ uint32_t features = *(const uint32_t*) dp;
+
+ if (features & GNU_PROPERTY_AARCH64_FEATURE_1_BTI) {
+ (*prot) |= PROT_BTI;
+ goto out;
+ }
+ }
+
+ dp += (pr_datasz + 3) & ~3; // align to 4 bytes
+ }
+ }
+ }
+out:
+ /* Only add flags if the mapping will be executable */
+ *mask = PROT_EXEC;
+ rc = 0;
+error:
+ free(buf);
+ return rc;
+}
diff --git a/src/ldso/aarch64/arch_ldso_hook.h b/src/ldso/aarch64/arch_ldso_hook.h
new file mode 100644
index 00000000..11581712
--- /dev/null
+++ b/src/ldso/aarch64/arch_ldso_hook.h
@@ -0,0 +1,21 @@
+#ifndef LDSO_AARCH64_ARCH_LDSO_HOOK_H_
+#define LDSO_AARCH64_ARCH_LDSO_HOOK_H_
+
+/* Optional arch hook for the dynamic loader.
+ * If ARCH_SUPPORTS_DL_ADD_PROTECTIONS is defined, this function may
+ * examine ELF headers and file sections to determine whether to add
+ * arch-specific mmap/mprotect flags.
+ *
+ * Arguments:
+ * ph, eh (in) – program and ELF headers for the object being loaded.
+ * fd (in) – file descriptor for the mapped object.
+ * prot (out) – protection flags to OR into existing flags.
+ * mask (out) – bitmask; new flags in *prot are ORed into existing_flags
+ * only if (existing_flags & *mask).
+ *
+ * Returns 0 on sucess.
+ */
+unsigned dl_add_protections(const Elf64_Phdr *ph,
+ const Elf64_Ehdr *eh, int fd, unsigned *prot, unsigned *mask);
+
+#endif
--
2.51.0
Powered by blists - more mailing lists
Confused about mailing lists and their use? Read about mailing lists on Wikipedia and check out these guidelines on proper formatting of your messages.