[PATCH 2/2] malloc: change tunable glibc.mem.tagging to glibc.mem.aarch64_mte

Yury Khrustalev yury.khrustalev@arm.com
Fri Mar 20 11:36:57 GMT 2026


The 'glibc.mem.tagging' tunable was added as a generic tunable although
at the time memory tagging was only supported by aarch64.

The values for such a tunable are likely to be target-dependent.

This commit removes 'glibc.mem.tagging' and instead adds a new target
specific tunable 'glibc.mem.aarch64_mte' that accepts string values:

 - 'none': memory tagging is disabled (the default).
 - 'auto': enable CPU preferred tag checking mode.
 - 'sync': enable synchronous tag check fault mode.
 - 'async': enable asynchronous tag check fault mode.

This tunable affects the flags used for the PR_TAGGED_ADDR_ENABLE
prctl syscall and only has effect when the system supports HWCAP2_MTE.

This commit also changes when this prctl syscall happens. There are
three stages:

 1) Check if the system supports HWCAP2_MTE and get the value of
    the 'glibc.mem.aarch64_mte' tunable if it does.
 2) Check ELF marking and amend 'aarch64_mte' value if necessary
    (note: this commit doesn't add any actual checks here, just
    the way to implement them if required).
 3) Make the prctl syscall based on the 'aarch64_mte' value.

If some MTE mode was requested but the prctl call failed, it is an error.

The Glibc manual has been updated accordingly.

This commit also adds 2 new tests for memory tagging in malloc on
aarch64 and fixes a related small issue in __libc_free unblocking
most straightforward use cases. Note, however, that memtag support
in malloc remains experimental.
---
 elf/dl-tunables.list                          |   5 -
 malloc/arena.c                                |   5 +-
 malloc/malloc.c                               |   2 +-
 manual/tunables.texi                          |  36 ++----
 sysdeps/aarch64/Makefile                      |   1 +
 sysdeps/aarch64/cpu-features.h                |  11 +-
 sysdeps/aarch64/dl-diagnostics-cpu.c          |   3 +-
 sysdeps/aarch64/dl-mte.c                      |  65 +++++++++++
 sysdeps/aarch64/dl-prop.h                     |  11 ++
 sysdeps/aarch64/dl-start.S                    |   4 +
 sysdeps/aarch64/dl-tunables.list              |   5 +
 sysdeps/aarch64/libc-mtag.h                   |  20 +++-
 sysdeps/generic/libc-mtag.h                   |  15 +++
 sysdeps/unix/sysv/linux/aarch64/Makefile      |  17 +++
 .../unix/sysv/linux/aarch64/cpu-features.c    |  51 +++++----
 .../unix/sysv/linux/aarch64/dl-procruntime.c  |  16 +++
 sysdeps/unix/sysv/linux/aarch64/libc-start.h  |   9 ++
 .../unix/sysv/linux/aarch64/tst-mte-helper.h  | 104 ++++++++++++++++++
 .../unix/sysv/linux/aarch64/tst-mte-malloc.c  |  68 ++++++++++++
 .../unix/sysv/linux/aarch64/tst-mte-realloc.c |  64 +++++++++++
 20 files changed, 448 insertions(+), 64 deletions(-)
 create mode 100644 sysdeps/aarch64/dl-mte.c
 create mode 100644 sysdeps/unix/sysv/linux/aarch64/tst-mte-helper.h
 create mode 100644 sysdeps/unix/sysv/linux/aarch64/tst-mte-malloc.c
 create mode 100644 sysdeps/unix/sysv/linux/aarch64/tst-mte-realloc.c

