[PATCH v2] aarch64: Add MTE mode tunable

Adhemerval Zanella Netto adhemerval.zanella@linaro.org
Thu Sep 10 14:30:01 GMT 2026



On 07/09/26 11:02, Yury Khrustalev wrote:
> Add tunable glibc.cpu.mtemode for selecting tag checking fault mode
> for Memory Tagging Extension (MTE): disabled, enabled, sync, or async.
> Default is disabled.
> 
> ---
> Passes regression in aarch64. OK for trunk?
> 
> Please note that this patch does not include support for MTE binary
> marking as there is no intention to implement it at this stage.
> However, this patch is designed to support such an extension in the
> future.
> 
> Relevant discussion:
> https://inbox.sourceware.org/libc-alpha/ansmVLinBE86-SZ4@arm.com/
> 
> Base commit: fe03757f67
> 
> Changes in v2:
>  - Don't add the 'mte' property to the cpu_features struct.
>  - Define bool variable for MTE for ifunc resolvers based on
>    the MTE mode value.
>  - v1: https://inbox.sourceware.org/libc-alpha/20260904094800.1810189-1-yury.khrustalev@arm.com/

It is not clear to me why add this support without any MTE usage within
glibc. All these MTE enablement can done by the application itself, if
it requires so (like a MTE aware malloc).

I think this feature should added along the MTE malloc you seems to be
working on.

