File aarch64-lock-gcs-startup.patch of Package glibc

From 2ee41ba6ecd34bf7e5c4f90fdfa20376c1dcecb0 Mon Sep 17 00:00:00 2001
From: Yury Khrustalev <yury.khrustalev@arm.com>
Date: Mon, 2 Feb 2026 18:27:53 +0000
Subject: [PATCH] aarch64: Lock GCS status at startup

If GCS is enabled (see tunable glibc.cpu.aarch64_gcs), we lock all GCS
operations (including status, write on shadow stack, and push to shadow
stack) unless OPTIONAL policy is used.

Reviewed-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>
(cherry picked from commit 5061f524a2976daf3062dd34beae1d7d15502770)
---
 manual/tunables.texi                         | 54 ++++++++++++--------
 sysdeps/aarch64/dl-gcs.c                     |  6 +++
 sysdeps/aarch64/dl-start.S                   | 21 +++++++-
 sysdeps/unix/sysv/linux/aarch64/libc-start.h | 29 +++++++++--
 4 files changed, 82 insertions(+), 28 deletions(-)

diff --git a/manual/tunables.texi b/manual/tunables.texi
index 7956df919b..29f2ba000f 100644
--- a/manual/tunables.texi
+++ b/manual/tunables.texi
@@ -620,42 +620,54 @@ 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 @code{0} = disabled: do not enable GCS.
+@item @code{1} = enforced: check markings and abort if any binary is not
+marked, otherwise enable GCS and lock all GCS features.
+@item @code{2} = optional: check markings but keep GCS off if any binary
+is unmarked, otherwise enable GCS but do not lock any GCS features.
+@item @code{3} = override: enable GCS and lock all GCS features, 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
-the process will be aborted.
+markings are not ignored (i.e. @code{aarch64_gcs == 1} or @code{2}), then
+@code{dlopen} will return an error.
 
 Default is @code{0}, so GCS is disabled by default.
 
-This tunable is specific to AArch64. On systems that do not support
+This tunable is specific to AArch64.  On systems that do not support
 Guarded Control Stack this tunable has no effect.
 
+GCS features (or operations on shadow stack) such as @code{STATUS} (i.e.
+enabling and disabling GCS), @code{WRITE}, and @code{PUSH}, will be locked
+for the @code{enforced} and @code{override} tunable values.
+
 Before enabling GCS for the process the value of this tunable is checked
 and depending on it the following outcomes are possible.
 
-@code{aarch64_gcs == 0}: GCS will not be enabled and GCS markings will not be
+@itemize @bullet
+@item
+@code{aarch64_gcs == 0}: GCS will remain disabled 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.
-
+at startup and, only if all binaries are GCS-marked, GCS will be enabled and
+all GCS features will be locked.  If any of the binaries are not GCS-marked,
+the process will abort.  Subsequent call to @code{dlopen} for an unmarked binary
+will result in @code{dlopen} returning an error.
+@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.
+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 but GCS features
+will not be locked.  In this case a call to @code{dlopen} for an unmarked binary
+will result in @code{dlopen} returning an error.
+@item
+@code{aarch64_gcs == 3}: GCS will be enabled and all GCS features will be
+locked.  GCS markings will not be checked for any binaries.
+@end itemize
 
-@code{aarch64_gcs == 3}: GCS will be enabled and GCS markings will not be
-checked for any binaries.
 @end deftp
 
 @node Memory Related Tunables
diff --git a/sysdeps/aarch64/dl-gcs.c b/sysdeps/aarch64/dl-gcs.c
index e1d1db4852..841d6428d6 100644
--- a/sysdeps/aarch64/dl-gcs.c
+++ b/sysdeps/aarch64/dl-gcs.c
@@ -147,3 +147,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 3b5ff2cccb..c278485cd3 100644
--- a/sysdeps/aarch64/dl-start.S
+++ b/sysdeps/aarch64/dl-start.S
@@ -35,12 +35,13 @@ ENTRY (_start)
 	/* Use GL(dl_aarch64_gcs) to set the shadow stack status.  */
 	adrp	x16, _rtld_local
 	add	x16, x16, :lo12:_rtld_local