diff --git a/elf/dl-tunables.list b/elf/dl-tunables.list
index 040a544c0e..e4e4ed4c92 100644
--- a/elf/dl-tunables.list
+++ b/elf/dl-tunables.list
@@ -108,11 +108,6 @@ glibc {
   }
 
   mem {
-    tagging {
-      type: INT_32
-      minval: 0
-      maxval: 255
-    }
     decorate_maps {
       type: INT_32
       minval: 0
diff --git a/malloc/arena.c b/malloc/arena.c
index 2f894d21e9..8c09aefce1 100644
--- a/malloc/arena.c
+++ b/malloc/arena.c
@@ -253,14 +253,13 @@ __ptmalloc_init (void)
 #endif
 
 #ifdef USE_AARCH64_MTAG_HEAP
-  if ((TUNABLE_GET_FULL (glibc, mem, tagging, int32_t, NULL) & 1) != 0)
+  if (__libc_mtag_enabled ())
     {
-      /* If the tunable says that we should be using tagged memory
+      /* If we should be using tagged memory
 	 and that morecore does not support tagged regions, then
 	 disable it.  */
       if (__MTAG_SBRK_UNTAGGED)
 	__always_fail_morecore = true;
-
       mtag_enabled = true;
       mtag_mmap_flags = __MTAG_MMAP_FLAGS;
     }
diff --git a/malloc/malloc.c b/malloc/malloc.c
index 7de6599d30..0f5bc08d8c 100644
--- a/malloc/malloc.c
+++ b/malloc/malloc.c
@@ -3341,7 +3341,7 @@ __libc_free (void *mem)
 	}
 
       if (__glibc_unlikely (tcache_inactive ()))
-	return tcache_free_init (mem);
+        return tcache_free_init (chunk2mem (p));
     }
 #endif
 
diff --git a/manual/tunables.texi b/manual/tunables.texi
index 72769428e8..772148b2fa 100644
--- a/manual/tunables.texi
+++ b/manual/tunables.texi
@@ -40,7 +40,6 @@ glibc.malloc.perturb: 0 (min: 0, max: 255)
 glibc.cpu.x86_shared_cache_size: 0x100000 (min: 0x0, max: 0xffffffffffffffff)
 glibc.pthread.rseq: 1 (min: 0, max: 1)
 glibc.cpu.prefer_map_32bit_exec: 0 (min: 0, max: 1)
-glibc.mem.tagging: 0 (min: 0, max: 255)
 glibc.malloc.hugetlb: 0x0 (min: 0x0, max: 0xffffffffffffffff)
 glibc.cpu.x86_rep_movsb_threshold: 0x2000 (min: 0x100, max: 0xffffffffffffffff)
 glibc.malloc.mxfast: 0x0 (min: 0x0, max: 0xffffffffffffffff)
@@ -663,32 +662,21 @@ This tunable namespace supports operations that affect the way @theglibc{}
 and the process manage memory.
 @end deftp
 
-@deftp Tunable glibc.mem.tagging
-If the hardware supports memory tagging, this tunable can be used to
-control the way @theglibc{} uses this feature.  At present this is only
-supported on AArch64 systems with the MTE extension; it is ignored for
-all other systems.
+@deftp Tunable glibc.mem.aarch64_mte
+On AArch64 systems that support the Memory Tagging Extension (MTE) extension
+this tunable allows to select the tag check fault mode (MTE mode). This
+tunable only has effect when @theglibc{} has been configured with
+@code{--enable-memory-tagging}.
 
-This tunable takes a value between 0 and 255 and acts as a bitmask
-that enables various capabilities.
+Available values are:
 
-Bit 0 (the least significant bit) causes the @code{malloc}
-subsystem to allocate
-tagged memory, with each allocation being assigned a random tag.
-
-Bit 1 enables precise faulting mode for tag violations on systems that
-support deferred tag violation reporting.  This may cause programs
-to run more slowly.
-
-Bit 2 enables either precise or deferred faulting mode for tag violations
-whichever is preferred by the system.
-
-Other bits are currently reserved.
-
-@Theglibc{} startup code will automatically enable memory tagging
-support in the kernel if this tunable has any non-zero value.
+@itemize @bullet
+@item @code{none}: (the default), memory tagging is disabled.
+@item @code{auto}: enable CPU preferred tag checking mode.
+@item @code{sync}: enable synchronous tag check fault mode.
+@item @code{async}: enable asynchronous tag check fault mode.
+@end itemize
 
