[PATCH v2] aarch64: Add MTE mode tunable

Yury Khrustalev yury.khrustalev@arm.com
Mon Sep 7 14:02:12 GMT 2026


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/

---
 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>
-- 
2.47.3



More information about the Libc-alpha mailing list