-	ldr	x1, [x16, GL_DL_AARCH64_GCS_OFFSET]
-	cbz	x1, L(skip_gcs_enable)
+	ldr	x22, [x16, GL_DL_AARCH64_GCS_OFFSET]
+	cbz	x22, L(skip_gcs_enable)
 
 	/* 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,19 @@ ENTRY (_start)
 	mov	x8, #SYS_ify(prctl)
 	svc	0x0
 	cbnz	w0, L(failed_gcs_enable)
+	/* Check if we need to lock GCS features.  */
+	/* If the aarch64_gcs tunable is either 0 or 2 do not lock GCS.  */
+	tst	x22, #-3
+	beq	L(skip_gcs_enable)
+	mov	x0, PR_LOCK_SHADOW_STACK_STATUS
+	/* Lock everything including future operations.  */
+	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 +89,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/unix/sysv/linux/aarch64/libc-start.h b/sysdeps/unix/sysv/linux/aarch64/libc-start.h
index 9eecc557fd..4ccd13741b 100644
--- a/sysdeps/unix/sysv/linux/aarch64/libc-start.h
+++ b/sysdeps/unix/sysv/linux/aarch64/libc-start.h
@@ -25,9 +25,17 @@
 
 # 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
 
+# ifndef GCS_POLICY_DISABLED
+/* GCS is disabled.  */
+#  define GCS_POLICY_DISABLED 0
+/* Optionally enable GCS if all startup dependencies are marked.  */
+#  define GCS_POLICY_OPTIONAL 2
+# endif
+
 /* Must be on a top-level stack frame that does not return.  */
 static inline void __attribute__((always_inline))
 aarch64_libc_setup_tls (void)
@@ -46,12 +54,23 @@ aarch64_libc_setup_tls (void)
 
   _rtld_main_check (main_map, _dl_argv[0]);
 
-  if (GL(dl_aarch64_gcs) != 0)
+  uint64_t gcs = GL (dl_aarch64_gcs);
+  if (gcs != GCS_POLICY_DISABLED)
     {
-      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 ret;
+      ret = INLINE_SYSCALL_CALL (prctl, PR_SET_SHADOW_STACK_STATUS,
+				 PR_SHADOW_STACK_ENABLE, 0, 0, 0);
+      if (ret != 0)
+	_dl_fatal_printf ("failed to enable GCS: %d\n", -ret);
+      /* Do not lock GCS features if policy is OPTIONAL.  */
+      if (gcs != GCS_POLICY_OPTIONAL)
+	{
+	  /* Lock all bits, including future bits.  */
+	  ret = INLINE_SYSCALL_CALL (prctl, PR_LOCK_SHADOW_STACK_STATUS,
+				     ~0, 0, 0, 0);
+	  if (ret != 0)
+	    _dl_fatal_printf ("failed to lock GCS: %d\n", -ret);
+	}
     }
 }
 
-- 
2.53.0

From 305ce0b58809869295e62c3caa7eda4c8e41134f Mon Sep 17 00:00:00 2001
From: Yury Khrustalev <yury.khrustalev@arm.com>
Date: Tue, 3 Feb 2026 15:51:11 +0000
Subject: [PATCH] aarch64: Tests for locking GCS

Check that GCS is locked properly based on the value of the
glibc.cpu.aarch64_gcs tunable.

Test tst-gcs-execv checks that a child process can be spawned correctly
when GCS is locked for the parent process.

Test tst-gcs-fork checks that if GCS is not locked for the parent
process, the forked child can disable GCS.

Tests tst-gcs-lock and tst-gcs-lock-static check that GCS is locked
for dynamic and static executables when run with aarch64_gcs=1.

Tests tst-gcs-unlock and tst-gcs-unlock-static check that GCS is not
locked for dynamic and static executables when run with aarch64_gcs=0.

Test tst-gcs-lock-ptrace checks via ptrace that when GCS is locked,
all GCS features are locked.