-The default value is @samp{0}, which disables all memory tagging.
 @end deftp
 
 @deftp Tunable glibc.mem.decorate_maps
diff --git a/sysdeps/aarch64/Makefile b/sysdeps/aarch64/Makefile
index d6c5cc96ca..dfbc33d2f4 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 a6ec3eebe9..413f5aca82 100644
--- a/sysdeps/aarch64/cpu-features.h
+++ b/sysdeps/aarch64/cpu-features.h
@@ -62,13 +62,20 @@ enum {
   BTI_CHECK_ENFORCED = 1,
 };
 
+enum
+{
+  MTE_TUNABLE_NONE  = 0,
+  MTE_TUNABLE_AUTO  = 1,
+  MTE_TUNABLE_SYNC  = 2,
+  MTE_TUNABLE_ASYNC = 3,
+};
+
 struct cpu_features
 {
   uint64_t midr_el1;
   unsigned zva_size;
   bool bti;
-  /* Currently, the GLIBC memory tagging tunable only defines 8 bits.  */
-  uint8_t mte_state;
+  uint8_t mte;
   bool sve;
   bool unused;
   bool mops;
diff --git a/sysdeps/aarch64/dl-diagnostics-cpu.c b/sysdeps/aarch64/dl-diagnostics-cpu.c
index 697868cb25..8e605f523f 100644
--- a/sysdeps/aarch64/dl-diagnostics-cpu.c
+++ b/sysdeps/aarch64/dl-diagnostics-cpu.c
@@ -45,8 +45,7 @@ _dl_diagnostics_cpu (void)
   print_cpu_features_value ("midr_el1",
                             GLRO (dl_aarch64_cpu_features).midr_el1);
   print_cpu_features_value ("mops", GLRO (dl_aarch64_cpu_features).mops);
-  print_cpu_features_value ("mte_state",
-                            GLRO (dl_aarch64_cpu_features).mte_state);
+  print_cpu_features_value ("mte", GLRO (dl_aarch64_cpu_features).mte);
   print_cpu_features_value ("sve", GLRO (dl_aarch64_cpu_features).sve);
   print_cpu_features_value ("zva_size",
                             GLRO (dl_aarch64_cpu_features).zva_size);
diff --git a/sysdeps/aarch64/dl-mte.c b/sysdeps/aarch64/dl-mte.c
new file mode 100644
index 0000000000..000e3727f0
--- /dev/null
+++ b/sysdeps/aarch64/dl-mte.c
@@ -0,0 +1,65 @@
+/* AArch64 MTE (memory tagging) functions.
+   Copyright (C) 2026 Free Software Foundation, Inc.
+
+   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/>.  */
+
+#if defined USE_AARCH64_MTAG_HEAP || defined USE_AARCH64_MTAG_STACK
+
+#include <ldsodefs.h>
+#include <libc-mtag.h>
+
+/* 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
+_dl_mte_check (struct link_map *l, const char *program)
+{
+  if (!GLRO (dl_aarch64_cpu_features).mte)
+    return;
+  /* TODO: Update GL (dl_aarch64_mte) as necessary.  */
+}
+
+void __libc_mtag_init (void)
+{
+  /* HWCAP2_MTE is not supported: nothing to do.  */
+  if (!GLRO (dl_aarch64_cpu_features).mte)
+    return;
+  int t = GL (dl_aarch64_mte);
+  uint64_t flags = PR_TAGGED_ADDR_ENABLE | MTE_ALLOWED_TAGS;
+  switch (t)
+    {
+    case MTE_TUNABLE_NONE:
+      return;
+    case MTE_TUNABLE_AUTO:
+      flags |= PR_MTE_TCF_SYNC | PR_MTE_TCF_ASYNC;
+      break;
+    case MTE_TUNABLE_SYNC:
+      flags |= PR_MTE_TCF_SYNC;
+      break;
+    case MTE_TUNABLE_ASYNC:
+      flags |= PR_MTE_TCF_ASYNC;
+      break;
+    default:
+      _dl_fatal_printf ("unknown MTE tunable value: %d\n", t);
+    }
+  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: %d\n", -r);
+}
+
+#endif /* USE_AARCH64_MTAG_HEAP || USE_AARCH64_MTAG_STACK */
diff --git a/sysdeps/aarch64/dl-prop.h b/sysdeps/aarch64/dl-prop.h
index cf236df59b..e9e14a9693 100644
--- a/sysdeps/aarch64/dl-prop.h
+++ b/sysdeps/aarch64/dl-prop.h
@@ -27,11 +27,19 @@ extern void _dl_bti_check (struct link_map *, const char *)
 extern void _dl_gcs_check (struct link_map *, const char *, int)
     attribute_hidden;
 
