[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