Reviewed-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>
(cherry picked from commit ad9784419e274cef9ba7152d7cd2490d291f837b)
---
 sysdeps/unix/sysv/linux/aarch64/Makefile      |  25 +++
 .../unix/sysv/linux/aarch64/tst-gcs-execv.c   |  91 ++++++++++
 .../unix/sysv/linux/aarch64/tst-gcs-fork.c    |  75 ++++++++
 .../unix/sysv/linux/aarch64/tst-gcs-helper.h  |   8 +-
 .../sysv/linux/aarch64/tst-gcs-lock-ptrace.c  | 166 ++++++++++++++++++
 .../sysv/linux/aarch64/tst-gcs-lock-static.c  |   1 +
 .../unix/sysv/linux/aarch64/tst-gcs-lock.c    |  58 ++++++
 .../linux/aarch64/tst-gcs-unlock-static.c     |   2 +
 .../unix/sysv/linux/aarch64/tst-gcs-unlock.c  |   2 +
 9 files changed, 427 insertions(+), 1 deletion(-)
 create mode 100644 sysdeps/unix/sysv/linux/aarch64/tst-gcs-execv.c
 create mode 100644 sysdeps/unix/sysv/linux/aarch64/tst-gcs-fork.c
 create mode 100644 sysdeps/unix/sysv/linux/aarch64/tst-gcs-lock-ptrace.c
 create mode 100644 sysdeps/unix/sysv/linux/aarch64/tst-gcs-lock-static.c
 create mode 100644 sysdeps/unix/sysv/linux/aarch64/tst-gcs-lock.c
 create mode 100644 sysdeps/unix/sysv/linux/aarch64/tst-gcs-unlock-static.c
 create mode 100644 sysdeps/unix/sysv/linux/aarch64/tst-gcs-unlock.c

diff --git a/sysdeps/unix/sysv/linux/aarch64/Makefile b/sysdeps/unix/sysv/linux/aarch64/Makefile
index b8eb9c0752..89a02faed1 100644
--- a/sysdeps/unix/sysv/linux/aarch64/Makefile
+++ b/sysdeps/unix/sysv/linux/aarch64/Makefile
@@ -32,10 +32,14 @@ gcs-tests-dynamic = \
   tst-gcs-dlopen-override \
   tst-gcs-enforced \
   tst-gcs-enforced-abort \
+  tst-gcs-execv \
+  tst-gcs-fork \
   tst-gcs-ld-debug-both \
   tst-gcs-ld-debug-dlopen \
   tst-gcs-ld-debug-exe \
   tst-gcs-ld-debug-shared \
+  tst-gcs-lock \
+  tst-gcs-lock-ptrace \
   tst-gcs-noreturn \
   tst-gcs-optional-off \
   tst-gcs-optional-on \
@@ -48,15 +52,18 @@ gcs-tests-dynamic = \
   tst-gcs-shared-enforced-abort \
   tst-gcs-shared-optional \
   tst-gcs-shared-override \
+  tst-gcs-unlock \
   # gcs-tests-dynamic
 
 gcs-tests-static = \
   tst-gcs-disabled-static \
   tst-gcs-enforced-static \
   tst-gcs-enforced-static-abort \
+  tst-gcs-lock-static \
   tst-gcs-optional-static-off \
   tst-gcs-optional-static-on \
   tst-gcs-override-static \
+  tst-gcs-unlock-static \
   # gcs-tests-static
 
 tests += \
@@ -106,6 +113,24 @@ tst-gcs-optional-static-on-ENV = GLIBC_TUNABLES=glibc.cpu.aarch64_gcs=2
 tst-gcs-optional-static-off-ENV = GLIBC_TUNABLES=glibc.cpu.aarch64_gcs=2
 tst-gcs-override-static-ENV = GLIBC_TUNABLES=glibc.cpu.aarch64_gcs=3
 