+#if defined USE_AARCH64_MTAG_HEAP || defined USE_AARCH64_MTAG_STACK
+extern void _dl_mte_check (struct link_map *, const char *)
+    attribute_hidden;
+#endif
+
 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);
+#if defined USE_AARCH64_MTAG_HEAP || defined USE_AARCH64_MTAG_STACK
+  _dl_mte_check (m, program);
+#endif
 }
 
 static inline void __attribute__ ((always_inline))
@@ -39,6 +47,9 @@ _dl_open_check (struct link_map *m, int dlopen_mode)
 {
   _dl_bti_check (m, NULL);
   _dl_gcs_check (m, NULL, dlopen_mode);
+#if defined USE_AARCH64_MTAG_HEAP || defined USE_AARCH64_MTAG_STACK
+  _dl_mte_check (m, NULL);
+#endif
 }
 
 static inline void __attribute__ ((always_inline))
diff --git a/sysdeps/aarch64/dl-start.S b/sysdeps/aarch64/dl-start.S
index c278485cd3..e730dd6ec8 100644
--- a/sysdeps/aarch64/dl-start.S
+++ b/sysdeps/aarch64/dl-start.S
@@ -66,6 +66,10 @@ ENTRY (_start)
 	cbnz	w0, L(failed_gcs_lock)
 L(skip_gcs_enable):
 
+#if defined USE_AARCH64_MTAG_HEAP || defined USE_AARCH64_MTAG_STACK
+	bl	__libc_mtag_init
+#endif
+
 .globl _dl_start_user
 .type _dl_start_user, %function
 _dl_start_user:
diff --git a/sysdeps/aarch64/dl-tunables.list b/sysdeps/aarch64/dl-tunables.list
index a2ccba0b29..e153387971 100644
--- a/sysdeps/aarch64/dl-tunables.list
+++ b/sysdeps/aarch64/dl-tunables.list
@@ -34,4 +34,9 @@ glibc {
       default: 0
     }
   }
+  mem {
+    aarch64_mte {
+      type: STRING
+    }
+  }
 }
diff --git a/sysdeps/aarch64/libc-mtag.h b/sysdeps/aarch64/libc-mtag.h
index f821a2087e..69a05e8de4 100644
--- a/sysdeps/aarch64/libc-mtag.h
+++ b/sysdeps/aarch64/libc-mtag.h
@@ -21,9 +21,17 @@
 
 #ifndef USE_AARCH64_MTAG_HEAP
 /* Generic bindings for systems that do not support memory tagging.  */
-#include_next "libc-mtag.h"
+# include_next "libc-mtag.h"
 #else
 
+#ifndef PR_SET_TAGGED_ADDR_CTRL
+# 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)
+#endif
+
 /* Used to ensure additional alignment when objects need to have distinct
    tags.  */
 #define __MTAG_GRANULE_SIZE 16
@@ -64,6 +72,16 @@ __libc_mtag_new_tag (void *p)
   return x0;
 }
 
