[PATCH 1/2] aarch64: Lock GCS with glibc.cpu.aarch64_gcs_lock tunable

Adhemerval Zanella Netto adhemerval.zanella@linaro.org
Wed Dec 17 20:17:36 GMT 2025



On 12/12/25 12:46, Yury Khrustalev wrote:
> This tunable controls locking status of Guarded Control Stack
> (GCS) for the process when GCS is enabled:
> 
>   0 = unlocked: GCS can be disabled via a prctl() call.
>   1 = locked: GCS is locked and cannot be disable for the process.
> 
> By default, GCS will be locked if enabled. If GCS is not enabled
> at startup, this tunable has no effect.
> 
> Issue a prctl() syscall to lock GCS status based on the value
> of the glibc.cpu.aarch64_gcs_lock tunable for static and also
> dynamically-linked binaries.
> 
> All operation bits are locked including the future ones. The
> prctl() syscall will accept this and this will ensure that
> any future operations are locked as well.

I am not sure if this hardening is better server with a tunable
than through a GNU property on the ET_EXEC itself.  I would expect
this to be modeled PT_GNU_STACK, even though GCS state is fully
handled in userland by design.

It has an extra burden enabling this through binutils and extra
marking; but I think once we start to deploy GCS enable system
flipping the tunable switch might also have unforseen breakage
(as we saw we start to enforce non-executable stacks).

The tunable options for GCS is already somewhat complex with
multiple options depending whether the object has or not 
GNU_PROPERTY_AARCH64_FEATURE_1_GCS.