+LDFLAGS-tst-gcs-execv += -Wl,-z,gcs=always
+tst-gcs-execv-ENV = GLIBC_TUNABLES=glibc.cpu.aarch64_gcs=1
+tst-gcs-execv-ARGS = -- $(host-test-program-cmd)
+LDFLAGS-tst-gcs-fork += -Wl,-z,gcs=always
+tst-gcs-fork-ENV = GLIBC_TUNABLES=glibc.cpu.aarch64_gcs=2
+
+LDFLAGS-tst-gcs-lock += -Wl,-z,gcs=always
+tst-gcs-lock-ENV = GLIBC_TUNABLES=glibc.cpu.aarch64_gcs=1
+LDFLAGS-tst-gcs-lock-ptrace += -Wl,-z,gcs=always
+tst-gcs-lock-ptrace-ENV = GLIBC_TUNABLES=glibc.cpu.aarch64_gcs=1
+tst-gcs-lock-ptrace-ARGS = -- $(host-test-program-cmd)
+LDFLAGS-tst-gcs-lock-static += -Wl,-z,gcs=always
+tst-gcs-lock-static-ENV = GLIBC_TUNABLES=glibc.cpu.aarch64_gcs=1
+LDFLAGS-tst-gcs-unlock += -Wl,-z,gcs=always
+tst-gcs-unlock-ENV = GLIBC_TUNABLES=glibc.cpu.aarch64_gcs=2
+LDFLAGS-tst-gcs-unlock-static += -Wl,-z,gcs=always
+tst-gcs-unlock-static-ENV = GLIBC_TUNABLES=glibc.cpu.aarch64_gcs=2
+
 # force one of the dependencies to be unmarked
 LDFLAGS-tst-gcs-mod2.so += -Wl,-z,gcs=never
 