+/* Check if memory tagging is enabled.  */
+static inline bool
+__libc_mtag_enabled (void)
+{
+  return GL (dl_aarch64_mte) != MTE_TUNABLE_NONE;
+}
+
+/* Init memory tagging support.  */
+void __libc_mtag_init (void);
+
 #endif /* USE_AARCH64_MTAG_HEAP */
 
 #endif /* _AARCH64_LIBC_MTAG_H */
diff --git a/sysdeps/generic/libc-mtag.h b/sysdeps/generic/libc-mtag.h
index 5477bfa17f..85e51d17f1 100644
--- a/sysdeps/generic/libc-mtag.h
+++ b/sysdeps/generic/libc-mtag.h
@@ -70,4 +70,19 @@ __libc_mtag_new_tag (void *p)
   return p;
 }
 
+/* Generic version of check if memory tagging is enabled.  */
+static inline bool
+__libc_mtag_enabled (void)
+{
+  __libc_mtag_link_error ();
+  return false;
+}
+
+/* Generic version of init memory tagging support.  */
+static inline void
+__libc_mtag_init (void)
+{
+  __libc_mtag_link_error ();
+}
+
 #endif /* _GENERIC_LIBC_MTAG_H */
diff --git a/sysdeps/unix/sysv/linux/aarch64/Makefile b/sysdeps/unix/sysv/linux/aarch64/Makefile
index 57461fded7..25d158237d 100644
--- a/sysdeps/unix/sysv/linux/aarch64/Makefile
+++ b/sysdeps/unix/sysv/linux/aarch64/Makefile
@@ -11,6 +11,23 @@ LDFLAGS-tst-tlsdesc-pac = -rdynamic
 $(objpfx)tst-tlsdesc-pac.out: $(objpfx)tst-tlsdesc-pac-mod.so
 endif
 
+ifeq ($(subdir),malloc)
+ifeq (yes,$(memory-tagging-heap))
+
+tests += \
+  tst-mte-malloc \
+  tst-mte-realloc \
+  # tests
+
+CFLAGS-tst-mte-malloc.o += -march=armv9-a+memtag
+CFLAGS-tst-mte-realloc.o += -march=armv9-a+memtag
+
+tst-mte-malloc-ENV = GLIBC_TUNABLES=glibc.mem.aarch64_mte=sync
+tst-mte-realloc-ENV = GLIBC_TUNABLES=glibc.mem.aarch64_mte=sync
+
+endif # ifeq (yes,$(memory-tagging-heap))
+endif # ifeq ($(subdir),malloc)
+
 ifeq ($(subdir),misc)
 sysdep_headers += sys/elf.h
 tests += \
diff --git a/sysdeps/unix/sysv/linux/aarch64/cpu-features.c b/sysdeps/unix/sysv/linux/aarch64/cpu-features.c
index 62dcea3f94..d6dcc5ca85 100644
--- a/sysdeps/unix/sysv/linux/aarch64/cpu-features.c
+++ b/sysdeps/unix/sysv/linux/aarch64/cpu-features.c
@@ -20,7 +20,6 @@
 #include <cpu-features.h>
 #include <sys/auxv.h>
 #include <elf/dl-hwcaps.h>
-#include <sys/prctl.h>
 #include <sys/utsname.h>
 #include <dl-tunables-parse.h>
 #include <dl-symbol-redir-ifunc.h>
@@ -65,6 +64,21 @@ get_midr_from_mcpu (const struct tunable_str_t *mcpu)
   return UINT64_MAX;
 }
 