> 
> ---
>  manual/tunables.texi                          | 27 ++++++
>  sysdeps/aarch64/Makefile                      |  1 +
>  sysdeps/aarch64/cpu-features.h                | 17 ++++
>  sysdeps/aarch64/dl-mte.c                      | 77 ++++++++++++++++
>  sysdeps/aarch64/dl-prop.h                     |  4 +
>  sysdeps/aarch64/dl-start.S                    | 10 ++-
>  sysdeps/aarch64/dl-tunables.list              |  3 +
>  sysdeps/aarch64/multiarch/init-arch.h         |  3 +-
>  sysdeps/unix/sysv/linux/aarch64/Makefile      |  5 ++
>  .../unix/sysv/linux/aarch64/cpu-features.c    | 22 +++++
>  .../unix/sysv/linux/aarch64/dl-procruntime.c  | 32 +++++++
>  sysdeps/unix/sysv/linux/aarch64/libc-start.h  |  6 ++
>  .../sysv/linux/aarch64/tst-mtemode-tunable.c  | 90 +++++++++++++++++++
>  13 files changed, 292 insertions(+), 5 deletions(-)
>  create mode 100644 sysdeps/aarch64/dl-mte.c
>  create mode 100644 sysdeps/unix/sysv/linux/aarch64/tst-mtemode-tunable.c
> 
> diff --git a/manual/tunables.texi b/manual/tunables.texi
> index 9052c56d9b..bbff2f5fdd 100644
> --- a/manual/tunables.texi
> +++ b/manual/tunables.texi
> @@ -760,6 +760,33 @@ locked.  GCS markings will not be checked for any binaries.
>  
>  @end deftp
>  
> +@deftp Tunable glibc.cpu.mtemode
> +On AArch64 Linux systems that support Memory Tagging Extension (MTE) this
> +tunable allows to select the fault mode for memory tag checking (MTE mode).
> +
> +Supported values are:
> +
> +@itemize @bullet
> +@item @code{disabled}: (the default), memory tagging is not enabled and the
> +CPU and the kernel ignore tag checking faults.
> +@item @code{enabled}: Use system-preferred tag checking mode. This value is
> +intended for general purpose use cases.
> +@item @code{sync}: Select synchronous tag checking fault mode: when tag check
> +fault occurs, a @code{SIGSEGV} is raised synchronously and memory access is not
> +performed. This value is useful for debugging.
> +@item @code{async}: Use asynchronous tag checking fault mode: a @code{SIGSEGV}
> +is raised asynchronously following one or multiple tag check faults.
> +@end itemize
> +
> +Any incorrect value or an empty value of this tunable will result in
> +the @code{enabled} value being used. When tunable is not provided, the
> +default value @code{disabled} is used.
> +
> +The system-preferred MTE mode can be configured via sysfs as described in
> +@url{https://www.kernel.org/doc/html/latest/arch/arm64/memory-tagging-extension.html}.
> +
> +@end deftp
> +
>  @node Memory Related Tunables
>  @section Memory Related Tunables
>  @cindex memory related tunables
> diff --git a/sysdeps/aarch64/Makefile b/sysdeps/aarch64/Makefile
> index 87ce51c821..f046148091 100644
> --- a/sysdeps/aarch64/Makefile
> +++ b/sysdeps/aarch64/Makefile
> @@ -4,6 +4,7 @@ ifeq ($(subdir),elf)
>  sysdep-dl-routines += \
>    dl-bti \
>    dl-gcs \
> +  dl-mte \
>    # sysdep-dl-routines
>  
>  tests += \
> diff --git a/sysdeps/aarch64/cpu-features.h b/sysdeps/aarch64/cpu-features.h
> index b7dab3dff4..022335405c 100644
> --- a/sysdeps/aarch64/cpu-features.h
> +++ b/sysdeps/aarch64/cpu-features.h
> @@ -59,6 +59,23 @@ enum {
>    BTI_CHECK_ENFORCED = 1,
>  };
>  
> +enum {
> +  MTE_MODE_DISABLED = 0,
> +  MTE_MODE_ENABLED = 1,
> +  MTE_MODE_SYNC = 2,
> +  MTE_MODE_ASYNC = 3,
> +};
> +
> +enum
> +{
> +  MTE_SCOPE_NONE = 0,
> +  MTE_SCOPE_HEAP = 1 << 0,
> +  MTE_SCOPE_STACK = 1 << 1,
> +  MTE_SCOPE_GLOBALS = 1 << 2,
> +};
> +
> +#define MTE_SCOPE_STANDARD (MTE_SCOPE_HEAP)
> +
>  struct cpu_features
>  {
>    uint64_t midr_el1;
> diff --git a/sysdeps/aarch64/dl-mte.c b/sysdeps/aarch64/dl-mte.c
> new file mode 100644
> index 0000000000..e4650ed22e
> --- /dev/null
> +++ b/sysdeps/aarch64/dl-mte.c
> @@ -0,0 +1,77 @@
> +/* AArch64 implementation for MTE (memory tagging).
> +   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 <sys/cdefs.h>
> +#include <ldsodefs.h>
> +
> +/* For the MTE prctl syscall.  */
> +#define PR_SET_TAGGED_ADDR_CTRL 55
> +#define PR_MTE_TAG_SHIFT        3
> +#define PR_TAGGED_ADDR_ENABLE   (1UL << 0)
> +#define PR_MTE_TCF_SYNC         (1UL << 1)
> +#define PR_MTE_TCF_ASYNC        (1UL << 2)
> +
> +/* The maximal set of permitted tags that the MTE random tag generation
> +   instruction may use.  We exclude tag 0 because a) we want to reserve
> +   that for the libc heap structures and b) because it makes it easier
> +   to see when pointer have been correctly tagged.  */
> +#define MTE_ALLOWED_TAGS        (0xfffe << PR_MTE_TAG_SHIFT)
> +
> +void __mte_init (void);
> +rtld_hidden_proto (__mte_init)
> +
> +void __mte_init (void)
> +{
> +  unsigned int mode = GL (dl_aarch64_mte_mode);
> +  if (mode == MTE_MODE_DISABLED)
> +    return;
> +  uint64_t flags = PR_TAGGED_ADDR_ENABLE | MTE_ALLOWED_TAGS;
> +  switch (mode)
> +    {
> +    case MTE_MODE_ENABLED:
> +      flags |= PR_MTE_TCF_SYNC | PR_MTE_TCF_ASYNC;
> +      break;
> +    case MTE_MODE_SYNC:
> +      flags |= PR_MTE_TCF_SYNC;
> +      break;
> +    case MTE_MODE_ASYNC:
> +      flags |= PR_MTE_TCF_ASYNC;
> +      break;
> +    default:
> +      _dl_fatal_printf ("unknown MTE mode: %d\n", mode);
> +    }
> +  /* We use inline system call to avoid unnecessary dependency
> +     on the sys/prctl.h header.  */
> +  int r = INLINE_SYSCALL_CALL (prctl, PR_SET_TAGGED_ADDR_CTRL, flags, 0, 0, 0);
> +  if (r == -1)
> +    _dl_fatal_printf ("failed to enable MTE\n");
> +}
> +rtld_hidden_def (__mte_init)
> +
> +void
> +_dl_mte_check (struct link_map *l, const char *program)
> +{
> +  /* MTE binary marking can be checked here when support for memtag ABI
> +     is added.  When the main binary has no MTE marking, we enable only
> +     standard protection when requested MTE mode is not 'none'.
> +     Currently standard protection includes: heap.  */
> +  if (GL (dl_aarch64_mte_mode) == MTE_MODE_DISABLED)
> +    GL (dl_aarch64_mte_scope) = MTE_SCOPE_NONE;
> +  else
> +    GL (dl_aarch64_mte_scope) = MTE_SCOPE_STANDARD;
> +}
> diff --git a/sysdeps/aarch64/dl-prop.h b/sysdeps/aarch64/dl-prop.h
> index 0d2672d32d..26fc639405 100644
> --- a/sysdeps/aarch64/dl-prop.h
> +++ b/sysdeps/aarch64/dl-prop.h
> @@ -27,11 +27,15 @@ extern void _dl_bti_check (struct link_map *, const char *)
>  extern void _dl_gcs_check (struct link_map *, const char *, int)
>      attribute_hidden;
>  
> +extern void _dl_mte_check (struct link_map *, const char *)
> +    attribute_hidden;
> +
>  static inline void __attribute__ ((always_inline))
>  _rtld_main_check (struct link_map *m, const char *program)
>  {
>    _dl_bti_check (m, program);
>    _dl_gcs_check (m, program, 0);
> +  _dl_mte_check (m, program);
>  }
>  
>  static inline void __attribute__ ((always_inline))
> diff --git a/sysdeps/aarch64/dl-start.S b/sysdeps/aarch64/dl-start.S
> index c278485cd3..2d8a2710a6 100644
> --- a/sysdeps/aarch64/dl-start.S
> +++ b/sysdeps/aarch64/dl-start.S
> @@ -33,9 +33,9 @@ ENTRY (_start)
>  	mov	x21, x0
>  
>  	/* Use GL(dl_aarch64_gcs) to set the shadow stack status.  */
> -	adrp	x16, _rtld_local
> -	add	x16, x16, :lo12:_rtld_local
> -	ldr	x22, [x16, GL_DL_AARCH64_GCS_OFFSET]
> +	adrp	x23, _rtld_local
> +	add	x23, x23, :lo12:_rtld_local
> +	ldr	x22, [x23, GL_DL_AARCH64_GCS_OFFSET]
>  	cbz	x22, L(skip_gcs_enable)
>  
>  	/* Enable GCS before user code runs.  Note that IFUNC resolvers and
> @@ -66,6 +66,8 @@ ENTRY (_start)
>  	cbnz	w0, L(failed_gcs_lock)
>  L(skip_gcs_enable):
>  
> +	bl	HIDDEN_JUMPTARGET(__mte_init)
> +
>  .globl _dl_start_user
>  .type _dl_start_user, %function
>  _dl_start_user:
> @@ -77,7 +79,7 @@ _dl_start_user:
>  	add	x3, x2, x1, lsl 3
>  	add	x3, x3, 8
>  	/* Run the init functions of the loaded modules.  */
> -	ldr	x0, [x16]
> +	ldr	x0, [x23]
>  	bl	_dl_init
>  	/* Load the finalizer function.  */
>  	adrp	x0, _dl_fini
> diff --git a/sysdeps/aarch64/dl-tunables.list b/sysdeps/aarch64/dl-tunables.list
> index c876f3fe0d..03491ca2c2 100644
> --- a/sysdeps/aarch64/dl-tunables.list
> +++ b/sysdeps/aarch64/dl-tunables.list
> @@ -33,5 +33,8 @@ glibc {
>        maxval: 3
>        default: 0
>      }
> +    mtemode {
> +      type: STRING
> +    }
>    }
>  }
> diff --git a/sysdeps/aarch64/multiarch/init-arch.h b/sysdeps/aarch64/multiarch/init-arch.h
> index e00d1746d8..4ed3dbfc8b 100644
> --- a/sysdeps/aarch64/multiarch/init-arch.h
> +++ b/sysdeps/aarch64/multiarch/init-arch.h
> @@ -25,7 +25,8 @@
>    unsigned __attribute__((unused)) zva_size =				      \
>      GLRO(dl_aarch64_cpu_features).zva_size;				      \
>    bool __attribute__((unused)) bti = GLRO(dl_aarch64_cpu_features).bti;	      \
> -  bool __attribute__((unused)) mte = GLRO(dl_hwcap2) & HWCAP2_MTE;	      \
> +  bool __attribute__((unused)) mte =					      \
> +    (GL (dl_aarch64_mte_mode) != MTE_MODE_DISABLED);			      \
>    bool __attribute__((unused)) sve = GLRO(dl_aarch64_cpu_features).sve;	      \
>    bool __attribute__((unused)) sve2 = GLRO(dl_aarch64_cpu_features).sve2;     \
>    bool __attribute__((unused)) mops = GLRO(dl_aarch64_cpu_features).mops;
> diff --git a/sysdeps/unix/sysv/linux/aarch64/Makefile b/sysdeps/unix/sysv/linux/aarch64/Makefile
> index baefac15b5..4debe41df6 100644
> --- a/sysdeps/unix/sysv/linux/aarch64/Makefile
> +++ b/sysdeps/unix/sysv/linux/aarch64/Makefile
> @@ -19,6 +19,11 @@ tests-static += \
>    # tests-static
>  tst-cpu-tunable-static-pie-TUNABLES = glibc.cpu.hwcaps=-midr,-sve,-mops
>  endif
> +
> +tests-internal += \
> +  tst-mtemode-tunable \
> +  # tests-internal
> +
>  endif
>  
>  ifeq ($(subdir),misc)
> diff --git a/sysdeps/unix/sysv/linux/aarch64/cpu-features.c b/sysdeps/unix/sysv/linux/aarch64/cpu-features.c
> index 6a13638368..b1da4b0038 100644
> --- a/sysdeps/unix/sysv/linux/aarch64/cpu-features.c
> +++ b/sysdeps/unix/sysv/linux/aarch64/cpu-features.c
> @@ -59,6 +59,21 @@ TUNABLE_CALLBACK (set_hwcaps) (tunable_val_t *val)
>      }
>  }
>  
> +static void
> +TUNABLE_CALLBACK (set_aarch64_mte_mode) (tunable_val_t *val)
> +{
> +  if (tunable_strcmp_cte (val, "enabled"))
> +    GL (dl_aarch64_mte_mode) = MTE_MODE_ENABLED;
> +  else if (tunable_strcmp_cte (val, "sync"))
> +    GL (dl_aarch64_mte_mode) = MTE_MODE_SYNC;
> +  else if (tunable_strcmp_cte (val, "async"))
> +    GL (dl_aarch64_mte_mode) = MTE_MODE_ASYNC;
> +  else if (tunable_strcmp_cte (val, "disabled"))
> +    GL (dl_aarch64_mte_mode) = MTE_MODE_DISABLED;
> +  else
> +    GL (dl_aarch64_mte_mode) = MTE_MODE_ENABLED;
> +}
> +
>  static inline void
>  init_cpu_features (struct cpu_features *cpu_features)
>  {
> @@ -81,6 +96,13 @@ init_cpu_features (struct cpu_features *cpu_features)
>    if (cpu_features->bti)
>      GLRO (dl_aarch64_bti) = TUNABLE_GET (glibc, cpu, aarch64_bti, uint64_t, 0);
>  
> +  /* Check if MTE is supported.  */
> +  if (GLRO (dl_hwcap2) & HWCAP2_MTE)
> +    TUNABLE_GET (glibc, cpu, mtemode, tunable_val_t *,
> +		 TUNABLE_CALLBACK (set_aarch64_mte_mode));
> +  else
> +    GL (dl_aarch64_mte_mode) = MTE_MODE_DISABLED;
> +
>    /* Check if SVE is supported.  */
>    cpu_features->sve = GLRO (dl_hwcap) & HWCAP_SVE;
>    cpu_features->sve2 = GLRO (dl_hwcap2) & HWCAP2_SVE2;
> diff --git a/sysdeps/unix/sysv/linux/aarch64/dl-procruntime.c b/sysdeps/unix/sysv/linux/aarch64/dl-procruntime.c
> index 1f3b58d0fc..3b1f8b93aa 100644
> --- a/sysdeps/unix/sysv/linux/aarch64/dl-procruntime.c
> +++ b/sysdeps/unix/sysv/linux/aarch64/dl-procruntime.c
> @@ -35,3 +35,35 @@ PROCINFO_CLASS unsigned long _dl_aarch64_gcs
>  ,
>  # endif
>  #endif
> +
> +#if !IS_IN (ldconfig)
> +# if !defined PROCINFO_DECL && defined SHARED
> +  ._dl_aarch64_mte_mode
> +# else
> +PROCINFO_CLASS unsigned int _dl_aarch64_mte_mode
> +# endif
> +# ifndef PROCINFO_DECL
> += MTE_MODE_DISABLED
> +# endif
> +# if !defined SHARED || defined PROCINFO_DECL
> +;
> +# else
> +,
> +# endif
> +#endif
> +
> +#if !IS_IN (ldconfig)
> +# if !defined PROCINFO_DECL && defined SHARED
> +  ._dl_aarch64_mte_scope
> +# else
> +PROCINFO_CLASS unsigned int _dl_aarch64_mte_scope
> +# endif
> +# ifndef PROCINFO_DECL
> += MTE_SCOPE_NONE
> +# endif
> +# if !defined SHARED || defined PROCINFO_DECL
> +;
> +# else
> +,
> +# endif
> +#endif
> \ No newline at end of file
> diff --git a/sysdeps/unix/sysv/linux/aarch64/libc-start.h b/sysdeps/unix/sysv/linux/aarch64/libc-start.h
> index 53683ee511..3a7422f70c 100644
> --- a/sysdeps/unix/sysv/linux/aarch64/libc-start.h
> +++ b/sysdeps/unix/sysv/linux/aarch64/libc-start.h
> @@ -36,6 +36,9 @@
>  #  define GCS_POLICY_OPTIONAL 2
>  # endif
>  
> +void __mte_init (void);
> +rtld_hidden_proto (__mte_init)
> +
>  /* Must be on a top-level stack frame that does not return.  */
>  static inline void __attribute__((always_inline))
>  aarch64_libc_setup_tls (void)
> @@ -72,6 +75,9 @@ aarch64_libc_setup_tls (void)
>  	    _dl_fatal_printf ("failed to lock GCS: %d\n", -ret);
>  	}
>      }
> +
> +  __mte_init ();
> +
>  }
>  
>  # define ARCH_SETUP_IREL() apply_irel ()
> diff --git a/sysdeps/unix/sysv/linux/aarch64/tst-mtemode-tunable.c b/sysdeps/unix/sysv/linux/aarch64/tst-mtemode-tunable.c
> new file mode 100644
> index 0000000000..f23a794a17
> --- /dev/null
> +++ b/sysdeps/unix/sysv/linux/aarch64/tst-mtemode-tunable.c
> @@ -0,0 +1,90 @@
> +/* Test that glibc.cpu.mtemode selects corect mode for MTE tag checking.
> +   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 <stdio.h>
> +#include <unistd.h>
> +#include <sys/auxv.h>
> +
> +#include <array_length.h>
> +#include <support/check.h>
> +#include <support/xdlfcn.h>
> +#include <support/support.h>
> +#include <support/capture_subprocess.h>
> +
> +#include <cpu-features.h>
> +#define SHARED
> +#include <ldsodefs.h>
> +
> +static int
> +work (unsigned int expected)
> +{
> +  /* Avoid introducing a copy relocation due to the hidden alias in
> +   ld.so.  */
> +  struct rtld_global *gl = xdlsym (NULL, "_rtld_global");
> +  TEST_COMPARE (gl->_dl_aarch64_mte_mode, expected);
> +  return 0;
> +}
> +
> +static int
> +do_test_argv (int argc, char *argv[])
> +{
> +  if (argc == 2)
> +    return work (atol (argv[1]));
> +
> +  unsigned long hwcap2 = getauxval (AT_HWCAP2);
> +  if ((hwcap2 & HWCAP2_MTE) == 0)
> +    FAIL_UNSUPPORTED ("MTE is not supported by this system");
> +
> +  struct test_entry
> +  {
> +    unsigned int mode;
> +    const char *tunable;
> +  };
> +
> +  struct test_entry tests[] = {
> +    /* Test each explicit tunable value.  */
> +    { .mode = MTE_MODE_DISABLED, .tunable = "glibc.cpu.mtemode=disabled" },
> +    { .mode = MTE_MODE_ENABLED, .tunable = "glibc.cpu.mtemode=enabled" },
> +    { .mode = MTE_MODE_SYNC, .tunable = "glibc.cpu.mtemode=sync" },
> +    { .mode = MTE_MODE_ASYNC, .tunable = "glibc.cpu.mtemode=async" },
> +    /* Test wrong value and no tunable set.  */
> +    { .mode = MTE_MODE_ENABLED, .tunable = "glibc.cpu.mtemode=gibberish" },
> +    { .mode = MTE_MODE_DISABLED, .tunable = "" },
> +  };
> +
> +  array_foreach (t, tests)
> +    {
> +      char *mode = xasprintf ("%u", t->mode);
> +      char *tunable = xasprintf ("GLIBC_TUNABLES=%s", t->tunable);
> +      char *subproc = xasprintf ("tst-mtemode-tunable-%s", t->tunable);
> +      char *spargv[] = { argv[0], mode, NULL, };
> +      char *spenvp[] = { tunable, NULL, };
> +      struct support_capture_subprocess result;
> +      result = support_capture_subprogram (spargv[0], spargv, spenvp);
> +      support_capture_subprocess_check (&result, subproc, 0, sc_allow_none);
> +      support_capture_subprocess_free (&result);
> +      free (mode);
> +      free (tunable);
> +      free (subproc);
> +    }
> +
> +  return 0;
> +}
> +
> +#define TEST_FUNCTION_ARGV do_test_argv
> +#include <support/test-driver.c>



More information about the Libc-alpha mailing list