arm64 Tagged-Address ABI: Pointer consistency across kernel-user boundaries
0 reputation · 31 May 2025, 05:13 UTC
0 reputation · 31 May 2025, 05:13 UTC
On AArch64 Linux systems with 48-bit virtual addresses, the CONFIG_ARM64_TAGGED_ADDR_ABI configuration and prctl(PR_SET_TAGGED_ADDR_CTRL) allow tasks to utilize the top byte of pointers for metadata. While this enables efficient memory tagging in user-space, the behavior of these pointers when crossing the kernel boundary remains a critical compatibility concern.
There is significant uncertainty regarding how the kernel handles pointers with set tag bits when they are passed as arguments to system calls, returned as values, or delivered via signal contexts and ptrace. Depending on the kernel version and specific subsystem, these tagged pointers may be preserved, stripped of their metadata, or rejected as invalid addresses.
This inconsistency creates a challenge for developers building libraries that rely on tagged pointers but must interact with various kernel versions or different VA_BITS configurations (48 vs 52).
CONFIG_ARM64_TAGGED_ADDR_ABI states?35275 reputation · 31 May 2025, 09:09 UTC
When CONFIG_ARM64_TAGGED_ADDR_ABI is enabled, the kernel accepts tagged pointers in system-call arguments by masking off the top byte during address validation (via untagged_addr()), but it does not preserve tags on pointers returned to user-space (e.g., mmap, mremap return zero-tagged addresses). If the config is disabled or the task has not enabled tagged addressing via prctl(PR_SET_TAGGED_ADDR_CTRL, PR_TAGGED_ADDR_ENABLE), passing a tagged pointer typically yields EFAULT. The documented ABI guarantees only acceptance of tagged inputs — not round-trip tag survival.
untagged_addr() before validating the address. The syscall succeeds as if the pointer were untagged.EFAULT.EFAULT regardless of prctl state.mmap, mremap, shmat, etc.) have the top byte cleared to zero. Libraries must not assume tags survive a kernel round-trip.siginfo_t (e.g., si_ptr for SIGIO) are likewise untagged.si_addr, faulting PC/SP in ucontext_t) preserves the original top-byte tags because the kernel copies raw registers.ptrace (PTRACE_GETREGSET/SETREGSET, PTRACE_PEEKUSER/POKEUSER) exposes and accepts raw tagged register values — tags are not stripped.With CONFIG_ARM64_VA_BITS=52, the tag still occupies bits 56–63; the same untagged_addr() masking applies. Hardware TBI (TCR_EL1.TBI0/1) must be enabled per-task (the kernel does this when tagged-address control is active).
grep -E 'CONFIG_ARM64_TAGGED_ADDR_ABI|CONFIG_ARM64_VA_BITS' /boot/config-$(uname -r)
#include <sys/mman.h>
#include <stdint.h>
#include <stdio.h>
#include <sys/prctl.h>
#include <linux/prctl.h>
int main() {
prctl(PR_SET_TAGGED_ADDR_CTRL, PR_TAGGED_ADDR_ENABLE, 0, 0, 0);
void *p = mmap(NULL, 4096, PROT_READ|PROT_WRITE,
MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
uintptr_t tagged = (uintptr_t)p | 0x4200000000000000ULL;
void *ret = mmap((void*)tagged, 4096, PROT_READ|PROT_WRITE,
MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED_NOREPLACE, -1, 0);
printf("mmap returned: %p (tag: 0x%02lx)\n", ret, ((uintptr_t)ret >> 56) & 0xFF);
return 0;
}
si_addr tag preservation:
#include <signal.h>
#include <stdio.h>
#include <stdint.h>
#include <sys/prctl.h>
#include <linux/prctl.h>
void handler(int sig, siginfo_t *si, void *uc) {
printf("si_addr tag: 0x%02lx\n", ((uintptr_t)si->si_addr >> 56) & 0xFF);
_exit(0);
}
int main() {
prctl(PR_SET_TAGGED_ADDR_CTRL, PR_TAGGED_ADDR_ENABLE, 0, 0, 0);
struct sigaction sa = {.sa_sigaction = handler, .sa_flags = SA_SIGINFO};
sigaction(SIGSEGV, &sa, NULL);
uintptr_t tagged = 0x4200000000001000ULL;
*(volatile char*)tagged = 0; // fault with tagged address
return 0;
}
What is the exact kernel version (uname -r) and CONFIG_ARM64_VA_BITS value (48 or 52) on your target deployment? This determines whether the tagged-address ABI is present and which TBI register bits apply.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.