Skip to content

Commit b6166c8

Browse files
kovdan01eleviantatrosinenko
committed
aarch64: initial pauth support
1. Support PAuth dynamic relocs: - R_AARCH64_AUTH_ABS64; - R_AARCH64_AUTH_RELATIVE (including relr); - R_AARCH64_JUMP_SLOT (sign slot contents if DT_AARCH64_PAC_PLT dynamic tag is present); - R_AARCH64_AUTH_GLOB_DAT; - R_AARCH64_AUTH_TLSDESC. 2. Support signed function pointers in init/fini arrays (with optional address discrimination enabled). 3. Check PAuth core info compatibility for DSOs in the process. TODO: 1. Support function pointer type discrimination. This should not be enabled under normal conditions though, and pauthtest ABI in LLVM intentionally does not include that. 2. Do not store unsigned LR in memory in raw assembly code: see #3. 3. Support non-null `__ehdr_start`. See in-code comment in `__dls2` function in src/dynlink.c for details. 4. Enhance test coverage: current proof-of-concept is good enough to run llvm-test-suite, but we lack test coverage of some parts of musl. Co-authored-by: Evgeny Leviant <[email protected]> Co-authored-by: Anatoly Trosinenko <[email protected]>
1 parent 0784374 commit b6166c8

File tree

10 files changed

+443
-6
lines changed

10 files changed

+443
-6
lines changed

arch/aarch64/reloc.h

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,39 @@
1818
#define REL_DTPMOD R_AARCH64_TLS_DTPMOD64
1919
#define REL_DTPOFF R_AARCH64_TLS_DTPREL64
2020
#define REL_TPOFF R_AARCH64_TLS_TPREL64
21+
22+
#ifndef __has_feature
23+
#define __has_feature(x) 0
24+
#endif
25+
26+
#if __has_feature(ptrauth_elf_got)
27+
#define R_AARCH64_AUTH_TLSDESC 0x413
28+
#define REL_TLSDESC R_AARCH64_AUTH_TLSDESC
29+
#else
2130
#define REL_TLSDESC R_AARCH64_TLSDESC
31+
#endif
2232

2333
#define CRTJMP(pc,sp) __asm__ __volatile__( \
2434
"mov sp,%1 ; br %0" : : "r"(pc), "r"(sp) : "memory" )
35+
36+
#if __has_feature(ptrauth_intrinsics)
37+
38+
#include <stdint.h>
39+
40+
#define TARGET_RELOCATE(dso, type, reladdr, sym, addend, is_phase_2, dyn, error_sym) \
41+
do_target_reloc(dso, type, reladdr, sym, addend, is_phase_2, dyn, error_sym)
42+
#define DO_TARGET_RELR(dso, dyn) do_pauth_relr(dso, dyn)
43+
44+
int do_target_reloc(int type, uint64_t* reladdr, uint64_t base, uint64_t symval,
45+
uint64_t addend, int is_phase_2, uint64_t* dyn, uint64_t error_sym);
46+
47+
void do_pauth_relr(uint64_t base, uint64_t* dyn);
48+
49+
#define GETFUNCSYM(fp, sym, got) do { \
50+
hidden void sym(); \
51+
*(fp) = sym; } while(0)
52+
53+
#define FPTR_CAST(fty, p) \
54+
((fty)__builtin_ptrauth_sign_unauthenticated((void*)(p), 0, 0))
55+
56+
#endif

crt/aarch64/crti.s

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@
22
.global _init
33
.type _init,%function
44
_init:
5+
paciasp
56
stp x29,x30,[sp,-16]!
67
mov x29,sp
78

89
.section .fini
910
.global _fini
1011
.type _fini,%function
1112
_fini:
13+
paciasp
1214
stp x29,x30,[sp,-16]!
1315
mov x29,sp

crt/aarch64/crtn.s

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
.section .init
22
ldp x29,x30,[sp],#16
3+
autiasp
34
ret
45

56
.section .fini
67
ldp x29,x30,[sp],#16
8+
autiasp
79
ret

ldso/dynlink.c