+#if defined USE_AARCH64_MTAG_HEAP || defined USE_AARCH64_MTAG_STACK
+static void
+TUNABLE_CALLBACK (set_aarch64_mte) (tunable_val_t *val)
+{
+  if (tunable_strcmp_cte (val, "auto"))
+    GL (dl_aarch64_mte) = MTE_TUNABLE_AUTO;
+  else if (tunable_strcmp_cte (val, "sync"))
+    GL (dl_aarch64_mte) = MTE_TUNABLE_SYNC;
+  else if (tunable_strcmp_cte (val, "async"))
+    GL (dl_aarch64_mte) = MTE_TUNABLE_ASYNC;
+  else
+    GL (dl_aarch64_mte) = MTE_TUNABLE_NONE;
+}
+#endif /* USE_AARCH64_MTAG_HEAP || USE_AARCH64_MTAG_STACK */
+
 static inline void
 init_cpu_features (struct cpu_features *cpu_features)
 {
@@ -101,31 +115,16 @@ 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);
 
-  /* Setup memory tagging support if the HW and kernel support it, and if
-     the user has requested it.  */
-  cpu_features->mte_state = 0;
-
-#ifdef USE_AARCH64_MTAG_HEAP
-  int mte_state = TUNABLE_GET (glibc, mem, tagging, unsigned, 0);
-  cpu_features->mte_state = (GLRO (dl_hwcap2) & HWCAP2_MTE) ? mte_state : 0;
-  /* If we lack the MTE feature, disable the tunable, since it will
-     otherwise cause instructions that won't run on this CPU to be used.  */
-  TUNABLE_SET (glibc, mem, tagging, cpu_features->mte_state);
-
-  if (cpu_features->mte_state & 4)
-    /* Enable choosing system-preferred faulting mode.  */
-    __prctl (PR_SET_TAGGED_ADDR_CTRL,
-	     (PR_TAGGED_ADDR_ENABLE | PR_MTE_TCF_SYNC | PR_MTE_TCF_ASYNC
-	      | MTE_ALLOWED_TAGS),
-	     0, 0, 0);
-  else if (cpu_features->mte_state & 2)
-    __prctl (PR_SET_TAGGED_ADDR_CTRL,
-	     (PR_TAGGED_ADDR_ENABLE | PR_MTE_TCF_SYNC | MTE_ALLOWED_TAGS),
-	     0, 0, 0);
-  else if (cpu_features->mte_state)
-    __prctl (PR_SET_TAGGED_ADDR_CTRL,
-	     (PR_TAGGED_ADDR_ENABLE | PR_MTE_TCF_ASYNC | MTE_ALLOWED_TAGS),
-	     0, 0, 0);
+  /* Check if MTE is supported.  */
+  cpu_features->mte = 0;
+  GL (dl_aarch64_mte) = MTE_TUNABLE_NONE;
+#if defined USE_AARCH64_MTAG_HEAP || defined USE_AARCH64_MTAG_STACK
+  if (GLRO (dl_hwcap2) & HWCAP2_MTE)
+    {
+      cpu_features->mte = 1;
+      TUNABLE_GET (glibc, mem, aarch64_mte, tunable_val_t *,
+		   TUNABLE_CALLBACK (set_aarch64_mte));
+    }
 #endif /* USE_AARCH64_MTAG_HEAP */
 
   /* Check if SVE is supported.  */
diff --git a/sysdeps/unix/sysv/linux/aarch64/dl-procruntime.c b/sysdeps/unix/sysv/linux/aarch64/dl-procruntime.c
index 1f3b58d0fc..9689ddce25 100644
--- a/sysdeps/unix/sysv/linux/aarch64/dl-procruntime.c
+++ b/sysdeps/unix/sysv/linux/aarch64/dl-procruntime.c
@@ -35,3 +35,19 @@ PROCINFO_CLASS unsigned long _dl_aarch64_gcs
 ,
 # endif
 #endif
