[glibc/arm/malloc-mte-v3] malloc: aarch64: Add MTE memory tagging

Yury Khrustalev via Glibc-cvs <[email protected]> Mon, 1 Jun 2026 11:43:42 +0000 (GMT)
Newsgroups gmane.comp.lib.glibc.cvs
Message-ID <[email protected]>
https://sourceware.org/git/gitweb.cgi?p=glibc.git;h=d5d5f03c8facfc0e72514dba42e3bbaeb1bf1fe6

commit d5d5f03c8facfc0e72514dba42e3bbaeb1bf1fe6
Author: Yury Khrustalev <[email protected]>
Date:   Fri May 29 13:44:25 2026 +0100

    malloc: aarch64: Add MTE memory tagging
    
    Tag memory on systems that support MTE and when memory
    tagging is enabled at runtime.
    
    Currently we implement tagging as a wrapper around core malloc
    functions. User pointers are expected to be tagged and internal
    malloc pointers are untagged (or rather tagged with 0 tag).
    
    Also add tests that check logical and allocation tags.

Diff:
---
 sysdeps/aarch64/multiarch/malloc-mte.c             | 119 +++++++++++++---
 sysdeps/unix/sysv/linux/aarch64/Makefile           |  27 ++++
 sysdeps/unix/sysv/linux/aarch64/tst-mte-helper.h   | 110 +++++++++++++++
 .../sysv/linux/aarch64/tst-mte-malloc-static.c     |   1 +
 sysdeps/unix/sysv/linux/aarch64/tst-mte-malloc.c   | 154 +++++++++++++++++++++
 .../sysv/linux/aarch64/tst-mte-realloc-static.c    |   1 +
 sysdeps/unix/sysv/linux/aarch64/tst-mte-realloc.c  |  72 ++++++++++
 7 files changed, 467 insertions(+), 17 deletions(-)

diff --git a/sysdeps/aarch64/multiarch/malloc-mte.c b/sysdeps/aarch64/multiarch/malloc-mte.c
index aeb5e1d3ca..8d9125ef34 100644
--- a/sysdeps/aarch64/multiarch/malloc-mte.c
+++ b/sysdeps/aarch64/multiarch/malloc-mte.c
@@ -16,76 +16,161 @@
    License along with the GNU C Library; if not, see
    <http://www.gnu.org/licenses/>.  */
 
+#include "aarch64-mte.h"
+
 #include <malloc-ifuncs.h>
+#include <errno.h>
+
+#define TAG_MEM(ptr, tagfun) __glibc_unlikely (ptr == NULL) ? NULL : ({ \
+  size_t size = __malloc_usable_size (ptr); \
+  tagfun (__mte_new_tag (ptr), size); \
+})
+
+#define UNTAG_MEM(non_null_ptr) ({ \
+  void *untagged = __mte_clear_tag (non_null_ptr); \
+  size_t size = __malloc_usable_size (untagged); \
+  __mte_tag_region (untagged, size); \
+})
 
 void *__libc_malloc_mte (size_t bytes)
 {
-  return __libc_malloc (bytes);
+  void *untagged = __libc_malloc (bytes);
+  return TAG_MEM (untagged, __mte_tag_region);
 }
 libc_hidden_def (__libc_malloc_mte)
 
 void *__libc_calloc_mte (size_t n, size_t elem_size)
 {
-  return __libc_calloc (n, elem_size);
+  /* We use core malloc instead of calloc because we can
+     take advantage of MTE to zero memory region.  */
+  void *untagged = __libc_malloc (n * elem_size);
+  return TAG_MEM (untagged, __mte_tag_region_zero);
 }
 libc_hidden_def (__libc_calloc_mte)
 
 void *__libc_memalign_mte (size_t alignment, size_t bytes)
 {
-  return __libc_memalign (alignment, bytes);
+  void *untagged = __libc_memalign (alignment, bytes);
+  return TAG_MEM (untagged, __mte_tag_region);
 }
 libc_hidden_def (__libc_memalign_mte)
 
 void *__libc_valloc_mte (size_t bytes)
 {
-  return __libc_valloc (bytes);
+  void *untagged = __libc_valloc (bytes);
+  return TAG_MEM (untagged, __mte_tag_region);
 }
 libc_hidden_def (__libc_valloc_mte)
 
 void *__libc_pvalloc_mte (size_t bytes)
 {
-  return __libc_pvalloc (bytes);
+  void *untagged = __libc_pvalloc (bytes);
+  return TAG_MEM (untagged, __mte_tag_region);
 }
 libc_hidden_def (__libc_pvalloc_mte)
 
