[PATCH v2 1/2] aarch64: Lock GCS status at startup

Yury Khrustalev yury.khrustalev@arm.com
Thu Feb 5 13:20:45 GMT 2026


Also allow to opt-out of locking for certain operations based on
bitmask given via the glibc.cpu.aarch64_gcs_lock tunable.

By default, all operations are locked.
---
 manual/tunables.texi                          | 50 +++++++++++++++----
 sysdeps/aarch64/dl-gcs.c                      |  2 +-
 sysdeps/aarch64/dl-start.S                    | 12 +++++
 sysdeps/aarch64/dl-tunables.list              |  6 +++
 sysdeps/aarch64/rtld-global-offsets.sym       |  5 +-
 .../unix/sysv/linux/aarch64/cpu-features.c    |  7 ++-
 .../unix/sysv/linux/aarch64/dl-procruntime.c  | 16 ++++++
 sysdeps/unix/sysv/linux/aarch64/libc-start.h  | 16 ++++--
 8 files changed, 95 insertions(+), 19 deletions(-)

diff --git a/manual/tunables.texi b/manual/tunables.texi
index 7956df919b..19a24f6abe 100644
--- a/manual/tunables.texi
+++ b/manual/tunables.texi
@@ -620,13 +620,12 @@ This tunable controls Guarded Control Stack (GCS) for the process.
 
 Accepted values are:
 
-0 = disabled: do not enable GCS.
-
-1 = enforced: check markings and fail if any binary is not marked.
-
-2 = optional: check markings but keep GCS off if any binary is unmarked.
-
-3 = override: enable GCS, markings are ignored.
+@itemize @bullet
+@item 0 = disabled: do not enable GCS.
+@item 1 = enforced: check markings and fail if any binary is not marked.
+@item 2 = optional: check markings but keep GCS off if any binary is unmarked.
+@item 3 = override: enable GCS, markings are ignored.
+@end itemize
 
 If unmarked binary is loaded via @code{dlopen} when GCS is enabled and
 markings are not ignored (@code{aarch64_gcs == 1} or @code{2}), then
@@ -640,24 +639,55 @@ Guarded Control Stack this tunable has no effect.
 Before enabling GCS for the process the value of this tunable is checked
 and depending on it the following outcomes are possible.
 
+@itemize @bullet
+@item
 @code{aarch64_gcs == 0}: GCS will not be enabled and GCS markings will not be
 checked for any binaries.
-
+@item
 @code{aarch64_gcs == 1}: GCS markings will be checked for all binaries loaded
 at startup and, only if all binaries are GCS-marked, GCS will be enabled. If
 any of the binaries are not GCS-marked, the process will abort. Subsequent call
 to @code{dlopen} for an unmarked binary will also result in abort.
-
+@item
 @code{aarch64_gcs == 2}: GCS markings will be checked for all binaries loaded
 at startup and, if any of such binaries are not GCS-marked, GCS will not be
 enabled and there will be no more checks for GCS marking. If all binaries
 loaded at startup are GCS-marked, then GCS will be enabled, in which case a
 call to @code{dlopen} for an unmarked binary will also result in abort.
-
+@item
 @code{aarch64_gcs == 3}: GCS will be enabled and GCS markings will not be
 checked for any binaries.
+@end itemize
+
+@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 opt out of locking of selected GCS features.
+
+The default value is @code{0xffffffffffffffff}, which means that by default,
+all GCS features (or operations on the shadow stack) are locked when GCS is
+enabled for the process.  The @code{glibc.cpu.aarch64_gcs_lock} tunable allows
+to opt out of locking for specific features: every @code{0} bit in the value
+of this tunable means that the corresponding feature will not be locked.
+
+Currently, 3 features are supported:
+
+@itemize @bullet
+@item
+@code{0b001}: status (enabled or disable GCS): @code{PR_SHADOW_STACK_ENABLE}.
+@item
+@code{0b010}: write to shadow stack: @code{PR_SHADOW_STACK_WRITE}.
+@item
+@code{0b100}: push a value to shadow stack: @code{PR_SHADOW_STACK_PUSH}.
+@end itemize
+
 @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 e1d1db4852..563d1ae0c3 100644
