[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