diff --git a/sysdeps/unix/sysv/linux/aarch64/tst-gcs-execv.c b/sysdeps/unix/sysv/linux/aarch64/tst-gcs-execv.c
new file mode 100644
index 0000000000..91053a4726
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/aarch64/tst-gcs-execv.c
@@ -0,0 +1,91 @@
+/* AArch64 test for GCS for creating child process.
+   Copyright (C) 2026 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include "tst-gcs-helper.h"
+
+#include <sys/prctl.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+
+static int
+target (void)
+{
+  /* In child.  */
+  printf ("in child: %u\n", getpid ());
+  TEST_VERIFY (__check_gcs_status ());
+
+  /* Try disabling GCS (should fail with EBUSY).  */
+  int res = prctl (PR_SET_SHADOW_STACK_STATUS, 0, 0, 0, 0);
+  TEST_COMPARE (res, -1);
+  TEST_COMPARE (errno, EBUSY);
+  return 0;
+}
+
+int main(int argc, char *argv[])
+{
+  /* Check if GCS could possible by enabled.  */
+  if (!(getauxval (AT_HWCAP) & HWCAP_GCS))
+    FAIL_UNSUPPORTED ("kernel or CPU does not support GCS");
+
+  /* GCS should be enabled for this test at the start.  */
+  TEST_VERIFY (__check_gcs_status ());
+
+  /* If last argument is 'target', we just run target code.  */
+  if (strcmp (argv[argc - 1], "target") == 0)
+    return target ();
+
+  /* In parent, we should at least have 3 arguments.  */
+  if (argc < 3)
+    FAIL_EXIT1 ("wrong number of arguments: %d", argc);
+
+  char *child_args[] = { NULL, NULL, NULL, NULL, NULL, NULL };
+
+  /* Check command line arguments to construct child command.  */
+  if (strcmp (argv[0], argv[2]) == 0)
+    {
+      /* Command looks like
+	 /path/to/test -- /path/to/test  */
+      /* /path/to/test  */
+      child_args[0] = argv[0];
+      /* Extra argument for the child process.  */
+      child_args[1] = (char *)"target";
+    }
+  else
+    {
+      /* Command looks like
+	 /path/to/test -- /path/to/ld.so ...  */
+      TEST_VERIFY_EXIT (argc > 5);
+      TEST_COMPARE_STRING (argv[3], "--library-path");
+      /* /path/to/ld-linux-aarch64.so.1  */
+      child_args[0] = argv[2];
+      /* --library-path  */
+      child_args[1] = argv[3];
+      /* Library path...  */
+      child_args[2] = argv[4];
+      /* /path/to/test  */
+      child_args[3] = argv[5];
+      /* Extra argument for the child process.  */
+      child_args[4] = (char *)"target";
+    }
+
+  printf ("in parent: %u\n", getpid ());
+  /* Spawn child process.  */
+  execv (child_args[0], child_args);
+  FAIL_EXIT1 ("execv: %m");
+}
diff --git a/sysdeps/unix/sysv/linux/aarch64/tst-gcs-fork.c b/sysdeps/unix/sysv/linux/aarch64/tst-gcs-fork.c
new file mode 100644
index 0000000000..365807a562
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/aarch64/tst-gcs-fork.c
@@ -0,0 +1,75 @@
+/* AArch64 test for GCS for creating child process using fork.
+   Copyright (C) 2026 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include "tst-gcs-helper.h"
+
+#include <support/xunistd.h>
+#include <sys/ptrace.h>
+#include <sys/prctl.h>
+#include <sys/wait.h>
+#include <sys/uio.h>
+#include <unistd.h>
+#include <errno.h>
+
+static int
+do_test (void)
+{
+  /* Check if GCS could possible by enabled.  */
+  if (!(getauxval (AT_HWCAP) & HWCAP_GCS))
+    FAIL_UNSUPPORTED ("kernel or CPU does not support GCS");
+
+  /* GCS should be enabled for this test at the start.  */
+  TEST_VERIFY (__check_gcs_status ());
+
+  pid_t pid = xfork ();
+  const char *name;
+  if (pid == 0)
+    name = "child";
+  else
+    name = "parent";
+
+  /* Both parent and child should initially have GCS enabled.  */
+  TEST_VERIFY (__check_gcs_status ());
+  uint64_t data;
+  if (prctl (PR_GET_SHADOW_STACK_STATUS, &data, 0, 0, 0))
+    FAIL_EXIT1 ("prctl: %m");
+  printf ("in %s: gcs status: %016lx\n", name, data);
+
+  if (pid)
+    {
+      int status;
+      xwaitpid (pid, &status, 0);
+      printf ("in %s: child exited with code %u\n", name, WEXITSTATUS(status));
+    }
+  else
+    {
+      /* Try disabling GCS for the child
+	 (should succeed because of the tunable).  */
+      if (prctl (PR_SET_SHADOW_STACK_STATUS, 0, 0, 0, 0))
+	FAIL_EXIT1 ("prctl: %m");
+      /* GCS should be disabled.  */
+      TEST_VERIFY (!__check_gcs_status ());
+      if (prctl (PR_GET_SHADOW_STACK_STATUS, &data, 0, 0, 0))
+	FAIL_EXIT1 ("prctl: %m");
+      printf ("in %s: gcs status: %016lx\n", name, data);
+    }
+
+  return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/sysdeps/unix/sysv/linux/aarch64/tst-gcs-helper.h b/sysdeps/unix/sysv/linux/aarch64/tst-gcs-helper.h
index 35ce0036ec..c075fdc205 100644
--- a/sysdeps/unix/sysv/linux/aarch64/tst-gcs-helper.h
+++ b/sysdeps/unix/sysv/linux/aarch64/tst-gcs-helper.h
@@ -26,7 +26,13 @@
 #include <stdio.h>
 #include <sys/auxv.h>
 
