[PATCH 1/1] aarch64: clear ZA state of SME before clone syscall

enh enh@google.com
Thu May 22 12:29:09 GMT 2025


given the kernel workaround in [1], is this useful? is it likely that
glibc will run on a kernel old enough to not have that patch, but new
enough to have the other kernel patches you need for SME to work?
(there wasn't a release in that gap, was that?)

On Thu, May 22, 2025 at 7:49 AM Yury Khrustalev <yury.khrustalev@arm.com> wrote:
>
> Call __libc_arm_za_disable before syscall to clear ZA state of SME for the
> clone and clone3 syscalls.
>
> As indicated in [1], not clearing this state may cause a variety of issues.
> We clear this state immediately before making a syscall as this is safe to
> do anyway and prevents those problems from happening.
>
> The __libc_arm_za_disable function would do nothing if SME is not available.
>
> Also add relevant tests for clone and clone3 use cases. While the former is
> trivial, the latter is a bit complex since the clone3 symbol is not public.
>
> To avoid having to check all possible ways clone3() may be called via other
> public functions (e.g. fork(), vfork(), pthread_create()), we put together a
> somewhat synthetic test that links directly with clone3.o. All the existing
> functions that have calls to clone3() may not actually use it, in which case
> the outcome of this test would be unexpected. Having a direct call to the
> clone3 symbol in the test allows to check precisely what we need to test:
> that the __arm_za_disable() function is called as per the SME ABI [2].
>
> Since we use unusual approach when linking test for the clone3 use case, to
> keep things simple, we actually call __arm_za_disable() provided by libgcc
> since GCC 14 instead of using the internal Glibc implementation, but that
> is OK for the purposes of this test since both functions do the same thing
> while the one from libgcc doesn't have extra link-time dependencies.
>
> [1]: https://lore.kernel.org/linux-arm-kernel/20250508132644.1395904-14-mark.rutland@arm.com/
> [2]: https://github.com/ARM-software/abi-aa/blob/main/aapcs64/aapcs64.rst
> ---
>  sysdeps/aarch64/Makefile                 |   7 ++
>  sysdeps/aarch64/tst-sme-clone.c          |  93 +++++++++++++++
>  sysdeps/aarch64/tst-sme-clone3.c         | 143 +++++++++++++++++++++++
>  sysdeps/aarch64/tst-sme-helper.h         |  32 +++++
>  sysdeps/aarch64/tst-sme-za-state.c       |  38 +-----
>  sysdeps/unix/sysv/linux/aarch64/clone.S  |  11 ++
>  sysdeps/unix/sysv/linux/aarch64/clone3.S |  11 ++
>  7 files changed, 301 insertions(+), 34 deletions(-)
>  create mode 100644 sysdeps/aarch64/tst-sme-clone.c
>  create mode 100644 sysdeps/aarch64/tst-sme-clone3.c
>
> diff --git a/sysdeps/aarch64/Makefile b/sysdeps/aarch64/Makefile
> index 0fc6cf1693..51eb294b74 100644
> --- a/sysdeps/aarch64/Makefile
> +++ b/sysdeps/aarch64/Makefile
> @@ -76,6 +76,13 @@ tests += \
>    tst-sme-jmp \
>    tst-sme-za-state \
>    # tests
> +tests-internal += \
> +  tst-sme-clone \
> +  tst-sme-clone3 \
> +  # tests-internal
> +
> +$(objpfx)tst-sme-clone3: $(objpfx)clone3.o
> +
>  endif
>
>  ifeq ($(subdir),malloc)
> diff --git a/sysdeps/aarch64/tst-sme-clone.c b/sysdeps/aarch64/tst-sme-clone.c
> new file mode 100644
> index 0000000000..2e801ba17b
> --- /dev/null
> +++ b/sysdeps/aarch64/tst-sme-clone.c
> @@ -0,0 +1,93 @@
> +/* Test that ZA state of SME is cleared in both parent and child
> +   when clone() syscall is used.
> +   Copyright (C) 2025 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 <stdio.h>
> +#include <clone3.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <sys/auxv.h>
> +
> +#include <support/check.h>
> +#include <support/xsched.h>
> +#include <support/xunistd.h>
> +
> +#include <support/support.h>
> +#include <support/test-driver.h>
> +
> +#include "tst-sme-helper.h"
> +
> +static int
> +fun (void * const arg)
> +{
> +  printf ("in child: %s\n", (const char *)arg);
> +  /* Check that ZA state of SME was disabled in child.  */
> +  check_sme_za_state ("after clone in child", /* Clear.  */ true);
> +  return 0;
> +}
> +
> +static char __attribute__((aligned(16)))
> +stack[1024 * 1024];
> +
> +static void
> +run (struct blk *ptr)
> +{
> +  char *syscall_name = (char *)"clone";
> +  printf ("in parent: before %s\n", syscall_name);
> +
> +  /* Enabled ZA state so that effect of disabling be observable.  */
> +  enable_sme_za_state (ptr);
> +  check_sme_za_state ("before clone", /* Clear.  */ false);
> +
> +  pid_t pid = xclone (fun, syscall_name, stack, sizeof (stack),
> +                     CLONE_NEWUSER | CLONE_NEWNS | SIGCHLD);
> +
> +  /* Check that ZA state of SME was disabled in parent.  */
> +  check_sme_za_state ("after clone in parent", /* Clear.  */ true);
> +
> +  TEST_VERIFY (xwaitpid (pid, NULL, 0) == pid);
> +}
> +
> +static int
> +do_test (void)
> +{
> +  unsigned long hwcap2 = getauxval (AT_HWCAP2);
> +  if ((hwcap2 & HWCAP2_SME) == 0)
> +    return EXIT_UNSUPPORTED;
> +
> +  /* Get current streaming SVE vector register size.  */
> +  svl = get_svl ();
> +  printf ("svl: %lu\n", svl);
> +  TEST_VERIFY_EXIT (!(svl < 16 || svl % 16 != 0 || svl >= (1 << 16)));
> +
> +  /* Initialise buffer for ZA state of SME.  */
> +  sme_state = xmalloc (svl * svl);
> +  memset (sme_state, 1, svl * svl);
> +  struct blk blk = {
> +    .za_save_buffer = sme_state,
> +    .num_za_save_slices = svl,
> +    .__reserved = {0},
> +  };
> +
> +  run (&blk);
> +
> +  return 0;
> +}
> +
> +#include <support/test-driver.c>
> +
> diff --git a/sysdeps/aarch64/tst-sme-clone3.c b/sysdeps/aarch64/tst-sme-clone3.c
> new file mode 100644
> index 0000000000..8aca369500
> --- /dev/null
> +++ b/sysdeps/aarch64/tst-sme-clone3.c
> @@ -0,0 +1,143 @@
> +/* Test that ZA state of SME is cleared in both parent and child
> +   when clone3() syscall is used.
> +   Copyright (C) 2025 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 <stdio.h>
> +#include <clone3.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <errno.h>
> +#include <sys/auxv.h>
> +#include <sys/wait.h>
> +
> +#include <support/check.h>
> +#include <support/xsched.h>
> +#include <support/xunistd.h>
> +#include <support/support.h>
> +#include <support/test-driver.h>
> +
> +#include "tst-sme-helper.h"
> +
> +/* Since clone3 is not a public symbol, we link this test explicitly
> +   with clone3.o and have to provide this declaration.  */
> +int __clone3 (struct clone_args *cl_args, size_t size,
> +           int (*func)(void *arg), void *arg);
> +
> +static int
> +fun (void * const arg)
> +{
> +  printf ("in child: %s\n", (const char *)arg);
> +  /* Check that ZA state of SME was disabled in child.  */
> +  check_sme_za_state ("after clone3 in child", /* Clear.  */ true);
> +  return 0;
> +}
> +
> +static char __attribute__((aligned(16)))
> +stack[1024 * 1024];
> +
> +static void
> +run (struct blk *ptr)
> +{
> +  char *syscall_name = (char *)"clone3";
> +  struct clone_args args = {
> +    .flags = CLONE_VM | CLONE_VFORK,
> +    .exit_signal = SIGCHLD,
> +    .stack = (uintptr_t) stack,
> +    .stack_size = sizeof (stack),
> +  };
> +  printf ("in parent: before %s\n", syscall_name);
> +
> +  /* Enabled ZA state so that effect of disabling be observable.  */
> +  enable_sme_za_state (ptr);
> +  check_sme_za_state ("before clone", /* Clear.  */ false);
> +
> +  pid_t pid = __clone3 (&args, sizeof (args), fun, syscall_name);
> +
> +  /* Check that ZA state of SME was disabled in parent.  */
> +  check_sme_za_state ("after clone in parent", /* Clear.  */ true);
> +
> +  printf ("%s child pid: %d\n", syscall_name, pid);
> +  if (pid == -1)
> +    {
> +      if (errno == ENOSYS)
> +        {
> +          puts ("clone3 syscall is not supported");
> +          exit (EXIT_UNSUPPORTED);
> +        }
> +      perror ("clone3");
> +      TEST_VERIFY_EXIT (0);
> +    }
> +  if (waitid (P_PID, pid, NULL, WEXITED))
> +    {
> +      perror ("waitid");
> +      TEST_VERIFY_EXIT (0);
> +    }
> +  printf ("in parent: after %s\n", syscall_name);
> +}
> +
> +static int
> +do_test (void)
> +{
> +  unsigned long hwcap2 = getauxval (AT_HWCAP2);
> +  if ((hwcap2 & HWCAP2_SME) == 0)
> +    return EXIT_UNSUPPORTED;
> +
> +  /* Get current streaming SVE vector register size.  */
> +  svl = get_svl ();
> +  printf ("svl: %lu\n", svl);
> +  TEST_VERIFY_EXIT (!(svl < 16 || svl % 16 != 0 || svl >= (1 << 16)));
> +
> +  /* Initialise buffer for ZA state of SME.  */
> +  sme_state = xmalloc (svl * svl);
> +  memset (sme_state, 1, svl * svl);
> +  struct blk blk = {
> +    .za_save_buffer = sme_state,
> +    .num_za_save_slices = svl,
> +    .__reserved = {0},
> +  };
> +
> +  run (&blk);
> +
> +  return 0;
> +}
> +
> +#include <support/test-driver.c>
> +
> +/* Workaround to simplify linking with clone3.o.  */
> +void __syscall_error(int code)
> +{
> +  int err = -code;
> +  fprintf (stderr, "syscall error %d (%s)\n", err, strerror (err));
> +  exit (err);
> +}
> +
> +/* Provided by libgcc since GCC 14.  */
> +extern void __arm_za_disable (void);
> +
> +/* We don't want to pull all the dependencies of Glibc's implementation
> +   of __arm_za_disable, so we use symbol from libgcc because it is
> +   available for this test executable.  */
> +void __libc_arm_za_disable (void)
> +{
> +#if defined __GNUC__ && __GNUC__ >= 14
> +  __arm_za_disable ();
> +#else
> +  puts ("compiler doesn't support SME");
> +  exit (EXIT_UNSUPPORTED);
> +#endif
> +}
> diff --git a/sysdeps/aarch64/tst-sme-helper.h b/sysdeps/aarch64/tst-sme-helper.h
> index f049416c2b..46477a64bd 100644
> --- a/sysdeps/aarch64/tst-sme-helper.h
> +++ b/sysdeps/aarch64/tst-sme-helper.h
> @@ -95,3 +95,35 @@ set_tpidr2 (struct blk *blk)
>      ".inst   0xd51bd0a0  /* msr     tpidr2_el0, x0  */\n"
>      :: "r"(x0) : "memory");
>  }
> +
> +/* Check if SME state is disabled (when CLEAR is true) or
> +   enabled (when CLEAR is false).  */
> +static void __attribute__ ((unused))
> +check_sme_za_state (const char msg[], bool clear)
> +{
> +  unsigned long svcr = get_svcr ();
> +  void *tpidr2 = get_tpidr2 ();
> +  printf ("[%s]\n", msg);
> +  printf ("svcr = %016lx\n", svcr);
> +  printf ("tpidr2 = %016lx\n", (unsigned long)tpidr2);
> +  if (clear)
> +    {
> +      TEST_VERIFY (svcr == 0);
> +      TEST_VERIFY (tpidr2 == NULL);
> +    }
> +  else
> +    {
> +      TEST_VERIFY (svcr != 0);
> +      TEST_VERIFY (tpidr2 != NULL);
> +    }
> +}
> +
> +static uint8_t *sme_state;
> +
> +static void __attribute__ ((unused))
> +enable_sme_za_state (struct blk *ptr)
> +{
> +  set_tpidr2 (ptr);
> +  start_za ();
> +  load_za (sme_state);
> +}
> diff --git a/sysdeps/aarch64/tst-sme-za-state.c b/sysdeps/aarch64/tst-sme-za-state.c
> index 63f6eebeb4..2ed1a6b70b 100644
> --- a/sysdeps/aarch64/tst-sme-za-state.c
> +++ b/sysdeps/aarch64/tst-sme-za-state.c
> @@ -28,36 +28,6 @@
>
>  #include "tst-sme-helper.h"
>
> -static uint8_t *state;
> -
> -static void
> -enable_sme_za_state (struct blk *ptr)
> -{
> -  set_tpidr2 (ptr);
> -  start_za ();
> -  load_za (state);
> -}
> -
> -static void
> -check_sme_za_state (const char msg[], bool clear)
> -{
> -  unsigned long svcr = get_svcr ();
> -  void *tpidr2 = get_tpidr2 ();
> -  printf ("[%s]\n", msg);
> -  printf ("svcr = %016lx\n", svcr);
> -  printf ("tpidr2 = %016lx\n", (unsigned long)tpidr2);
> -  if (clear)
> -    {
> -      TEST_VERIFY (svcr == 0);
> -      TEST_VERIFY (tpidr2 == NULL);
> -    }
> -  else
> -    {
> -      TEST_VERIFY (svcr != 0);
> -      TEST_VERIFY (tpidr2 != NULL);
> -    }
> -}
> -
>  static void
>  run (struct blk *ptr)
>  {
> @@ -102,17 +72,17 @@ do_test (void)
>    TEST_VERIFY_EXIT (!(svl < 16 || svl % 16 != 0 || svl >= (1 << 16)));
>
>    /* Initialise buffer for ZA state of SME.  */
> -  state = xmalloc (svl * svl);
> -  memset (state, 1, svl * svl);
> +  sme_state = xmalloc (svl * svl);
> +  memset (sme_state, 1, svl * svl);
>    struct blk blk = {
> -    .za_save_buffer = state,
> +    .za_save_buffer = sme_state,
>      .num_za_save_slices = svl,
>      .__reserved = {0},
>    };
>
>    run (&blk);
>
> -  free (state);
> +  free (sme_state);
>    return 0;
>  }
>
> diff --git a/sysdeps/unix/sysv/linux/aarch64/clone.S b/sysdeps/unix/sysv/linux/aarch64/clone.S
> index 40015c6933..66812e7efd 100644
> --- a/sysdeps/unix/sysv/linux/aarch64/clone.S
> +++ b/sysdeps/unix/sysv/linux/aarch64/clone.S
> @@ -45,6 +45,17 @@ ENTRY(__clone)
>         and     x1, x1, -16
>         cbz     x1, .Lsyscall_error
>
> +       /* Clear ZA state of SME.  */
> +       /* The calling convention of __libc_arm_za_disable allows to do
> +          this thus allowing to avoid saving to and reading from stack.
> +          As a result we also don't need to sign the return address and
> +          check it after returning because it is not stored to stack.  */
> +       mov     x13, x30
> +       cfi_register (x30, x13)
> +       bl      __libc_arm_za_disable
> +       mov     x30, x13
> +       cfi_register (x13, x30)
> +
>         /* Do the system call.  */
>         /* X0:flags, x1:newsp, x2:parenttidptr, x3:newtls, x4:childtid.  */
>         mov     x0, x2                  /* flags  */
> diff --git a/sysdeps/unix/sysv/linux/aarch64/clone3.S b/sysdeps/unix/sysv/linux/aarch64/clone3.S
> index c9ca845ef2..b2e0d10eb2 100644
> --- a/sysdeps/unix/sysv/linux/aarch64/clone3.S
> +++ b/sysdeps/unix/sysv/linux/aarch64/clone3.S
> @@ -46,6 +46,17 @@ ENTRY(__clone3)
>         cbz     x10, .Lsyscall_error    /* No NULL cl_args pointer.  */
>         cbz     x2, .Lsyscall_error     /* No NULL function pointer.  */
>
> +       /* Clear ZA state of SME.  */
> +       /* The calling convention of __libc_arm_za_disable allows to do
> +          this thus allowing to avoid saving to and reading from stack.
> +          As a result we also don't need to sign the return address and
> +          check it after returning because it is not stored to stack.  */
> +       mov     x13, x30
> +       cfi_register (x30, x13)
> +       bl      __libc_arm_za_disable
> +       mov     x30, x13
> +       cfi_register (x13, x30)
> +
>         /* Do the system call, the kernel expects:
>            x8: system call number
>            x0: cl_args
> --
> 2.39.5
>


More information about the Libc-alpha mailing list