[PATCH v8 08/20] aarch64: Add GCS support for makecontext
Adhemerval Zanella Netto
adhemerval.zanella@linaro.org
Wed Jan 15 14:02:00 GMT 2025
On 14/01/25 13:03, Yury Khrustalev wrote:
> From: Szabolcs Nagy <szabolcs.nagy@arm.com>
>
> Changed the makecontext logic: previously the first setcontext jumped
> straight to the user callback function and the return address is set
> to __startcontext. This does not work when GCS is enabled as the
> integrity of the return address is protected, so instead the context
> is setup such that setcontext jumps to __startcontext which calls the
> user callback (passed in x20).
>
> The map_shadow_stack syscall is used to allocate a suitably sized GCS
> (which includes some reserved area to account for altstack signal
> handlers and otherwise supports maximum number of 16 byte aligned
> stack frames on the given stack) however the GCS is never freed as
> the lifetime of ucontext and related stack is user managed.
Afaik this is what x86_64 shadowstack does with __allocate_shadow_stack,
and this is explicit on edb5e0c8f915a798629717b5680a852c8bb3db25. Not
sure why it has been a problem yet for x86, but we might need to revise
it later (maybe because shadowstack has not been widly deployed).
LGTM, some nits below.
Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
> ---
> sysdeps/unix/sysv/linux/aarch64/makecontext.c | 63 ++++++++++++++++++-
> sysdeps/unix/sysv/linux/aarch64/setcontext.S | 4 ++
> 2 files changed, 65 insertions(+), 2 deletions(-)
>
> diff --git a/sysdeps/unix/sysv/linux/aarch64/makecontext.c b/sysdeps/unix/sysv/linux/aarch64/makecontext.c
> index 11516b79b9..815a604556 100644
> --- a/sysdeps/unix/sysv/linux/aarch64/makecontext.c
> +++ b/sysdeps/unix/sysv/linux/aarch64/makecontext.c
> @@ -22,6 +22,52 @@
> #include <stdint.h>
> #include <ucontext.h>
>
> +#define GCS_MAGIC 0x47435300
> +
> +/* Reserved for future use. */
> +static __thread void __attribute__ ((unused)) *__gcs = NULL;
There is no need to reserve this, specially because we are not sure
which strategy we will use (and this adds extra TLS size for libc.so).
> +
> +static struct _aarch64_ctx *extension (void *p)
> +{
> + return p;
> +}
> +
> +#ifndef SHADOW_STACK_SET_TOKEN
> +# define SHADOW_STACK_SET_TOKEN (1UL << 0)
> +# define SHADOW_STACK_SET_MARKER (1UL << 1)
> +#endif
> +
> +static void *
> +map_shadow_stack (void *addr, size_t size, unsigned long flags)
> +{
> + return (void *) INLINE_SYSCALL_CALL (map_shadow_stack, addr, size, flags);
> +}
> +
> +#define GCS_MAX_SIZE (1UL << 31)
> +#define GCS_ALTSTACK_RESERVE 160
> +
> +static void *
> +alloc_makecontext_gcs (size_t stack_size)
> +{
> + size_t size = (stack_size / 2 + GCS_ALTSTACK_RESERVE) & -8UL;
> + if (size > GCS_MAX_SIZE)
> + size = GCS_MAX_SIZE;
> +
> + unsigned long flags = SHADOW_STACK_SET_MARKER | SHADOW_STACK_SET_TOKEN;
> + void *base = map_shadow_stack (NULL, size, flags);
> + if (base == (void *) -1)
Maybe use MAP_FAILED here?
> + /* ENOSYS, bad size or OOM. */
> + abort ();
> + uint64_t *gcsp = (uint64_t *) ((char *) base + size);
> + /* Skip end of GCS token. */
> + gcsp--;
> + /* Verify GCS cap token. */
> + gcsp--;
> + if (((uint64_t)gcsp & 0xfffffffffffff000) + 1 != *gcsp)
> + abort ();
> + /* Return the target GCS pointer for context switch. */
> + return gcsp + 1;
> +}
>
> /* makecontext sets up a stack and the registers for the
> user context. The stack looks like this:
> @@ -56,10 +102,23 @@ __makecontext (ucontext_t *ucp, void (*func) (void), int argc, ...)
> sp = (uint64_t *) (((uintptr_t) sp) & -16L);
>
> ucp->uc_mcontext.regs[19] = (uintptr_t) ucp->uc_link;
> + ucp->uc_mcontext.regs[20] = (uintptr_t) func;
> ucp->uc_mcontext.sp = (uintptr_t) sp;
> - ucp->uc_mcontext.pc = (uintptr_t) func;
> + ucp->uc_mcontext.pc = (uintptr_t) __startcontext;
> ucp->uc_mcontext.regs[29] = (uintptr_t) 0;
> - ucp->uc_mcontext.regs[30] = (uintptr_t) &__startcontext;
> + ucp->uc_mcontext.regs[30] = (uintptr_t) 0;
> +
> + void *p = ucp->uc_mcontext.__reserved;
> + if (extension (p)->magic == FPSIMD_MAGIC)
> + p = (char *)p + extension (p)->size;
> + if (extension (p)->magic == GCS_MAGIC)
> + {
> + /* Using the kernel struct gcs_context layout. */
> + struct { uint64_t x, gcspr, y, z; } *q = p;
> + /* TODO: this allocation remains mapped even after thread
> + that uses it exits. */
> + q->gcspr = (uint64_t) alloc_makecontext_gcs (ucp->uc_stack.ss_size);
> + }
>
> va_start (ap, argc);
> for (i = 0; i < argc; ++i)
> diff --git a/sysdeps/unix/sysv/linux/aarch64/setcontext.S b/sysdeps/unix/sysv/linux/aarch64/setcontext.S
> index 848229ff26..695fc5b9b5 100644
> --- a/sysdeps/unix/sysv/linux/aarch64/setcontext.S
> +++ b/sysdeps/unix/sysv/linux/aarch64/setcontext.S
> @@ -180,7 +180,11 @@ L(gcs_done):
> PSEUDO_END (__setcontext)
> weak_alias (__setcontext, setcontext)
>
> +/* makecontext start function: receives uc_link in x19 and func in x20.
> + Arguments of func, x29, x30 and sp are set up by the caller. */
> ENTRY (__startcontext)
> + cfi_undefined (x30)
> + blr x20
> mov x0, x19
> cbnz x0, __setcontext
> 1: b HIDDEN_JUMPTARGET (exit)
More information about the Libc-alpha
mailing list