[PATCH v3 2/2] aarch64: Tests for locking GCS
Yury Khrustalev
yury.khrustalev@arm.com
Mon Feb 16 13:49:26 GMT 2026
On Thu, Feb 12, 2026 at 01:58:11PM -0300, Adhemerval Zanella Netto wrote:
>
>
> On 09/02/26 08:58, Yury Khrustalev wrote:
> > Check that GCS is locked properly based on the value of the
> > glibc.cpu.aarch64_gcs tunable.
>
> Maybe add some descriptions of the added test (what they test
> and why).
Makes sense, will do, thanks
> > ...
> >
> > +
> > +#include "tst-gcs-helper.h"
> > +
> > +#include <sys/prctl.h>
> > +#include <unistd.h>
> > +#include <errno.h>
> > +
> > +#ifndef PR_SET_SHADOW_STACK_STATUS
> > +# define PR_SET_SHADOW_STACK_STATUS 75
> > +#endif
>
> Maybe move the PR_SET_SHADOW_STACK_* definition to tst-gcs-helper.h
> (they are replicate in multiple files).
Good point.
>
> > +
> > +static int
> > +do_test_argv (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");
>
> Is checking HWCAP_GCS suffice to avoid false positive while checking GCS locking?
> I recall that there was some execve issue fixed only on recent kernels, but I
> don't recall whether this gates HWCAP_GCS advertise by the kernel.
It was fixed in all stable kernels AFAIK. Mark replied in [1].
[1]: https://inbox.sourceware.org/libc-alpha/bcd7a83c-cfba-41bf-b045-c6d528356af3@sirena.org.uk/
> > ...
> >
> > + /* GCS should be enabled for this test at the start. */
> > + TEST_VERIFY (__check_gcs_status ());
> > +
> > + if (argc == 1)
> > + {
> > + printf ("in parent: %u\n", getpid ());
> > + /* Spawn child process. */
> > + char *args[] = { argv[0], (char *)"child", NULL };
> > + execv (args[0], args);
>
> This fails without --enable-hardcoded-path-in-tests, usually the tests that
> required re-spawning themselves add an extra argument (though
> TEST-ARGS = -- $(host-test-program-cmd) make rule) and handle the arguments
> resconstruction (like tst-spawn*.c ones).
>
> You can also use support_spawn_wrap instead, it calls posix_spawn that will
> call execve in the end.
Thanks, I'll fix that.
> > ...
> >
> > +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 = fork ();
>
> Use xfork here.
OK
> > ...
> >
> > + if (pid)
> > + {
> > + int status;
> > + waitpid(pid, &status, 0);
>
> Use xwaitpid here.
OK.
> > ...
> >
> > +#ifndef NT_ARM_GCS
> > +#define NT_ARM_GCS 0x410
> > +#endif
>
> This should be always define by elf.h.
OK
> > +
> > +/* Uapi struct for PTRACE_GETREGSET with NT_ARM_GCS. */
> > +struct user_gcs
> > +{
> > + uint64_t enabled;
> > + uint64_t locked;
> > + uint64_t gcspr_el0;
> > +};
>
> Maybe move these to tst-gcs-helper.h?
It is only used in this one test, so I don't think moving it to the
header is helpful.
> > +
> > +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. */
> > + raise (SIGUSR1);
>
> Use xraise.
OK
> > +static void
> > +fork_target (char *argv[], 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 = fork ();
>
> Use xfork here.
OK
> > + if (pid == 0)
> > + {
> > + char *args[] = { argv[0], (char *)"target", NULL };
> > + 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. */
> > + ptrace (PTRACE_TRACEME, 0, NULL, NULL);
>
> I think we should also check for the ptrace return code.
OK
> > + execve (args[0], args, envp);
>
> Same as the tst-gcs-execv, this fails without --enable-hardcoded-path-in-tests
> (which is the dafault).
Will fix
> > + FAIL_EXIT1 ("execve: %m");
> > + }
> > + while (true)
> > + {
> > + int status;
> > + waitpid (pid, &status, 0);
>
> Use xwaitpid.
OK
> > ...
> >
> > 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
> >
> > ...
> >
> > +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);
> > + perror ("prctl");
>
> I think printing an error for the success case is confusing (at least it confuses
> me while testing, seeing a prctl printing the errno for a success testcase).
OK, makes sense
Thanks,
Yury
More information about the Libc-alpha
mailing list