--- a/sysdeps/aarch64/dl-gcs.c
+++ b/sysdeps/aarch64/dl-gcs.c
@@ -145,5 +145,5 @@ _dl_gcs_check (struct link_map *l, const char *program, int dlopen_mode)
 /* Used to report error when prctl system call to enabled GCS fails.  */
 void _dl_gcs_enable_failed (int code)
 {
-  _dl_fatal_printf ("failed to enable GCS: %d\n", -code);
+  _dl_fatal_printf ("failed to enable or lock GCS: %d\n", -code);
 }
diff --git a/sysdeps/aarch64/dl-start.S b/sysdeps/aarch64/dl-start.S
index 3b5ff2cccb..b8f74a5547 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,17 @@ ENTRY (_start)
 	mov	x8, #SYS_ify(prctl)
 	svc	0x0
 	cbnz	w0, L(failed_gcs_enable)
+	/* Lock GCS status to prevent any operations.  */
+	mov	x0, PR_LOCK_SHADOW_STACK_STATUS
+	adrp	x16, _rtld_local
+	add	x16, x16, :lo12:_rtld_local
+	ldr	x1, [x16, GL_DL_AARCH64_GCS_LOCK_OFFSET]
+	mov	x2, 0
+	mov	x3, 0
+	mov	x4, 0
+	mov	x8, #SYS_ify(prctl)
+	svc	0x0
+	cbnz	w0, L(failed_gcs_enable)
 L(skip_gcs_enable):
 
 .globl _dl_start_user
diff --git a/sysdeps/aarch64/dl-tunables.list b/sysdeps/aarch64/dl-tunables.list
index a2ccba0b29..13d53d13cc 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: 0xffffffffffffffff
+      default: 0xffffffffffffffff
+    }
   }
 }
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 15aed15a66..9f4e8f61e0 100644
--- a/sysdeps/unix/sysv/linux/aarch64/cpu-features.c
+++ b/sysdeps/unix/sysv/linux/aarch64/cpu-features.c
@@ -182,6 +182,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 1f3b58d0fc..d415949f6d 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 9eecc557fd..ab24aa7cab 100644
--- a/sysdeps/unix/sysv/linux/aarch64/libc-start.h
+++ b/sysdeps/unix/sysv/linux/aarch64/libc-start.h
@@ -25,6 +25,7 @@
 
 # ifndef PR_SET_SHADOW_STACK_STATUS
 #  define PR_SET_SHADOW_STACK_STATUS	75
+#  define PR_LOCK_SHADOW_STACK_STATUS	76
 #  define PR_SHADOW_STACK_ENABLE	(1UL << 0)
 # endif
 
@@ -48,10 +49,17 @@ aarch64_libc_setup_tls (void)
 
   if (GL(dl_aarch64_gcs) != 0)
     {
-      int ret = INLINE_SYSCALL_CALL (prctl, PR_SET_SHADOW_STACK_STATUS,
-				     PR_SHADOW_STACK_ENABLE, 0, 0, 0);
-      if (ret)
-        _dl_fatal_printf ("failed to enable GCS: %d\n", -ret);
+      int r0 = INLINE_SYSCALL_CALL (prctl, PR_SET_SHADOW_STACK_STATUS,
+				    PR_SHADOW_STACK_ENABLE, 0, 0, 0);
+      if (r0)
+	_dl_fatal_printf ("failed to enable GCS: %d\n", -r0);
+
+      uint64_t lock = GL (dl_aarch64_gcs_lock);
+      /* Lock all bits, including future bits.  */
+      int r1 = INLINE_SYSCALL_CALL (prctl, PR_LOCK_SHADOW_STACK_STATUS, lock,
+				    0, 0, 0);
+      if (r1)
+	_dl_fatal_printf ("failed to lock GCS: %d\n", -r1);
     }
 }
 
-- 
2.47.3



More information about the Libc-alpha mailing list