Lines changed: 179 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#define _GNU_SOURCE
22
#define SYSCALL_NO_TLS 1
3+
#include <inttypes.h>
34
#include <stdlib.h>
45
#include <stdarg.h>
56
#include <stddef.h>
@@ -19,6 +20,9 @@
1920
#include <dlfcn.h>
2021
#include <semaphore.h>
2122
#include <sys/membarrier.h>
23+
#if __has_feature(ptrauth_intrinsics)
24+
#include <ptrauth.h>
25+
#endif
2226
#include "pthread_impl.h"
2327
#include "fork_impl.h"
2428
#include "dynlink.h"
@@ -45,6 +49,18 @@ static void (*error)(const char *, ...) = error_noop;
4549
#define container_of(p,t,m) ((t*)((char *)(p)-offsetof(t,m)))
4650
#define countof(a) ((sizeof (a))/(sizeof (a)[0]))
4751

52+
#ifndef TARGET_RELOCATE
53+
#define TARGET_RELOCATE(...) 0
54+
#endif
55+
56+
#ifndef DO_TARGET_RELR
57+
#define DO_TARGET_RELR(...)
58+
#endif
59+
60+
#ifndef FPTR_CAST
61+
#define FPTR_CAST(fty, p) ((fty)(p))
62+
#endif
63+
4864
struct debug {
4965
int ver;
5066
void *head;
@@ -111,6 +127,11 @@ struct dso {
111127
size_t *got;
112128
} *funcdescs;
113129
size_t *got;
130+
#ifdef __aarch64__
131+
/* PAuth core info as defined in PAUTHABIELF64:
132+
* https://github.com/ARM-software/abi-aa/blob/2025Q1/pauthabielf64/pauthabielf64.rst#core-information */
133+
size_t* pauth;
134+
#endif
114135
char buf[];
115136
};
116137