-static bool __check_gcs_status (void)
+#ifndef PR_SET_SHADOW_STACK_STATUS
+# define PR_GET_SHADOW_STACK_STATUS 74
+# define PR_SET_SHADOW_STACK_STATUS 75
+#endif
+
+static bool
+__check_gcs_status (void)
 {
   register unsigned long x16 asm ("x16");
   asm volatile (
diff --git a/sysdeps/unix/sysv/linux/aarch64/tst-gcs-lock-ptrace.c b/sysdeps/unix/sysv/linux/aarch64/tst-gcs-lock-ptrace.c
new file mode 100644
index 0000000000..27fa1d3dea
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/aarch64/tst-gcs-lock-ptrace.c
@@ -0,0 +1,166 @@
+/* AArch64 test for GCS for creating child process using fork
+   with ptrace to check locked GCS operations.
+   Copyright (C) 2026 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include "tst-gcs-helper.h"
+
+#include <support/xunistd.h>
+#include <support/xsignal.h>
+#include <sys/ptrace.h>
+#include <sys/prctl.h>
+#include <sys/wait.h>
+#include <sys/uio.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+
+/* Uapi struct for PTRACE_GETREGSET with NT_ARM_GCS.  */
+struct user_gcs
+{
+  uint64_t enabled;
+  uint64_t locked;
+  uint64_t gcspr_el0;
+};
+
+static int
+target (void)
+{
+  /* This signal is raised after the process has started
+     and has been initialised so we can ptrace it at this
+     point and obtain GCS locked features.  */
+  xraise (SIGUSR1);
+  return 0;
+}
+
+static void
+fork_target (char *args[], uint64_t aarch64_gcs)
+{
+  /* Currently kernel returns only lower 32 bits of locked
+     features so we only compare them.  */
+  bool lock_gcs = aarch64_gcs != 0 && aarch64_gcs != 2;
+  uint64_t expected_locked = lock_gcs ? 0xfffffffful : 0ul;
+  pid_t pid = xfork ();
+  if (pid == 0)
+    {
+      char tunables[90];
+      snprintf (tunables, sizeof (tunables), "GLIBC_TUNABLES="
+		"glibc.cpu.aarch64_gcs=0x%016lx", aarch64_gcs);
+      char *envp[] = { tunables, NULL };
+      /* We need to ptrace child process to use PTRACE_GETREGSET
+	 with NT_ARM_GCS after it has started.  */
+      int res = ptrace (PTRACE_TRACEME, 0, NULL, NULL);
+      if (res != 0)
+	FAIL_EXIT1 ("ptrace: %m");
+      execve (args[0], args, envp);
+      FAIL_EXIT1 ("execve: %m");
+    }
+  bool checked = false;
+  while (true)
+    {
+      int status;
+      xwaitpid (pid, &status, 0);
+      if (WIFSTOPPED (status))
+	{
+	  /* Child stopped by signal.  */
+	  int sig = WSTOPSIG (status);
+	  if (sig == SIGUSR1)
+	    {
+	      struct user_gcs ugcs = {};
+	      struct iovec io;
+	      io.iov_base = &ugcs;
+	      io.iov_len = sizeof (struct user_gcs);
+	      if (ptrace (PTRACE_GETREGSET, pid, NT_ARM_GCS, &io))
+		FAIL_EXIT1 ("ptrace (PTRACE_GETREGSET): %m");
+	      printf ("expected vs locked: %016lx %016lx\n",
+		      expected_locked, ugcs.locked);
+	      if (lock_gcs)
+		TEST_VERIFY_EXIT (ugcs.enabled);
+	      TEST_VERIFY_EXIT (ugcs.locked == expected_locked);
+	      if (aarch64_gcs != 0)
+		TEST_VERIFY_EXIT ((void *) ugcs.gcspr_el0 != NULL);
+	      checked = true;
+	    }
+        }
+      else if (WIFSIGNALED (status))
+	{
+	  /* Child terminated by signal.  */
+	  break;
+	}
+      else if (WIFEXITED (status))
+	{
+          /* Child terminated by normally.  */
+	  break;
+	}
+      ptrace (PTRACE_CONT, pid, 0, 0);
+    }
+  /* If child process hasn't run correctly, this will remain false.  */
+  TEST_VERIFY_EXIT (checked);
+}
+
+int main(int argc, char *argv[])
+{
+  /* Check if GCS could possible by enabled.  */
+  if (!(getauxval (AT_HWCAP) & HWCAP_GCS))
+    FAIL_UNSUPPORTED ("kernel or CPU does not support GCS");
+
+  /* GCS should be enabled for this test.  */
+  TEST_VERIFY (__check_gcs_status ());
+
+  /* If last argument is 'target', we just run target code.  */
+  if (strcmp (argv[argc - 1], "target") == 0)
+    return target ();
+
+  /* In parent, we should at least have 3 arguments.  */
+  if (argc < 3)
+    FAIL_EXIT1 ("wrong number of arguments: %d", argc);
+
+  char *child_args[] = { NULL, NULL, NULL, NULL, NULL , NULL };
+
+  /* Check command line arguments to construct child command.  */
+  if (strcmp (argv[0], argv[2]) == 0)
+    {
+      /* Command looks like
+	 /path/to/test -- /path/to/test  */
+      /* /path/to/test  */
+      child_args[0] = argv[0];
+      /* Extra argument for the child process.  */
+      child_args[1] = (char *)"target";
+    }
+  else
+    {
+      /* Command looks like
+	 /path/to/test -- /path/to/ld.so ...  */
+      TEST_VERIFY_EXIT (argc > 5);
+      TEST_COMPARE_STRING (argv[3], "--library-path");
+      /* /path/to/ld-linux-aarch64.so.1  */
+      child_args[0] = argv[2];
+      /* --library-path  */
+      child_args[1] = argv[3];
+      /* Library path...  */
+      child_args[2] = argv[4];
+      /* /path/to/test  */
+      child_args[3] = argv[5];
+      /* Extra argument for the child process.  */
+      child_args[4] = (char *)"target";
+    }
+
+  /* Check all 4 values for the aarch64_gcs tunable.  */
+  for (uint64_t aarch64_gcs = 0; aarch64_gcs < 4; aarch64_gcs++)
+    fork_target (child_args, aarch64_gcs);
+  return 0;
+}
diff --git a/sysdeps/unix/sysv/linux/aarch64/tst-gcs-lock-static.c b/sysdeps/unix/sysv/linux/aarch64/tst-gcs-lock-static.c
new file mode 100644
index 0000000000..b80e2f70e8
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/aarch64/tst-gcs-lock-static.c
@@ -0,0 +1 @@
+#include "tst-gcs-lock.c"
diff --git a/sysdeps/unix/sysv/linux/aarch64/tst-gcs-lock.c b/sysdeps/unix/sysv/linux/aarch64/tst-gcs-lock.c
new file mode 100644
index 0000000000..9a17ef514d
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/aarch64/tst-gcs-lock.c
@@ -0,0 +1,58 @@
+/* AArch64 test for GCS locking.
+   Copyright (C) 2026 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include "tst-gcs-helper.h"
+
+#include <linux/prctl.h>
+#include <sys/prctl.h>
+#include <errno.h>
+
+static int
+do_test (void)
+{
+  /* Check if GCS could possible by enabled.  */
+  if (!(getauxval (AT_HWCAP) & HWCAP_GCS))
+    FAIL_UNSUPPORTED ("kernel or CPU does not support GCS");
+
+  TEST_VERIFY (__check_gcs_status ());
+
+  /* Try disabling GCS.  */
+  int res = prctl (PR_SET_SHADOW_STACK_STATUS, 0, 0, 0, 0);
+  if (res)
+    {
+      TEST_COMPARE (errno, EBUSY);
+#ifdef GCS_SHOULD_UNLOCK
+      FAIL_EXIT1 ("GCS was not unlocked (was supposed to): %m");
+#else
+      TEST_VERIFY (__check_gcs_status ());
+#endif
+    }
+  else
+    {
+#ifdef GCS_SHOULD_UNLOCK
+      TEST_VERIFY (!__check_gcs_status ());
+      puts ("GCS unlocked successfully");
+#else
+      FAIL_EXIT1 ("GCS was unlocked (was not supposed to)");
+#endif
+    }
+
+  return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/sysdeps/unix/sysv/linux/aarch64/tst-gcs-unlock-static.c b/sysdeps/unix/sysv/linux/aarch64/tst-gcs-unlock-static.c
new file mode 100644
index 0000000000..7e02820031
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/aarch64/tst-gcs-unlock-static.c
@@ -0,0 +1,2 @@
+#define GCS_SHOULD_UNLOCK
+#include "tst-gcs-lock.c"
diff --git a/sysdeps/unix/sysv/linux/aarch64/tst-gcs-unlock.c b/sysdeps/unix/sysv/linux/aarch64/tst-gcs-unlock.c
new file mode 100644
index 0000000000..7e02820031
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/aarch64/tst-gcs-unlock.c
@@ -0,0 +1,2 @@
+#define GCS_SHOULD_UNLOCK
+#include "tst-gcs-lock.c"
-- 
2.53.0

openSUSE Build Service is sponsored by