-void *__libc_realloc_mte (void *oldmem, size_t bytes)
+/* See malloc.c for details.  */
+#ifndef REALLOC_ZERO_BYTES_FREES
+#define REALLOC_ZERO_BYTES_FREES 1
+#endif
+
+void *__libc_realloc_mte (void *tagged_oldmem, size_t bytes)
 {
-  return __libc_realloc (oldmem, bytes);
+  /* Quick check: realloc of null is supposed to be same as malloc.  */
+  if (tagged_oldmem == NULL)
+    return __libc_malloc_mte (bytes);
+
+#if REALLOC_ZERO_BYTES_FREES
+  /* Quick check: realloc with 0 size is supposed to be same as free.  */
+  if (bytes == 0)
+    {
+      __libc_free_mte (tagged_oldmem);
+      return NULL;
+    }
+#endif
+
+  /* Bad size, old memory remains unchanged.  */
+  if (bytes > PTRDIFF_MAX)
+    {
+      __set_errno (ENOMEM);
+      return NULL;
+    }
+
+  /* At this point we untag oldmem allocation.  */
+  void *untagged_oldmem = __mte_clear_tag (tagged_oldmem);
+
+  /* Mark the chunk as belonging to the library again.  */
+  size_t size_old = __malloc_usable_size (untagged_oldmem);
+  untagged_oldmem = __mte_tag_region (untagged_oldmem, size_old);
+
+  /* Call realloc core.  */
+  void *untagged_newmem = __libc_realloc (untagged_oldmem, bytes);
+  if (untagged_newmem == NULL)
+    return NULL;
+  size_t size_new = __malloc_usable_size (untagged_newmem);
+
+  /* If realloc core returns old pointer, we need re-tag it.  */
+  if (size_new == size_old && untagged_newmem == untagged_oldmem)
+    return __mte_tag_region (tagged_oldmem, size_new);
+
+  /* Otherwise, assign new tag.  */
+  void *tagged_newmem = __mte_new_tag (untagged_newmem);
+  return __mte_tag_region (tagged_newmem, size_new);
 }
 libc_hidden_def (__libc_realloc_mte)
 
-void __libc_free_mte (void *mem)
+void __libc_free_mte (void *tagged)
 {
-  __libc_free (mem);
+  if (__glibc_unlikely (tagged == NULL))
+    return;
+  /* Mark the chunk as belonging to the library again.  */
+  void *untagged = UNTAG_MEM (tagged);
+  /* Call free core.  */
+  __libc_free (untagged);
 }
 libc_hidden_def (__libc_free_mte)
 
-size_t __malloc_usable_size_mte (void *m)
+size_t __malloc_usable_size_mte (void *tagged)
 {
-  return __malloc_usable_size (m);
+  /* Clear only logical tag to allow accessing internal malloc
+     structures via offset from this pointer.  */
+  void *untagged = __mte_clear_tag (tagged);
+  return __malloc_usable_size (untagged);
 }
 libc_hidden_def (__malloc_usable_size_mte)
 
 int __posix_memalign_mte (void **memptr, size_t alignment, size_t size)
 {
-  return __posix_memalign (memptr, alignment, size);
+  /* Call core function.  */
+  int err = __posix_memalign (memptr, alignment, size);
+  if (err != 0)
+    return err;
+  *memptr = TAG_MEM (*memptr, __mte_tag_region);
+  return err;
 }
 libc_hidden_def (__posix_memalign_mte)
 
 void *__aligned_alloc_mte (size_t alignment, size_t bytes)
 {
-  return __aligned_alloc (alignment, bytes);
+  void *untagged = __aligned_alloc (alignment, bytes);
+  return TAG_MEM (untagged, __mte_tag_region);
 }
 libc_hidden_def (__aligned_alloc_mte)
 
-void __free_sized_mte (void *ptr, size_t size)
+void __free_sized_mte (void *tagged, size_t size)
 {
-  __free_sized (ptr, size);
+  /* Mark the chunk as belonging to the library again.  */
+  void *untagged = UNTAG_MEM (tagged);
+  /* Call core function.  */
+  __free_sized (untagged, size);
 }
 libc_hidden_def (__free_sized_mte)
 
-void __free_aligned_sized_mte (void *ptr, size_t alignment, size_t size)
+void __free_aligned_sized_mte (void *tagged, size_t alignment, size_t size)
 {
-  __free_aligned_sized (ptr, alignment, size);
+  /* Mark the chunk as belonging to the library again.  */
+  void *untagged = UNTAG_MEM (tagged);
+  /* Call core function.  */
+  __free_aligned_sized (untagged, alignment, size);
 }
 libc_hidden_def (__free_aligned_sized_mte)