> ---
>  manual/tunables.texi                           | 16 ++++++++++++++++
>  sysdeps/aarch64/dl-gcs.c                       |  6 ++++++
>  sysdeps/aarch64/dl-start.S                     | 18 ++++++++++++++++++
>  sysdeps/aarch64/dl-tunables.list               |  6 ++++++
>  sysdeps/aarch64/rtld-global-offsets.sym        |  5 +++--
>  sysdeps/unix/sysv/linux/aarch64/cpu-features.c |  7 +++++--
>  .../unix/sysv/linux/aarch64/dl-procruntime.c   | 16 ++++++++++++++++
>  sysdeps/unix/sysv/linux/aarch64/libc-start.h   | 12 ++++++++++++
>  8 files changed, 82 insertions(+), 4 deletions(-)
> 
> diff --git a/manual/tunables.texi b/manual/tunables.texi
> index 7956df919b..aeb726122f 100644
> --- a/manual/tunables.texi
> +++ b/manual/tunables.texi
> @@ -658,6 +658,22 @@ call to @code{dlopen} for an unmarked binary will also result in abort.
>  checked for any binaries.
>  @end deftp
>  
> +@deftp Tunable glibc.cpu.aarch64_gcs_lock
> +
> +This tunable introduces additional level of control over the Guarded Control
> +Stack (GCS) functionality managed by the @code{glibc.cpu.aarch64_gcs} tunable:
> +it allows to lock the GCS status after enabling it at startup to prevent any
> +thread of the application from disabling GCS later. If GCS is not enabled at
> +the startup time, this tunable has no effect.
> +
> +Supported values are:
> +
> +@code{aarch64_gcs_lock == 0}: GCS will not be locked.
> +
> +@code{aarch64_gcs_lock == 1}: GCS will be locked (this is default behaviour).
> +
> +@end deftp
> +
>  @node Memory Related Tunables
>  @section Memory Related Tunables
>  @cindex memory related tunables
> diff --git a/sysdeps/aarch64/dl-gcs.c b/sysdeps/aarch64/dl-gcs.c
> index 4ac86a5d6f..c2184bd56e 100644
> --- a/sysdeps/aarch64/dl-gcs.c
> +++ b/sysdeps/aarch64/dl-gcs.c
> @@ -119,3 +119,9 @@ void _dl_gcs_enable_failed (int code)
>  {
>    _dl_fatal_printf ("failed to enable GCS: %d\n", -code);
>  }
> +
> +/* Used to report error when prctl system call to lock GCS fails.  */
> +void _dl_gcs_lock_failed (int code)
> +{
> +  _dl_fatal_printf ("failed to lock GCS: %d\n", -code);
> +}
> diff --git a/sysdeps/aarch64/dl-start.S b/sysdeps/aarch64/dl-start.S
> index 2ccc219f1e..c4d4cbb7bd 100644
> --- a/sysdeps/aarch64/dl-start.S
> +++ b/sysdeps/aarch64/dl-start.S
> @@ -41,6 +41,7 @@ ENTRY (_start)
>  	/* Enable GCS before user code runs.  Note that IFUNC resolvers and
>  	   LD_AUDIT hooks may run before, but should not create threads.  */
>  #define PR_SET_SHADOW_STACK_STATUS  75
> +#define PR_LOCK_SHADOW_STACK_STATUS 76
>  #define PR_SHADOW_STACK_ENABLE      (1UL << 0)
>  	mov	x0, PR_SET_SHADOW_STACK_STATUS
>  	mov	x1, PR_SHADOW_STACK_ENABLE
> @@ -50,6 +51,20 @@ ENTRY (_start)
>  	mov	x8, #SYS_ify(prctl)
>  	svc	0x0
>  	cbnz	w0, L(failed_gcs_enable)
> +	/* Optionally, lock GCS to prevent its disabling later.  */
> +	adrp	x16, _rtld_local
> +	add	x16, x16, :lo12:_rtld_local
> +	ldr	x1, [x16, GL_DL_AARCH64_GCS_LOCK_OFFSET]
> +	cbz	x1, L(skip_gcs_enable)
> +	mov	x0, PR_LOCK_SHADOW_STACK_STATUS
> +	/* Lock all bits, including future bits.  */
> +	mov	x1, ~0
> +	mov	x2, 0
> +	mov	x3, 0
> +	mov	x4, 0
> +	mov	x8, #SYS_ify(prctl)
> +	svc	0x0
> +	cbnz	w0, L(failed_gcs_lock)
>  L(skip_gcs_enable):
>  
>  .globl _dl_start_user
> @@ -75,4 +90,7 @@ _dl_start_user:
>  L(failed_gcs_enable):
>  	b	_dl_gcs_enable_failed
>  
> +L(failed_gcs_lock):
> +	b	_dl_gcs_lock_failed
> +
>  END (_start)
> diff --git a/sysdeps/aarch64/dl-tunables.list b/sysdeps/aarch64/dl-tunables.list
> index 40757ac046..2b03f5a170 100644
> --- a/sysdeps/aarch64/dl-tunables.list
> +++ b/sysdeps/aarch64/dl-tunables.list
> @@ -33,5 +33,11 @@ glibc {
>        maxval: 3
>        default: 0
>      }
> +    aarch64_gcs_lock {
> +      type: UINT_64
> +      minval: 0
> +      maxval: 1
> +      default: 1
> +    }
>    }
>  }
> diff --git a/sysdeps/aarch64/rtld-global-offsets.sym b/sysdeps/aarch64/rtld-global-offsets.sym
> index 6c0690bb95..89bc91946e 100644
> --- a/sysdeps/aarch64/rtld-global-offsets.sym
> +++ b/sysdeps/aarch64/rtld-global-offsets.sym
> @@ -7,9 +7,10 @@
>  
>  -- Offsets of _rtld_global_ro in libc.so
>  
> -GLRO_DL_HWCAP_OFFSET	GLRO_offsetof (dl_hwcap)
> -GLRO_DL_HWCAP2_OFFSET	GLRO_offsetof (dl_hwcap2)
> +GLRO_DL_HWCAP_OFFSET		GLRO_offsetof (dl_hwcap)
> +GLRO_DL_HWCAP2_OFFSET		GLRO_offsetof (dl_hwcap2)
>  
>  -- Offsets of _rtld_global in libc.so
>  
>  GL_DL_AARCH64_GCS_OFFSET	GL_offsetof (dl_aarch64_gcs)
> +GL_DL_AARCH64_GCS_LOCK_OFFSET	GL_offsetof (dl_aarch64_gcs_lock)
> diff --git a/sysdeps/unix/sysv/linux/aarch64/cpu-features.c b/sysdeps/unix/sysv/linux/aarch64/cpu-features.c
> index a05a4ce794..c402aa5035 100644
> --- a/sysdeps/unix/sysv/linux/aarch64/cpu-features.c
> +++ b/sysdeps/unix/sysv/linux/aarch64/cpu-features.c
> @@ -181,6 +181,9 @@ init_cpu_features (struct cpu_features *cpu_features)
>    cpu_features->mops = GLRO (dl_hwcap2) & HWCAP2_MOPS;
>  
>    if (GLRO (dl_hwcap) & HWCAP_GCS)
> -    /* GCS status may be updated later by binary compatibility checks.  */
> -    GL (dl_aarch64_gcs) = TUNABLE_GET (glibc, cpu, aarch64_gcs, uint64_t, 0);
> +    {
> +      /* GCS status may be updated later by binary compatibility checks.  */
> +      GL (dl_aarch64_gcs) = TUNABLE_GET (glibc, cpu, aarch64_gcs, uint64_t, 0);
> +      GL (dl_aarch64_gcs_lock) = TUNABLE_GET (glibc, cpu, aarch64_gcs_lock, uint64_t, 0);
> +    }
>  }
> diff --git a/sysdeps/unix/sysv/linux/aarch64/dl-procruntime.c b/sysdeps/unix/sysv/linux/aarch64/dl-procruntime.c
> index 044544aa78..9ee7840cb6 100644
> --- a/sysdeps/unix/sysv/linux/aarch64/dl-procruntime.c
> +++ b/sysdeps/unix/sysv/linux/aarch64/dl-procruntime.c
> @@ -35,3 +35,19 @@ PROCINFO_CLASS unsigned long _dl_aarch64_gcs
>  ,
>  # endif
>  #endif
> +
> +#if !IS_IN (ldconfig)
> +# if !defined PROCINFO_DECL && defined SHARED
> +  ._dl_aarch64_gcs_lock
> +# else
> +PROCINFO_CLASS unsigned long _dl_aarch64_gcs_lock
> +# endif
> +# ifndef PROCINFO_DECL
> += 0
> +# endif
> +# if !defined SHARED || defined PROCINFO_DECL
> +;
> +# else
> +,
> +# endif
> +#endif
> diff --git a/sysdeps/unix/sysv/linux/aarch64/libc-start.h b/sysdeps/unix/sysv/linux/aarch64/libc-start.h
> index 75ae0a884a..8fad31e151 100644
> --- a/sysdeps/unix/sysv/linux/aarch64/libc-start.h
> +++ b/sysdeps/unix/sysv/linux/aarch64/libc-start.h
> @@ -28,6 +28,10 @@
>  #  define PR_SHADOW_STACK_ENABLE	(1UL << 0)
>  # endif
>  
> +# ifndef PR_LOCK_SHADOW_STACK_STATUS
> +#  define PR_LOCK_SHADOW_STACK_STATUS	76
> +# endif
> +
>  /* Must be on a top-level stack frame that does not return.  */
>  static inline void __attribute__((always_inline))
>  aarch64_libc_setup_tls (void)
> @@ -51,6 +55,14 @@ aarch64_libc_setup_tls (void)
>  				     PR_SHADOW_STACK_ENABLE, 0, 0, 0);
>        if (ret)
>          _dl_fatal_printf ("failed to enable GCS: %d\n", -ret);
> +      if (GL (dl_aarch64_gcs_lock) != 0)
> +	{
> +	  /* Lock all bits, including future bits.  */
> +	  ret = INLINE_SYSCALL_CALL (prctl, PR_LOCK_SHADOW_STACK_STATUS,
> +				     ~0ul, 0, 0, 0);
> +	  if (ret)
> +	    _dl_fatal_printf ("failed to lock GCS: %d\n", -ret);
> +	}
>      }
>  }
>  



More information about the Libc-alpha mailing list