+
+#if !IS_IN (ldconfig)
+# if !defined PROCINFO_DECL && defined SHARED
+  ._dl_aarch64_mte
+# else
+PROCINFO_CLASS unsigned long _dl_aarch64_mte
+# endif
+# ifndef PROCINFO_DECL
+= 0
+# endif
+# if !defined SHARED || defined PROCINFO_DECL
+;
+# else
+,
+# endif
+#endif
diff --git a/sysdeps/unix/sysv/linux/aarch64/libc-start.h b/sysdeps/unix/sysv/linux/aarch64/libc-start.h
index 4ccd13741b..5377462cda 100644
--- a/sysdeps/unix/sysv/linux/aarch64/libc-start.h
+++ b/sysdeps/unix/sysv/linux/aarch64/libc-start.h
@@ -36,6 +36,10 @@
 #  define GCS_POLICY_OPTIONAL 2
 # endif
 
+#if defined USE_AARCH64_MTAG_HEAP || defined USE_AARCH64_MTAG_STACK
+# include <libc-mtag.h>
+#endif
+
 /* 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 +76,11 @@ aarch64_libc_setup_tls (void)
 	    _dl_fatal_printf ("failed to lock GCS: %d\n", -ret);
 	}
     }
+
+#if defined USE_AARCH64_MTAG_HEAP || defined USE_AARCH64_MTAG_STACK
+  __libc_mtag_init ();
+#endif
+
 }
 
 # define ARCH_SETUP_IREL() apply_irel ()
diff --git a/sysdeps/unix/sysv/linux/aarch64/tst-mte-helper.h b/sysdeps/unix/sysv/linux/aarch64/tst-mte-helper.h
new file mode 100644
index 0000000000..eab7222464
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/aarch64/tst-mte-helper.h
@@ -0,0 +1,104 @@
+/* AArch64 test helper functions for MTE.
+   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/>.  */
+
+#ifndef TST_MTE_HELPER_H
+#define TST_MTE_HELPER_H
+
+#include <support/check.h>
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <malloc.h>
+#include <sys/auxv.h>
+#include <sys/prctl.h>
+
+#define GRANULE_SIZE 16
+
+/* Extract logical tag from pointer PTR.  */
+static __always_inline
+uint64_t get_logical_tag (const void *ptr)
+{
+  uint64_t t = (uint64_t)ptr;
+  return t >> 56ul & 0xf;
+}
+
+/* Load allocation tag from memory pointed-to by the PTR pointer.  */
+static __always_inline
+uint64_t get_allocation_tag (const void *ptr)
+{
+  uint64_t t;
+  asm volatile ("ldg %0, [%1]" : "=r" (t) : "r" (ptr));
+  return t >> 56ul & 0xf;
+}
+
+/* Read the Tag Check Override bit.  */
+static __always_inline
+uint64_t get_pstate_tco (void) {
+  uint64_t t;
+  asm volatile ("mrs %0, tco" : "=r" (t));
+  return t;
+}
+
+static __always_inline
+void check_tags (void *tm)
+{
+  size_t len = malloc_usable_size (tm);
+  TEST_VERIFY (len % GRANULE_SIZE == 0);
+
+  printf ("tagged ptr: %p usable size: %zu\n", tm, len);
+
+  uint64_t ltag = get_logical_tag (tm);
+  TEST_VERIFY (ltag != 0);
+
+  for (size_t offset = 0; offset < len; offset += GRANULE_SIZE)
+    {
+      const char *g = (char *)tm + offset;
+      uint64_t atag = get_allocation_tag (g);
+      TEST_COMPARE (ltag, atag);
+    }
+}
+
+static __always_inline
+void check_mte_enabled (void)
+{
+  /* Check if MTE is supported.  */
+  if (!(getauxval (AT_HWCAP2) & HWCAP2_MTE))
+    FAIL_UNSUPPORTED ("kernel or CPU does not support HWCAP2_MTE");
+
+  /* Check if Tag Check Override bit is set.  */
+  if (get_pstate_tco () != 0)
+    FAIL_UNSUPPORTED ("MTE tag check override is enabled");
+
+  /* Check applied MTE params.  */
+  uint64_t x = (uint64_t) prctl (PR_GET_TAGGED_ADDR_CTRL, 0, 0, 0, 0);
+  uint64_t status = (x & 1ul);
+  uint64_t mode = (x & PR_MTE_TCF_MASK) >> PR_MTE_TCF_SHIFT;
+  uint64_t tags = (x & PR_MTE_TAG_MASK) >> PR_MTE_TAG_SHIFT;
+
+  printf ("MTE status: %4lx\n", status);
+  printf ("MTE mode:   %4lx\n", mode);
+  printf ("MTE tags:   %4lx\n", tags);
+
+  /* This test should be run in sync mode for tag checks.  */
+  TEST_VERIFY (status == 1);
+  TEST_VERIFY (mode == PR_MTE_TCF_SYNC >> PR_MTE_TCF_SHIFT);
+  TEST_VERIFY (tags == 0xfffe);
+}
+
+#endif // TST_MTE_HELPER_H
\ No newline at end of file
diff --git a/sysdeps/unix/sysv/linux/aarch64/tst-mte-malloc.c b/sysdeps/unix/sysv/linux/aarch64/tst-mte-malloc.c
new file mode 100644
index 0000000000..e52a8c7fd9
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/aarch64/tst-mte-malloc.c
@@ -0,0 +1,68 @@
+/* AArch64 tests for heap 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 <support/check.h>
+#include <support/support.h>
+#include <support/xsignal.h>
+#include <support/test-driver.h>
+
+#include "tst-mte-helper.h"
+
+static int
+do_test (void)
+{
+  /* Check if MTE is supported, configured and enabled.  */
+  check_mte_enabled ();
+
+  /* Tagged pointer.  */
+  void *tm;
+
+  tm = malloc (32);
+  puts ("testing malloc");
+  check_tags (tm);
+  free (tm);
+
+  tm = calloc (2048, 16);
+  puts ("testing calloc");
+  check_tags (tm);
+  free (tm);
+
+  tm = valloc (96);
+  puts ("testing valloc");
+  check_tags (tm);
+  free (tm);
+
+  tm = aligned_alloc (16, 256);
+  puts ("testing aligned_alloc");
+  check_tags (tm);
+  free (tm);
+
+  tm = memalign (16, 2048);
+  puts ("testing memalign");
+  check_tags (tm);
+  free (tm);
+
+  tm = pvalloc (8196);
+  puts ("testing pvalloc");
+  check_tags (tm);
+  free (tm);
+
+  return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/sysdeps/unix/sysv/linux/aarch64/tst-mte-realloc.c b/sysdeps/unix/sysv/linux/aarch64/tst-mte-realloc.c
new file mode 100644
index 0000000000..c39ad5b3e3
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/aarch64/tst-mte-realloc.c
@@ -0,0 +1,64 @@
+/* AArch64 tests for heap 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 <support/check.h>
+#include <support/support.h>
+#include <support/xsignal.h>
+#include <support/test-driver.h>
+
+#include "tst-mte-helper.h"
+
+static int
+do_test (void)
+{
+
+  /* Check if MTE is supported, configured and enabled.  */
+  check_mte_enabled ();
+
+  /* Tagged pointer.  */
+  void *tm, *new_tm;
+  uint64_t ltag;
+
+  tm = realloc (NULL, 4096);
+  ltag = get_logical_tag (tm);
+  puts ("testing realloc (NULL)");
+  check_tags (tm);
+
+  /* Reduce size.  */
+  new_tm = realloc (tm, 64);
+  puts ("testing realloc (decreased size)");
+  check_tags (new_tm);
+
+  /* Check that realloc changes tag.  */
+  TEST_VERIFY (ltag != get_logical_tag (new_tm));
+
+  /* Increase size.  */
+  new_tm = realloc (new_tm, 16000);
+  puts ("testing realloc (increased size)");
+  check_tags (new_tm);
+
+  /* Check that realloc changes tag.  */
+  TEST_VERIFY (ltag != get_logical_tag (new_tm));
+
+  free (new_tm);
+
+  return 0;
+}
+
+#include <support/test-driver.c>
+
-- 
2.47.3



More information about the Libc-alpha mailing list