diff --git a/sysdeps/unix/sysv/linux/aarch64/Makefile b/sysdeps/unix/sysv/linux/aarch64/Makefile
index e7baa47458..0c15ae4ae2 100644
--- a/sysdeps/unix/sysv/linux/aarch64/Makefile
+++ b/sysdeps/unix/sysv/linux/aarch64/Makefile
@@ -372,6 +372,33 @@ gen-as-const-headers += ucontext_i.sym
 endif
 
 ifeq ($(subdir),malloc)
+ifneq ($(multi-arch),no)
+# Special test that check MTE memory tagging
+tests-for-mte += \
+  tst-mte-malloc \
+  tst-mte-malloc-static \
+  tst-mte-realloc \
+  tst-mte-realloc-static \
+  # tests-for-mte
+
+tests += $(tests-for-mte)
+
+tests-static += \
+  tst-mte-malloc-static \
+  tst-mte-realloc-static \
+  # tests-static
+
+CFLAGS-tst-mte-malloc.o += -march=armv9-a+memtag
+CFLAGS-tst-mte-malloc-static.o += -march=armv9-a+memtag
+CFLAGS-tst-mte-realloc.o += -march=armv9-a+memtag
+CFLAGS-tst-mte-realloc-static.o += -march=armv9-a+memtag
+
+tests-exclude-malloc-check += $(tests-for-mte)
+tests-exclude-mcheck += $(tests-for-mte)
+tests-exclude-hugetlb1 += $(tests-for-mte)
+tests-exclude-hugetlb2 += $(tests-for-mte)
+endif # ifneq ($(multi-arch),no)
+
 # Add MTE tunable to all malloc tests except malloc-check and mcheck variants
 tests-malloc-mte = \
   $(tests) \
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..acfb189530
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/aarch64/tst-mte-helper.h
@@ -0,0 +1,110 @@
+/* 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
+bool check_tags (void *tm)
+{
+  size_t len = malloc_usable_size (tm);
+  TEST_VERIFY (len % GRANULE_SIZE == 0);
+
+  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);
+      if (ltag != atag)
+	{
+          printf ("tagged ptr: %p usable size: %zu\n", tm, len);
+	  printf ("tags mismatch at offset %zu: logical=%lu, allocation=%lu\n",
+		  offset, ltag, atag);
+          return false;
+	}
+    }
+  return ltag != 0;
+}
+
+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
diff --git a/sysdeps/unix/sysv/linux/aarch64/tst-mte-malloc-static.c b/sysdeps/unix/sysv/linux/aarch64/tst-mte-malloc-static.c
new file mode 100644
index 0000000000..43f7f54008
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/aarch64/tst-mte-malloc-static.c
@@ -0,0 +1 @@
+#include "tst-mte-malloc.c"
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..1747224e47
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/aarch64/tst-mte-malloc.c
@@ -0,0 +1,154 @@
+/* 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 <array_length.h>
+
+#include "tst-mte-helper.h"
+
+/* Characteristic malloc sizes to cover various allocation methods.  */
+size_t sizes[] = {
+  1,
+  16, 40, 64, 120,
+  128, 500, 1000,
+  1050, 4096, 5000, 65000,
+  131072, 2000000
+};
+
+static void check_malloc (size_t len)
+{
+  printf ("testing malloc with req size %zu\n", len);
+  void *tm = malloc (len);
+  if (!check_tags (tm))
+    printf ("tagged pointer?: %016lx\n", (uintptr_t)tm);
+  free (tm);
+}
+
+static void check_calloc (size_t len)
+{
+  size_t num = len / sizeof (uint64_t) + 1;
+  printf ("testing calloc with req size %zu\n", num * sizeof (uint64_t));
+  uint64_t *tm = calloc (num, sizeof (uint64_t));
+  if (check_tags (tm))
+    for (int n = 0; n < num; n ++)
+      TEST_VERIFY_EXIT (tm[n] == 0);
+  else
+    printf ("tagged pointer?: %016lx\n", (uintptr_t)tm);
+  free (tm);
+}
+
+static void check_memalign (size_t len, size_t alignment)
+{
+  printf ("testing memalign(%zu) with req size %zu\n", alignment, len);
+  void *tm = memalign (alignment, len);
+  if (!check_tags (tm))
+    printf ("tagged pointer?: %016lx\n", (uintptr_t)tm);
+  free (tm);
+}
+
+static void check_valloc (size_t len)
+{
+  printf ("testing valloc with req size %zu\n", len);
+  void *tm = valloc (len);
+  if (!check_tags (tm))
+    printf ("tagged pointer?: %016lx\n", (uintptr_t)tm);
+  free_sized (tm, len);
+}
+
+static void check_pvalloc (size_t len)
+{
+  printf ("testing pvalloc with req size %zu\n", len);
+  void *tm = pvalloc (len);
+  if (!check_tags (tm))
+    printf ("tagged pointer?: %016lx\n", (uintptr_t)tm);
+  free_sized (tm, len);
+}
+
+static void check_posix_memalign (size_t len, size_t alignment)
+{
+  printf ("testing posix_memalign(%zu) with req size %zu\n", alignment, len);
+  void *p = NULL;
+  int err = posix_memalign (&p, alignment, len);
+  if (err)
+    perror ("posix_memalign");
+  TEST_VERIFY (p != NULL);
+  TEST_VERIFY (err == 0);
+  if (!check_tags (p))
+    printf ("tagged pointer?: %016lx\n", (uintptr_t)p);
+  free_aligned_sized (p, alignment, len);
+}
+
+static void check_aligned_alloc (size_t len, size_t alignment)
+{
+  printf ("testing aligned_alloc(%zu) with req size %zu\n", alignment, len);
+  void *tm = aligned_alloc (alignment, len);
+  if (!check_tags (tm))
+    printf ("tagged pointer?: %016lx\n", (uintptr_t)tm);
+  free_aligned_sized (tm, alignment, len);
+}
+
+static int
+do_test (void)
+{
+  /* Check if MTE is supported, configured and enabled.  */
+  check_mte_enabled ();
+
+  array_foreach_const (plen, sizes)
+    check_malloc (*plen);
+
+  array_foreach_const (plen, sizes)
+    check_calloc (*plen);
+
+  array_foreach_const (plen, sizes)
+    {
+      check_memalign (*plen, 2);
+      check_memalign (*plen, 4);
+      check_memalign (*plen, 8);
+      check_memalign (*plen, 16);
+      check_memalign (*plen, 32);
+    }
+
+  array_foreach_const (plen, sizes)
+    check_valloc (*plen);
+
+  array_foreach_const (plen, sizes)
+    check_pvalloc (*plen);
+
+  array_foreach_const (plen, sizes)
+    {
+      check_posix_memalign (*plen, sizeof (void *) * 1);
+      check_posix_memalign (*plen, sizeof (void *) * 2);
+      check_posix_memalign (*plen, sizeof (void *) * 4);
+    }
+
+  array_foreach_const (plen, sizes)
+    {
+      check_aligned_alloc (*plen, 2);
+      check_aligned_alloc (*plen, 4);
+      check_aligned_alloc (*plen, 8);
+      check_aligned_alloc (*plen, 16);
+      check_aligned_alloc (*plen, 32);
+    }
+
+  return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/sysdeps/unix/sysv/linux/aarch64/tst-mte-realloc-static.c b/sysdeps/unix/sysv/linux/aarch64/tst-mte-realloc-static.c
new file mode 100644
index 0000000000..9ee758128c
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/aarch64/tst-mte-realloc-static.c
@@ -0,0 +1 @@
+#include "tst-mte-realloc.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..e7197b58fe
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/aarch64/tst-mte-realloc.c
@@ -0,0 +1,72 @@
+/* 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 <array_length.h>
+
+#include "tst-mte-helper.h"
+
+/* Characteristic malloc sizes to cover various allocation methods.  */
+size_t sizes[] = {
+  1,
+  16, 40, 64, 120,
+  128, 500, 1000,
+  1050, 4096, 5000, 65000,
+  131072, 2000000
+};
+
+static void check_realloc (size_t len)
+{
+  /* Tagged pointers.  */
+  void *tm, *new_tm;
+
+  printf ("testing realloc (NULL) for req size %zu\n", len);
+  tm = realloc (NULL, len);
+  check_tags (tm);
+
+  /* Reduce size.  */
+  printf ("testing realloc (decreased size) for req size %zu\n", len);
+  new_tm = realloc (tm, len / 2 + 1);
+  check_tags (new_tm);
+
+  /* Increase size.  */
+  printf ("testing realloc (increased size) for req size %zu\n", len);
+  new_tm = realloc (new_tm, len + 2);
+  check_tags (new_tm);
+
+  free (new_tm);
+}
+
+static int
+do_test (void)
+{
+
+  /* Check if MTE is supported, configured and enabled.  */
+  check_mte_enabled ();
+
+  array_foreach_const (plen, sizes)
+    check_realloc (*plen);
+
+  return 0;
+}
+
+#include <support/test-driver.c>
+