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

Yury Khrustalev yury.khrustalev@arm.com
Fri Dec 12 15:46:58 GMT 2025


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.
---
 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);
+	}
     }
 }
 
-- 
2.47.3



More information about the Libc-alpha mailing list