@@ -471,6 +492,9 @@ static void do_relocs(struct dso *dso, size_t *rel, size_t rel_size, size_t stri
471492
case REL_GOT:
472493
case REL_PLT:
473494
*reloc_addr = sym_val + addend;
495+
/* If AArch64 PAC is enabled and DT_AARCH64_PAC_PLT is present, sign the contents of R_AARCH64_JUMP_SLOT.
496+
* Otherwise, do nothing. */
497+
TARGET_RELOCATE(type, reloc_addr, (size_t)base, sym_val, addend, head == &ldso, dso->dynv, (uint64_t)error);
474498
break;
475499
case REL_USYMBOLIC:
476500
memcpy(reloc_addr, &(size_t){sym_val + addend}, sizeof(size_t));
@@ -518,6 +542,15 @@ static void do_relocs(struct dso *dso, size_t *rel, size_t rel_size, size_t stri
518542
#endif
519543
case REL_TLSDESC:
520544
if (stride<3) addend = reloc_addr[!TLSDESC_BACKWARDS];
545+
#ifdef __aarch64__
546+
/* TODO: Submit implementation of undefined weak TLS symbols support to
547+
* mainline musl when it's implemented for other architectures.
548+
* The patch is work-in-progress. */
549+
if (sym && sym->st_info>>4 == STB_WEAK && sym->st_shndx == SHN_UNDEF) {
550+
reloc_addr[0] = (size_t)__tlsdesc_undef_weak;
551+
reloc_addr[1] = 0;
552+
} else
553+
#endif
521554
if (def.dso->tls_id > static_tls_cnt) {
522555
struct td_index *new = malloc(sizeof *new);
523556
if (!new) {
@@ -533,7 +566,11 @@ static void do_relocs(struct dso *dso, size_t *rel, size_t rel_size, size_t stri
533566
reloc_addr[0] = (size_t)__tlsdesc_dynamic;
534567
reloc_addr[1] = (size_t)new;
535568
} else {
569+
#if __has_feature(ptrauth_intrinsics) && !__has_feature(ptrauth_elf_got)
570+
reloc_addr[0] = (size_t)ptrauth_strip(&__tlsdesc_static, 0);
571+
#else
536572
reloc_addr[0] = (size_t)__tlsdesc_static;
573+
#endif
537574
#ifdef TLS_ABOVE_TP
538575
reloc_addr[1] = tls_val + def.dso->tls.offset
539576
+ TPOFF_K + addend;
@@ -549,8 +586,18 @@ static void do_relocs(struct dso *dso, size_t *rel, size_t rel_size, size_t stri
549586
reloc_addr[0] = reloc_addr[1];
550587
reloc_addr[1] = tmp;
551588
}
589+
#if __has_feature(ptrauth_elf_got)
590+
/* FIXME: actually, signing scheme is written in-place in relocation slot, and we should read and use that.
591+
* However, the scheme is known (IA key + addr div for function and DA key + addr div for data).
592+
* So, we just hard-code that. See also:
593+
* https://github.com/ARM-software/abi-aa/blob/2025Q1/pauthabielf64/pauthabielf64.rst#default-signing-schema */
594+
reloc_addr[0] = (size_t)(ptrauth_auth_and_resign((void*)(reloc_addr[0]), 0, 0, 0, (size_t)(reloc_addr)));
595+
reloc_addr[1] = (size_t)(ptrauth_sign_unauthenticated((void*)(reloc_addr[1]), 2, (size_t)(reloc_addr) + 8));
596+
#endif
552597
break;
553598
default:
599+
if (TARGET_RELOCATE(type, reloc_addr, (size_t)base, sym_val, addend, head == &ldso, dso->dynv, (uint64_t)error))
600+
break;
554601
error("Error relocating %s: unsupported relocation type %d",
555602
dso->name, type);
556603
if (runtime) longjmp(*rtld_fail, 1);
@@ -684,6 +731,84 @@ static void unmap_library(struct dso *dso)
684731
}
685732
}
686733

734+
#ifdef __aarch64__
735+
736+
/* See https://github.com/ARM-software/abi-aa/blob/2025Q1/pauthabielf64/pauthabielf64.rst#elf-marking */
737+
#define GNU_PROPERTY_AARCH64_FEATURE_PAUTH 0xc0000001
738+
739+
static uint32_t align8(uint32_t val) {
740+
if (val % 8 == 0)
741+
return val;
742+
return val + 8 - (val % 8);
743+
}
744+
745+
static void get_pauth_core_info(struct dso *dso) {
746+
for (int i = 0; i < dso->phnum; ++i) {
747+
Phdr *ph = &dso->phdr[i];
748+
749+
/* Minimal GNU property section containing PAuth core info has 40 bytes size. */
750+
if (!(ph->p_type == PT_NOTE && ph->p_memsz >= 40))
751+
continue;
752+
753+
uint32_t *note = laddr(dso, ph->p_vaddr);
754+
uint32_t *note_arr_end = (uint32_t*)((uintptr_t)note + ph->p_memsz);
755+
756+
for (; note != note_arr_end;
757+
/* We can hardcode 8-byte alignment since this code runs only on AArch64. */
758+
note = (uint32_t*)((uintptr_t)note + 4 + 4 + align8(4 + note[0]) + align8(note[1]))) {
759+
/* Note segment is ill-formed: last note information entry exceeds the right segment boundary. */
760+
if (note > note_arr_end) return;
761+
762+
if (!(note[0] == 4 && note[2] == NT_GNU_PROPERTY_TYPE_0 && strncmp((char*)&note[3], "GNU", 4) == 0))
763+
continue;
764+
765+
uint32_t *prop = &note[4];
766+
uint32_t *prop_arr_end = (uint32_t*)((uintptr_t)prop + note[1]);
767+
for (; prop != prop_arr_end;
768+
/* We can hardcode 8-byte alignment since this code runs only on AArch64. */
769+
prop = (uint32_t*)((uintptr_t)prop + 4 + 4 + align8(prop[1]))) {
770+
/* GNU property array is ill-formed: its last element end exceeds the right array boundary. */
771+
if (prop > prop_arr_end) return;
772+
773+
if (prop[0] != GNU_PROPERTY_AARCH64_FEATURE_PAUTH) continue;
774+
775+
/* PAuth GNU property must have exactly 16 bytes length:
776+
* 8 bytes for platform and 8 bytes for version value. */
777+
if (prop[1] != 16) return;
778+
779+
/* We do not expect multiple PAuth GNU properties. */
780+
if (dso->pauth != 0) return;
781+
782+
dso->pauth = (size_t*)&prop[2];
783+
}
784+
}
785+
}
786+
}
787+
788+
static void print_pauth_core_info(size_t *pauth, const char *name) {
789+
if (pauth == 0) {
790+
dprintf(2, "%s: no PAuth core info\n", name);
791+
return;
792+
}
793+
dprintf(2, "%s: (platform: 0x%" PRIx64 "; version: 0x%" PRIx64 ")\n", name, pauth[0], pauth[1]);
794+
}
795+
796+
static int check_pauth_core_info_compatibility(size_t *pauth1, const char *name1, size_t *pauth2, const char *name2) {
797+
if (pauth1 == pauth2)
798+
return 1;
799+
800+
if (pauth1 == 0 || pauth2 == 0 || pauth1[0] != pauth2[0] || pauth1[1] != pauth2[1]) {
801+
dprintf(2, "incompatible PAuth core info between %s and %s\n", name1, name2);
802+
print_pauth_core_info(pauth1, name1);
803+
print_pauth_core_info(pauth2, name2);
804+
return 0;
805+
}
806+
807+
return 1;
808+
}
809+
810+
#endif
811+
687812
static void *map_library(int fd, struct dso *dso)
688813
{
689814
Ehdr buf[(896+sizeof(Ehdr))/sizeof(Ehdr)];
@@ -860,6 +985,9 @@ static void *map_library(int fd, struct dso *dso)
860985
dso->base = base;
861986
dso->dynv = laddr(dso, dyn);
862987
if (dso->tls.size) dso->tls.image = laddr(dso, tls_image);
988+
#ifdef __aarch64__
989+
get_pauth_core_info(dso);
990+
#endif
863991
free(allocated_buf);
864992
return map;
865993
noexec:
@@ -1184,6 +1312,11 @@ static struct dso *load_library(const char *name, struct dso *needed_by)
11841312
close(fd);
11851313
if (!map) return 0;
11861314

1315+
#ifdef __aarch64__
1316+
if (!check_pauth_core_info_compatibility(head->pauth, head->name, temp_dso.pauth, name))
1317+
return 0;
1318+
#endif
1319+
11871320
/* Avoid the danger of getting two versions of libc mapped into the
11881321
* same process when an absolute pathname was used. The symbols
11891322
* checked are chosen to catch both musl and glibc, and to avoid
@@ -1421,6 +1554,9 @@ static void reloc_all(struct dso *p)
14211554
do_relocs(p, laddr(p, dyn[DT_RELA]), dyn[DT_RELASZ], 3);
14221555
if (!DL_FDPIC)
14231556
do_relr_relocs(p, laddr(p, dyn[DT_RELR]), dyn[DT_RELRSZ]);
1557+
if (p != &ldso) {
1558+
DO_TARGET_RELR((uint64_t)p->base, p->dynv);
1559+
}
14241560

14251561
if (head != &ldso && p->relro_start != p->relro_end) {
14261562
long ret = __syscall(SYS_mprotect, laddr(p, p->relro_start),
@@ -1464,6 +1600,9 @@ static void kernel_mapped_dso(struct dso *p)
14641600
p->map = p->base + min_addr;
14651601
p->map_len = max_addr - min_addr;
14661602
p->kernel_mapped = 1;
1603+
#ifdef __aarch64__
1604+
get_pauth_core_info(p);
1605+
#endif
14671606
}
14681607

14691608
void __libc_exit_fini()
@@ -1487,7 +1626,20 @@ void __libc_exit_fini()
14871626
if (dyn[0] & (1<<DT_FINI_ARRAY)) {
14881627
size_t n = dyn[DT_FINI_ARRAYSZ]/sizeof(size_t);
14891628
size_t *fn = (size_t *)laddr(p, dyn[DT_FINI_ARRAY])+n;
1490-
while (n--) ((void (*)(void))*--fn)();
1629+
#if __has_feature(ptrauth_init_fini)
1630+
while (n--) {
1631+
ptrauth_auth_function(
1632+
(void (*)(void))*--fn,
1633+
ptrauth_key_asia,
1634+
#if __has_feature(ptrauth_init_fini_address_discrimination)
1635+
ptrauth_blend_discriminator(fn, __ptrauth_init_fini_discriminator))();
1636+
#else
1637+
__ptrauth_init_fini_discriminator)();
1638+
#endif
1639+
}
1640+
#else
1641+
while (n--) FPTR_CAST(void (*)(void), (void*)*(--fn))();
1642+
#endif
14911643
}
14921644
#ifndef NO_LEGACY_INITFINI
14931645
if ((dyn[0] & (1<<DT_FINI)) && dyn[DT_FINI])
@@ -1605,7 +1757,21 @@ static void do_init_fini(struct dso **queue)
16051757
if (dyn[0] & (1<<DT_INIT_ARRAY)) {
16061758
size_t n = dyn[DT_INIT_ARRAYSZ]/sizeof(size_t);
16071759
size_t *fn = laddr(p, dyn[DT_INIT_ARRAY]);
1608-
while (n--) ((void (*)(void))*fn++)();
1760+
#if __has_feature(ptrauth_init_fini)
1761+
while (n--) {
1762+
ptrauth_auth_function(
1763+
(void (*)(void))*fn,
1764+
ptrauth_key_asia,
1765+
#if __has_feature(ptrauth_init_fini_address_discrimination)
1766+
ptrauth_blend_discriminator(fn, __ptrauth_init_fini_discriminator))();
1767+
#else
1768+
__ptrauth_init_fini_discriminator)();
1769+
#endif
1770+
++fn;
1771+
}
1772+
#else
1773+
while (n--) FPTR_CAST(void (*)(void), (void*)*fn++)();
1774+
#endif
16091775
}
16101776

16111777
pthread_mutex_lock(&init_fini_lock);
@@ -1727,7 +1893,16 @@ hidden void __dls2(unsigned char *base, size_t *sp)
17271893
} else {
17281894
ldso.base = base;
17291895
}
1896+
#if __has_feature(ptrauth_elf_got)
1897+
/* TODO: support non-null __ehdr_start. Since at this point relocations
1898+
* are not resolved yet, the contents of the GOT slot contains signing schema
1899+
* so the value is treated as non-null even when it is actually null.
1900+
* For undefined weak symbols with non-null values, implicit authentication
1901+
* sequence is emitted on access attempt, and this authentication obviously fails. */
1902+
Ehdr *ehdr = (void *)ldso.base;
1903+
#else
17301904
Ehdr *ehdr = __ehdr_start ? (void *)__ehdr_start : (void *)ldso.base;
1905+
#endif
17311906
ldso.name = ldso.shortname = "libc.so";
17321907
ldso.phnum = ehdr->e_phnum;
17331908
ldso.phdr = laddr(&ldso, ehdr->e_phoff);
@@ -1764,7 +1939,7 @@ hidden void __dls2(unsigned char *base, size_t *sp)
17641939
* load across the above relocation processing. */
17651940
struct symdef dls2b_def = find_sym(&ldso, "__dls2b", 0);
17661941
if (DL_FDPIC) ((stage3_func)&ldso.funcdescs[dls2b_def.sym-ldso.syms])(sp, auxv);
1767-
else ((stage3_func)laddr(&ldso, dls2b_def.sym->st_value))(sp, auxv);
1942+
else FPTR_CAST(stage3_func, laddr(&ldso, dls2b_def.sym->st_value))(sp, auxv);
17681943
}
17691944

17701945
/* Stage 2b sets up a valid thread pointer, which requires relocations
@@ -1788,7 +1963,7 @@ void __dls2b(size_t *sp, size_t *auxv)
17881963

17891964
struct symdef dls3_def = find_sym(&ldso, "__dls3", 0);
17901965
if (DL_FDPIC) ((stage3_func)&ldso.funcdescs[dls3_def.sym-ldso.syms])(sp, auxv);
1791-
else ((stage3_func)laddr(&ldso, dls3_def.sym->st_value))(sp, auxv);
1966+
else FPTR_CAST(stage3_func, laddr(&ldso, dls3_def.sym->st_value))(sp, auxv);
17921967
}
17931968

17941969
/* Stage 3 of the dynamic linker is called with the dynamic linker/libc

src/internal/dynlink.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ hidden void __dl_seterr(const char *, ...);
111111
hidden int __dl_invalid_handle(void *);
112112
hidden void __dl_vseterr(const char *, va_list);
113113

114-
hidden ptrdiff_t __tlsdesc_static(), __tlsdesc_dynamic();
114+
hidden ptrdiff_t __tlsdesc_static(), __tlsdesc_dynamic(), __tlsdesc_undef_weak();
115115

116116
hidden extern int __malloc_replaced;
117117
hidden extern int __aligned_alloc_replaced;

0 commit comments

Comments
 (0)