[glibc/azanella/aarch64-memtag] aarch64: Add initial Memtag ABI Extension to ELF

Adhemerval Zanella via Glibc-cvs <[email protected]>
Newsgroups gmane.comp.lib.glibc.cvs
Message-ID <[email protected]>
https://sourceware.org/git/gitweb.cgi?p=glibc.git;h=a8bcb6c0615dc3cdae238c2b52cc697c51eef2db

commit a8bcb6c0615dc3cdae238c2b52cc697c51eef2db
Author: Adhemerval Zanella <[email protected]>
Date:   Tue Mar 10 16:11:06 2026 +0200

    aarch64: Add initial Memtag ABI Extension to ELF
    
    This patch introduces initial support for the AArch64 Memory Tagging
    Extension (MTE) in the dynamic linker, specifically implementing
    stack tagging (DT_AARCH64_MEMTAG_STACK) and MTE mode configuration
    (DT_AARCH64_MEMTAG_MODE) as defined in the Memtag ABI Extension for
    ELF64 [1].
    
    This feature is guarded by a new configure option,
    --enable-aarch64-memtag-abi, and it is disable by default.
    
    Key additions and behaviors include:
    
    * ELF Tag Parsing & Validation: the dynamic linker now parses the new
      AArch64-specific dynamic tags.  Currently, only stack tagging is
      supported, and if an executable or its dependencies request heap
      tagging (DT_AARCH64_MEMTAG_HEAP) or global tagging
      (DT_AARCH64_MEMTAG_GLOBALS), the dynamic linker will emit an
      unsupported error and fail safely.
    
    * MTE Mode Resolution: the execution mode (synchronous or asynchronous)
      is determined by DT_AARCH64_MEMTAG_MODE.  If absent, the system defaults
      to asynchronous mode.
    
    * Interaction with --enable-memory-tagging: if the glibc build has
      --enable-memory-tagging (USE_MTAG) configured, the standard
      glibc.mem.tagging tunable takes precedence.  The tunable can override
      the MTE mode requested by the ELF ABI (e.g., forcing synchronous mode
      or deferring to system-preferred auto mode) and can independently
      enable heap tagging for the libc allocator.
    
    * Late Loading constraints: if the main executable does not enable
      the MTE stack at startup, any subsequent attempts to dlopen a DSO
      that requires MTE stack support will be rejected to prevent unsafe
      memory access.
    
    A comprehensive set of tests has been added to verify the ABI:
    * tst-memtag-stack-{sync,async}: verifies that compiling with
      -fsanitize=memtag-stack and linking with -z,memtag-mode=...
      correctly sets up the respective execution environment.
    
    * tst-memtag-stack-{dep1,dep2}: ensures that dependencies correctly
      inherit or trigger the MTE setup during the loading phase.
    
    * tst-memtag-stack-dlopen: verifies the dlopen constraints, ensuring
      that an MTE-dependent module fails to load with an appropriate
      dlerror if MTE was not initialized at program startup.
    
    * tst-memtag-stack-sync-tunable: check if the can override the
      dynamic tag value.
    
    * Fault handling is verified across main and spawned threads by
      intentionally triggering MTE boundary violations and asserting
      receipt of SIGSEGV with SEGV_MTESERR/SEGV_MTEAERR.
    
    [1] https://github.com/ARM-software/abi-aa/blob/main/memtagabielf64/memtagabielf64.rst

Diff:
---
 INSTALL                                            |  14 ++
 NEWS                                               |   5 +
 config.h.in                                        |   3 +
 config.make.in                                     |   1 +
 configure                                          |  24 +++
 configure.ac                                       |  14 ++
 elf/Versions                                       |   7 +
 elf/elf.h                                          |   7 +-
 malloc/arena.c                                     |   2 +-
 manual/install.texi                                |  21 ++
 sysdeps/aarch64/Makefile                           |   1 +
 sysdeps/aarch64/configure                          | 103 ++++++++++
 sysdeps/aarch64/configure.ac                       |  25 +++
 sysdeps/aarch64/cpu-features.h                     |  14 +-
 sysdeps/aarch64/dl-diagnostics-cpu.c               |   2 -
 sysdeps/aarch64/dl-mte.c                           | 227 +++++++++++++++++++++
 sysdeps/aarch64/dl-mte.h                           |  35 ++++
 sysdeps/aarch64/dl-prop.h                          |   7 +
 sysdeps/aarch64/dl-start.S                         |   2 +
 sysdeps/aarch64/libc-mtag.h                        |   6 +
 sysdeps/generic/libc-mtag.h                        |   6 +
 sysdeps/unix/sysv/linux/aarch64/Makefile           |  57 ++++++
 sysdeps/unix/sysv/linux/aarch64/cpu-features.c     |  27 ---
 sysdeps/unix/sysv/linux/aarch64/dl-mte-stack.c     |  31 +++
 sysdeps/unix/sysv/linux/aarch64/dl-procruntime.c   |  14 ++
 sysdeps/unix/sysv/linux/aarch64/libc-start.h       |   2 +
 sysdeps/unix/sysv/linux/aarch64/tst-memtag-mod1.c  |   1 +
 sysdeps/unix/sysv/linux/aarch64/tst-memtag-mod2.c  |   1 +
 .../sysv/linux/aarch64/tst-memtag-stack-async.c    |   2 +
 .../sysv/linux/aarch64/tst-memtag-stack-dep1.c     |   3 +
 .../sysv/linux/aarch64/tst-memtag-stack-dep2.c     |  59 ++++++
 .../sysv/linux/aarch64/tst-memtag-stack-dlopen.c   |  45 ++++
 .../sysv/linux/aarch64/tst-memtag-stack-skeleton.c |  74 +++++++
 .../sysv/linux/aarch64/tst-memtag-stack-sync.c     |   2 +
 .../sysv/linux/aarch64/tst-memtag-test-skeleton.c  |  85 ++++++++
 sysdeps/unix/sysv/linux/aarch64/tst-mte-helper.h   |  57 ++++++
 36 files changed, 952 insertions(+), 34 deletions(-)

diff --git a/INSTALL b/INSTALL
index 4174c9661f..2c264c0b96 100644
--- a/INSTALL
+++ b/INSTALL
@@ -299,6 +299,20 @@ passed to 'configure'.  For example:
 
      Default is to disable SFrame support.
 
+'--enable-aarch64-memtag-abi'
+    Enable initial support for the AArch64 Memory Tagging Extension (MTE) ABI.
+    The option implements stack tagging (DT_AARCH64_MEMTAG_STACK) and MTE
+    execution mode configuration (DT_AARCH64_MEMTAG_MODE) as defined in the
+    Memtag ABI Extension for ELF64.
+
+    Unsupported options (DT_AARCH64_MEMTAG_HEAP and DT_AARCH64_MEMTAG_GLOBALS)
+    triggers either startup failure or dlopen errors.
+
+    If `--enable-memory-tagging` is also uses, the `glibc.mem.tagging` tunable
+    takes precedence.
+
+    This option is disabled by default and only support on aarch64.
+
    To build the library and related programs, type 'make'.  This will
 produce a lot of output, some of which may look like errors from 'make'
 but aren't.  Look for error messages from 'make' containing '***'.
diff --git a/NEWS b/NEWS
index c6e9a83923..ea9fc1af1f 100644
--- a/NEWS
+++ b/NEWS
@@ -16,6 +16,11 @@ Major new features:
 * The SVID handling for cosh and sinh were moved to compat symbols, allowing
   improvements in performance.
 
+* On AArch64 now has initial support for the Memtag ABI Extension for ELF64,
+  specifically handling stack tagging (DT_AARCH64_MEMTAG_STACK) and execution
+  mode configuration (DT_AARCH64_MEMTAG_MODE).  This can be enabled using thw
+  new --enable-aarch64-memtag-abi configure option.
+
 Deprecated and removed features, and other changes affecting compatibility:
 
 * Although malloc and related functions currently return pointers
diff --git a/config.h.in b/config.h.in
index b53731c393..65457d7fe3 100644
--- a/config.h.in
+++ b/config.h.in
@@ -185,6 +185,9 @@
 /* Define if memory tagging support should be enabled.  */
 #undef USE_MTAG
 
+/* Define if AArch64 Memtag ABI extension should be enabled.  */
+#undef USE_AARCH64_MEMTAG_ABI
+
 /* Package description.  */
 #undef PKGVERSION
 
diff --git a/config.make.in b/config.make.in
index 856363d0cd..8be2096511 100644
--- a/config.make.in
+++ b/config.make.in
@@ -79,6 +79,7 @@ multi-arch = @multi_arch@
 mach-interface-list = @mach_interface_list@
 
 memory-tagging = @memory_tagging@
+aarch64-memtag-abi = @aarch64_memtag_abi@
 
 # Configuration options.
 build-shared = @shared@
diff --git a/configure b/configure
index c84d283bd5..25ebb86a40 100755
--- a/configure
+++ b/configure
@@ -703,6 +703,7 @@ INSTALL_PROGRAM
 base_machine
 build_pt_chown
 build_nscd
+aarch64_memtag_abi
 memory_tagging
 enable_werror
 force_install
@@ -812,6 +813,7 @@ enable_kernel
 enable_werror
 enable_multi_arch
 enable_memory_tagging
+enable_aarch64_memtag_abi
 enable_systemtap
 enable_build_nscd
 enable_nscd
@@ -1492,6 +1494,9 @@ Optional Features:
                           architectures
   --enable-memory-tagging enable memory tagging if supported by the
                           architecture [default=no]
+  --enable-aarch64-memtag-abi
+                          enable AArch64 Memtag ABI Extension for ELF
+                          [default=no]]
   --enable-systemtap      enable systemtap static probe points [default=no]
   --disable-build-nscd    disable building and installing the nscd daemon
   --disable-nscd          library functions will not contact the nscd daemon
@@ -4710,6 +4715,25 @@ if test "$memory_tagging" = yes; then
 fi
 
 
+# Check whether --enable-aarch64-memtag-abi was given.
+if test ${enable_aarch64_memtag_abi+y}
+then :
+  enableval=$enable_aarch64_memtag_abi; aarch64_memtag_abi=$enableval
+else case e in #(
+  e) aarch64_memtag_abi=no ;;
+esac
+fi
+
+if test "$aarch64_memtag_abi" = yes; then
+  case $host_cpu in
+    aarch64)
+      printf "%s\n" "#define USE_AARCH64_MEMTAG_ABI 1" >>confdefs.h
+
+      ;;
+  esac
+fi
+
+
 # Check whether --enable-systemtap was given.
 if test ${enable_systemtap+y}
 then :
diff --git a/configure.ac b/configure.ac
index 280ec8e5b3..a2275af4ec 100644
--- a/configure.ac
+++ b/configure.ac
@@ -343,6 +343,20 @@ if test "$memory_tagging" = yes; then
 fi
 AC_SUBST(memory_tagging)
 
+AC_ARG_ENABLE([aarch64-memtag-abi],
+	      AS_HELP_STRING([--enable-aarch64-memtag-abi],
+			     [enable AArch64 Memtag ABI Extension for ELF @<:@default=no@:>@]]),
+	      [aarch64_memtag_abi=$enableval],
+	      [aarch64_memtag_abi=no])
+if test "$aarch64_memtag_abi" = yes; then
+  case $host_cpu in
+    aarch64)
+      AC_DEFINE(USE_AARCH64_MEMTAG_ABI)
+      ;;
+  esac
+fi
+AC_SUBST(aarch64_memtag_abi)
+
 AC_ARG_ENABLE([systemtap],
               [AS_HELP_STRING([--enable-systemtap],
 	       [enable systemtap static probe points @<:@default=no@:>@])],
diff --git a/elf/Versions b/elf/Versions
index 1591031da9..824ade26d9 100644
--- a/elf/Versions
+++ b/elf/Versions
@@ -28,6 +28,13 @@ libc {
     # by scripts/versions.awk.
     __placeholder_only_for_empty_version_map;
   }
+%ifdef USE_AARCH64_MEMTAG_ABI
+  GLIBC_ABI_MEMTAG {
+    # This symbol is used only for empty version map and will be removed
+    # by scripts/versions.awk.
+    __placeholder_only_for_empty_version_map;
+  }
+%endif
   GLIBC_PRIVATE {
     # functions used in other libraries
     __libc_early_init;
diff --git a/elf/elf.h b/elf/elf.h
index 46a01281cb..02c4125cb1 100644
--- a/elf/elf.h
+++ b/elf/elf.h
@@ -3039,7 +3039,12 @@ enum
 #define DT_AARCH64_BTI_PLT	(DT_LOPROC + 1)
 #define DT_AARCH64_PAC_PLT	(DT_LOPROC + 3)
 #define DT_AARCH64_VARIANT_PCS	(DT_LOPROC + 5)
-#define DT_AARCH64_NUM		6
+#define DT_AARCH64_MEMTAG_MODE	(DT_LOPROC + 9)
+#define DT_AARCH64_MEMTAG_HEAP	(DT_LOPROC + 11)
+#define DT_AARCH64_MEMTAG_STACK	(DT_LOPROC + 12)
+#define DT_AARCH64_MEMTAG_GLOBALS	(DT_LOPROC + 13)
+#define DT_AARCH64_MEMTAG_GLOBALSSZ	(DT_LOPROC + 15)
+#define DT_AARCH64_NUM		16
 
 /* AArch64 specific values for the st_other field.  */
 #define STO_AARCH64_VARIANT_PCS 0x80
diff --git a/malloc/arena.c b/malloc/arena.c
index ddde32c712..8732721b03 100644
--- a/malloc/arena.c
+++ b/malloc/arena.c
@@ -253,7 +253,7 @@ __ptmalloc_init (void)
 #endif
 
 #ifdef USE_MTAG
-  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
 	 and that morecore does not support tagged regions, then
diff --git a/manual/install.texi b/manual/install.texi
index 073cda0530..99a0023664 100644
--- a/manual/install.texi
+++ b/manual/install.texi
@@ -328,6 +328,27 @@ Currently this is only supported on x86_64 and aarch64.  The option
 enables SFrame support on @code{backtrace}.
 
 Default is to disable SFrame support.
+
+@item --enable-aarch64-memtag-abi
+Enable initial support for the AArch64 Memory Tagging Extension (MTE) ABI.
+The option implements stack tagging (DT_AARCH64_MEMTAG_STACK) and MTE
+execution mode configuration (DT_AARCH64_MEMTAG_MODE) as defined in the
+Memtag ABI Extension for ELF64.
+
+Unsupported options (DT_AARCH64_MEMTAG_HEAP and DT_AARCH64_MEMTAG_GLOBALS)
+triggers either startup failure or dlopen errors.  If the execution mode
+is not specified, the system defaults to asynchronous mode.
+
+If @option{--enable-memory-tagging} is also uses, the @code{glibc.mem.tagging}
+tunable takes precedence.  The tunable can override the MTE mode requested by
+the MEMTAG ABI (e.g., forcing synchronous mode or deferring to the
+system-preferred auto mode) and can independently enable heap tagging.
+
+If the main executable does not enable the MTE stack at startup, any
+subsequent attempts to @code{dlopen} a shared object that requires MTE
+support will be rejected.
+
+This option is disabled by default and only support on aarch64.
 @end table
 
 To build the library and related programs, type @code{make}.  This will
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/configure b/sysdeps/aarch64/configure
index 44b833ea1d..2e1a4c62ca 100755
--- a/sysdeps/aarch64/configure
+++ b/sysdeps/aarch64/configure
@@ -484,3 +484,106 @@ else
 have-test-gcs = no"
 fi
 
+# Check if compiler supports MEMTAG stack support.  Also check if linker
+# support the required options (clang might only support ti for Android
+# builds).
+
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if compiler supports -fsanitize=memtag-stack" >&5
+printf %s "checking if compiler supports -fsanitize=memtag-stack... " >&6; }
+if test ${libc_cv_cc_memtag_stack+y}
+then :
+  printf %s "(cached) " >&6
+else case e in #(
+  e) if { ac_try='${CC-cc} -Werror -march=armv8.5-a+memtag -fsanitize=memtag-stack -xc /dev/null -S -o /dev/null'
+  { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_try\""; } >&5
+  (eval $ac_try) 2>&5
+  ac_status=$?
+  printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; }
+then :
+  libc_cv_cc_memtag_stack=yes
+else case e in #(
+  e) libc_cv_cc_memtag_stack=no ;;
+esac
+fi ;;
+esac
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $libc_cv_cc_memtag_stack" >&5
+printf "%s\n" "$libc_cv_cc_memtag_stack" >&6; }
+if test "$TEST_CC" = "$CC"; then
+  libc_cv_test_cc_memtag_stack=$libc_cv_cc_memtag_stack
+else
+
+saved_CC="$CC"
+CC="$TEST_CC"
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if compiler supports -fsanitize=memtag-stack in testing" >&5
+printf %s "checking if compiler supports -fsanitize=memtag-stack in testing... " >&6; }
+if test ${libc_cv_test_cc_memtag_stack+y}
+then :
+  printf %s "(cached) " >&6
+else case e in #(
+  e) if { ac_try='${CC-cc} -Werror -march=armv8.5-a+memtag -fsanitize=memtag-stack -xc /dev/null -S -o /dev/null'
+  { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_try\""; } >&5
+  (eval $ac_try) 2>&5
+  ac_status=$?
+  printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; }
+then :
+  libc_cv_test_cc_memtag_stack=yes
+else case e in #(
+  e) libc_cv_test_cc_memtag_stack=no
+ ;;
+esac
+fi ;;
+esac
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $libc_cv_test_cc_memtag_stack" >&5
+printf "%s\n" "$libc_cv_test_cc_memtag_stack" >&6; }
+
+CC="$saved_CC"
+
+fi
+
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for linker that supports -z memtag-stack" >&5
+printf %s "checking for linker that supports -z memtag-stack... " >&6; }
+libc_linker_feature=no
+cat > conftest.c <<EOF
+int _start (void) { return 42; }
+EOF
+if { ac_try='${CC-cc} $CFLAGS $CPPFLAGS $LDFLAGS $no_ssp
+		  -Wl,-z,memtag-stack -nostdlib -nostartfiles
+		  -fPIC -shared -o conftest.so conftest.c
+		  1>&5'
+  { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_try\""; } >&5
+  (eval $ac_try) 2>&5
+  ac_status=$?
+  printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; }
+then
+  if ${CC-cc} $CFLAGS $CPPFLAGS $LDFLAGS $no_ssp -Wl,-z,memtag-stack -nostdlib \
+      -nostartfiles -fPIC -shared -o conftest.so conftest.c 2>&1 \
+      | grep "warning: -z memtag-stack ignored" > /dev/null 2>&1; then
+    true
+  else
+    libc_linker_feature=yes
+  fi
+fi
+rm -f conftest*
+if test $libc_linker_feature = yes; then
+  libc_cv_ld_memtag_stack=yes
+else
+  libc_cv_ld_memtag_stack=no
+
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $libc_linker_feature" >&5
+printf "%s\n" "$libc_linker_feature" >&6; }
+if test $libc_cv_test_cc_memtag_stack = yes && \
+   test $libc_cv_ld_memtag_stack = yes
+then
+   config_vars="$config_vars
+have-test-memtag-stack = yes"
+else
+   config_vars="$config_vars
+have-test-memtag-stack = no"
+fi
+
diff --git a/sysdeps/aarch64/configure.ac b/sysdeps/aarch64/configure.ac
index b9981861f6..210c37ca54 100644
--- a/sysdeps/aarch64/configure.ac
+++ b/sysdeps/aarch64/configure.ac
@@ -105,3 +105,28 @@ then
 else
   LIBC_CONFIG_VAR([have-test-gcs], [no])
 fi
+
+# Check if compiler supports MEMTAG stack support.  Also check if linker
+# support the required options (clang might only support ti for Android
+# builds).
+LIBC_TRY_CC_AND_TEST_CC_OPTION(
+  [if compiler supports -fsanitize=memtag-stack],
+  [-Werror -march=armv8.5-a+memtag -fsanitize=memtag-stack],
+  libc_cv_cc_memtag_stack,
+  [libc_cv_cc_memtag_stack=yes],
+  [libc_cv_cc_memtag_stack=no],
+  libc_cv_test_cc_memtag_stack,
+  [libc_cv_test_cc_memtag_stack=yes],
+  [libc_cv_test_cc_memtag_stack=no]
+)
+LIBC_LINKER_FEATURE(
+  [-z memtag-stack], [-Wl,-z,memtag-stack],
+  [libc_cv_ld_memtag_stack=yes], [libc_cv_ld_memtag_stack=no]
+)
+if test $libc_cv_test_cc_memtag_stack = yes && \
+   test $libc_cv_ld_memtag_stack = yes
+then
+   LIBC_CONFIG_VAR([have-test-memtag-stack], [yes])
+else
+   LIBC_CONFIG_VAR([have-test-memtag-stack], [no])
+fi
diff --git a/sysdeps/aarch64/cpu-features.h b/sysdeps/aarch64/cpu-features.h
index d6367a4596..005a19818c 100644
--- a/sysdeps/aarch64/cpu-features.h
+++ b/sysdeps/aarch64/cpu-features.h
@@ -59,15 +59,23 @@ enum {
   BTI_CHECK_ENFORCED = 1,
 };
 
+enum {
+  MTE_MODE_SYNC  = 0x1,
+  MTE_MODE_ASYNC = 0x2,
+  MTE_MODE_AUTO  = 0x4,
+  MTE_MODE_MASK  = MTE_MODE_SYNC | MTE_MODE_ASYNC | MTE_MODE_AUTO,
+  MTE_STACK      = 0x8,
+  MTE_HEAP       = 0x10,
+};
+
 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 unused0;
   bool sve;
-  bool unused;
+  bool unused1;
   bool mops;
 };
 
diff --git a/sysdeps/aarch64/dl-diagnostics-cpu.c b/sysdeps/aarch64/dl-diagnostics-cpu.c
index 697868cb25..4bf244ec71 100644
--- a/sysdeps/aarch64/dl-diagnostics-cpu.c
+++ b/sysdeps/aarch64/dl-diagnostics-cpu.c
@@ -45,8 +45,6 @@ _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 ("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..2447bfa2d6
--- /dev/null
+++ b/sysdeps/aarch64/dl-mte.c
@@ -0,0 +1,227 @@
+/* AArch64 MTE support.
+   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/>.  */
+
+#include <assert.h>
+#include <ldsodefs.h>
+#include <sys/auxv.h>
+#include <dl-tunables.h>
+#include <dl-mte.h>
+#include <dl-prop.h>
+
+#pragma GCC optimize ("O0")
+
+/* 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)
+
+#ifdef USE_AARCH64_MEMTAG_ABI
+
+# define DT_AARCH64(x) (DT_AARCH64_##x - DT_LOPROC + DT_NUM)
+
+static void
+fail (const struct link_map *l, const char *program, const char *mode)
+{
+  if (program != NULL)
+    {
+      if (program[0] != '\0' && l->l_name[0] != '\0')
+	_dl_fatal_printf ("%s: %s: MTE protection %s is not supported\n",
+			  program, l->l_name, mode);
+      if (program[0] != '\0')
+        _dl_fatal_printf ("%s: MTE protection %s is not supported\n",
+			  program, mode);
+      _dl_fatal_printf ("error: MTE protection %s is not supported\n",
+			mode);
+  }
+  else
+    _dl_signal_error (0, l->l_name, "dlopen", "MTE is not enabled");
+}
+
+static void
+fail_mode (const struct link_map *l, const char *program, uint64_t mode)
+{
+  assert (program != NULL);
+  if (program[0] != '\0' && l->l_name[0] != '\0')
+    _dl_fatal_printf ("%s: %s: MTE mode 0x%lx not supported\n",
+		      program, l->l_name, mode);
+  if (program[0] != '\0')
+    _dl_fatal_printf ("%s: MTE mode 0x%lx not supported\n", program, mode);
+  _dl_fatal_printf ("error: MTE mode 0x%lx not supported\n", mode);
+}
+
+static void
+unsupported_stack (void)
+{
+  _dl_fatal_printf ("error: MTE stack required, but kernel does not support MTE\n");
+}
+
+static bool requires_mte_heap (const struct link_map *l)
+{
+  return l->l_info[DT_AARCH64 (MEMTAG_HEAP)] != NULL;
+}
+
+static bool requires_mte_stack (const struct link_map *l)
+{
+  return l->l_info[DT_AARCH64 (MEMTAG_STACK)] != NULL;
+}
+
+static bool requires_mte_globals (const struct link_map *l)
+{
+  return l->l_info[DT_AARCH64 (MEMTAG_GLOBALS)] != NULL
+    || l->l_info[DT_AARCH64 (MEMTAG_GLOBALSSZ)] != NULL;
+}
+
+static void check_mte_mode (const struct link_map *l, const char *program)
+{
+  /* Only the executable are considered for the MTE mode.  */
+  const ElfW(Dyn) *d = l->l_info[DT_AARCH64 (MEMTAG_MODE)];
+  if (d != NULL && l->l_type == lt_executable)
+    {
+      if (d->d_un.d_val == 0)
+	GL(dl_aarch64_mte) |= MTE_MODE_SYNC;
+      else if (d->d_un.d_val == 1)
+	GL(dl_aarch64_mte) |= MTE_MODE_ASYNC;
+      else
+	fail_mode (l, program, d->d_un.d_val);
+    }
+  else
+    /* Use sync by default or if the dynamic tag is not present.  */
+    GL(dl_aarch64_mte) |= MTE_MODE_ASYNC;
+}
+
+static void check_mte (const struct link_map *l, const char *program)
+{
+  if (requires_mte_stack (l))
+    GL(dl_aarch64_mte) |= MTE_STACK;
+  if (requires_mte_heap (l))
+    fail (l, program, "heap");
+  if (requires_mte_globals (l))
+    fail (l, program, "globals");
+}
+#endif
+
+static inline uint8_t mte_mode (void)
+{
+  return GL(dl_aarch64_mte) & MTE_MODE_MASK;
+}
+
+static inline bool enable_mte_stack (void)
+{
+#ifdef USE_AARCH64_MEMTAG_ABI
+  return GL(dl_aarch64_mte) & MTE_STACK;
+#else
+  return false;
+#endif
+}
+
+static inline bool enable_mte_heap (void)
+{
+#ifdef USE_MTAB
+  return GL(dl_aarch64_mte) & MTE_HEAD;
+#else
+  return false;
+#endif
+}
+
+static inline bool enable_mte (void)
+{
+  return enable_mte_stack () || enable_mte_heap ();
+}
+
+void
+_dl_mte_check (struct link_map *l, const char *program)
+{
+#ifdef USE_AARCH64_MEMTAG_ABI
+  check_mte_mode (l, program);
+
+  check_mte (l, program);
+  for (unsigned int i = 0; i < l->l_searchlist.r_nlist; i++)
+    check_mte (l->l_searchlist.r_list[i], program);
+
+  /* For dlopen, if program has not enabled MTE stack at the startup, signal
+     that the module can not be loaded.  */
+  if (enable_mte_stack () && program == NULL)
+    {
+      GL(dl_aarch64_mte) &= ~MTE_STACK;
+
+      fail (l, program, "stack");
+    }
+#endif
+}
+
+void
+_dl_mte_init (void)
+{
+#if defined USE_MTAG || defined USE_AARCH64_MEMTAG_ABI
+  if ((GLRO (dl_hwcap2) & HWCAP2_MTE) == 0 || !enable_mte ())
+    {
+# ifdef USE_AARCH64_MEMTAG_ABI
+      if (GL(dl_aarch64_mte) & MTE_STACK)
+	unsupported_stack ();
+# endif
+      GL(dl_aarch64_mte) = 0;
+      return;
+    }
+
+#ifdef USE_MTAG
+  /* The tunable can override the MTE mode from the MEMTAG ABI.  */
+  if (TUNABLE_IS_INITIALIZED_FULL (glibc, mem, tagging))
+    {
+      int32_t mte_tunable = TUNABLE_GET_FULL (glibc, mem, tagging, int32_t,
+					      NULL);
+      if (mte_tunable & 0x1)
+	GL(dl_aarch64_mte) |= MTE_HEAP;
+      if (mte_tunable & 0x2)
+	{
+	  GL(dl_aarch64_mte) &= ~MTE_MODE_ASYNC;
+	  GL(dl_aarch64_mte) |= MTE_MODE_SYNC;
+	}
+      if (mte_tunable & 0x4)
+	{
+	  /* The documentatation states that it is either precise or deferred
+	     faulting mode, so let the kernel handle it.  */
+	  GL(dl_aarch64_mte) &= ~(MTE_MODE_ASYNC | MTE_MODE_SYNC);
+	  GL(dl_aarch64_mte) |= MTE_MODE_AUTO;
+	}
+    }
+#endif
+
+  uint64_t flags = PR_TAGGED_ADDR_ENABLE | MTE_ALLOWED_TAGS;
+  switch (mte_mode ())
+    {
+    case MTE_MODE_SYNC:
+      flags |= PR_MTE_TCF_SYNC;
+      break;
+    case MTE_MODE_AUTO:
+      flags |= PR_MTE_TCF_SYNC | PR_MTE_TCF_ASYNC;
+      break;
+    case MTE_MODE_ASYNC:
+      flags |= PR_MTE_TCF_ASYNC;
+      break;
+    }
+
+  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
+
+#ifdef USE_AARCH64_MEMTAG_ABI
+  if (enable_mte_stack() && !_dl_mte_setup_stack ())
+    _dl_fatal_printf ("error: MTE stack setup failed\n");
+#endif
+}
diff --git a/sysdeps/aarch64/dl-mte.h b/sysdeps/aarch64/dl-mte.h
new file mode 100644
index 0000000000..3c872ffc7e
--- /dev/null
+++ b/sysdeps/aarch64/dl-mte.h
@@ -0,0 +1,35 @@
+/* AArch64 MTE support.
+   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/>.  */
+
+#ifndef _DL_MTE_H
+#define _DL_MTE_H
+
+#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
+
+#ifndef USE_AARCH64_MEMTAG_ABI
+static __always_inline bool _dl_mte_setup_stack (void) { return false; };
+#else
+extern bool _dl_mte_setup_stack (void) attribute_hidden;
+#endif
+
+#endif
diff --git a/sysdeps/aarch64/dl-prop.h b/sysdeps/aarch64/dl-prop.h
index cf236df59b..7969260ba2 100644
--- a/sysdeps/aarch64/dl-prop.h
+++ b/sysdeps/aarch64/dl-prop.h
@@ -27,11 +27,17 @@ 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;
+
+extern void _dl_mte_init (void) 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))
@@ -39,6 +45,7 @@ _dl_open_check (struct link_map *m, int dlopen_mode)
 {
   _dl_bti_check (m, NULL);
   _dl_gcs_check (m, NULL, dlopen_mode);
+  _dl_mte_check (m, NULL);
 }
 
 static inline void __attribute__ ((always_inline))
diff --git a/sysdeps/aarch64/dl-start.S b/sysdeps/aarch64/dl-start.S
index c278485cd3..a9a66fc881 100644
--- a/sysdeps/aarch64/dl-start.S
+++ b/sysdeps/aarch64/dl-start.S
@@ -65,6 +65,8 @@ ENTRY (_start)
 	svc	0x0
 	cbnz	w0, L(failed_gcs_lock)
 L(skip_gcs_enable):
+	/* Enable MTE support */
+	bl	_dl_mte_init
 
 .globl _dl_start_user
 .type _dl_start_user, %function
diff --git a/sysdeps/aarch64/libc-mtag.h b/sysdeps/aarch64/libc-mtag.h
index 1d7368b806..c1cf66d7b4 100644
--- a/sysdeps/aarch64/libc-mtag.h
+++ b/sysdeps/aarch64/libc-mtag.h
@@ -64,6 +64,12 @@ __libc_mtag_new_tag (void *p)
   return x0;
 }
 
+static __always_inline bool
+__libc_mtag_enabled (void)
+{
+  return GL(dl_aarch64_mte) & MTE_HEAP;
+}
+
 #endif /* USE_MTAG */
 
 #endif /* _AARCH64_LIBC_MTAG_H */
diff --git a/sysdeps/generic/libc-mtag.h b/sysdeps/generic/libc-mtag.h
index 5477bfa17f..05e0efdce6 100644
--- a/sysdeps/generic/libc-mtag.h
+++ b/sysdeps/generic/libc-mtag.h
@@ -70,4 +70,10 @@ __libc_mtag_new_tag (void *p)
   return p;
 }
 
+static __always_inline bool
+__libc_mtag_enabled (void)
+{
+  return false;
+}
+
 #endif /* _GENERIC_LIBC_MTAG_H */
diff --git a/sysdeps/unix/sysv/linux/aarch64/Makefile b/sysdeps/unix/sysv/linux/aarch64/Makefile
index 57461fded7..fe776c52e2 100644
--- a/sysdeps/unix/sysv/linux/aarch64/Makefile
+++ b/sysdeps/unix/sysv/linux/aarch64/Makefile
@@ -9,6 +9,10 @@ modules-names += \
 LDFLAGS-tst-tlsdesc-pac = -rdynamic
 
 $(objpfx)tst-tlsdesc-pac.out: $(objpfx)tst-tlsdesc-pac-mod.so
+
+sysdep-dl-routines += \
+  dl-mte-stack \
+  # sysdep-dl-routines
 endif
 
 ifeq ($(subdir),misc)
@@ -364,6 +368,59 @@ tst-gcs-audit-override-ARGS = -- $(host-test-program-cmd)
 
 endif # ifeq ($(have-test-gcs),yes)
 
+ifeq ($(aarch64-memtag-abi)$(have-test-memtag-stack),yesyes)
+tests += \
+  tst-memtag-stack-async \
+  tst-memtag-stack-dep1 \
+  tst-memtag-stack-dep2 \
+  tst-memtag-stack-dlopen \
+  tst-memtag-stack-sync \
+  # tests
+
+tests-special += \
+  $(objpfx)tst-memtag-stack-sync-tunable.out \
+  # tests-special
+
+modules-names += \
+  tst-memtag-mod1 \
+  tst-memtag-mod2 \
+  # modules-names
+
+# Disable stack protector for stack tagging tests.  Some GCC versions have a
+# a bug where the stack frame untagging at the function epiloge is done before
+# stack canary check.
+CFLAGS-tst-memtag-stack-sync.c += -march=armv8.5-a+memtag -fsanitize=memtag-stack \
+	$(no-stack-protector)
+LDFLAGS-tst-memtag-stack-sync += -Wl,-z,memtag-stack -Wl,-z,memtag-mode=sync
+tst-memtag-stack-sync-ARGS = sync
+
+CFLAGS-tst-memtag-stack-async.c += -march=armv8.5-a+memtag -fsanitize=memtag-stack \
+	-DMEMTAG_MODE_ASYNC $(no-stack-protector)
+LDFLAGS-tst-memtag-stack-async += -Wl,-z,memtag-stack -Wl,-z,memtag-mode=async
+tst-memtag-stack-async-ARGS = async
+
+CFLAGS-tst-memtag-stack-dep1.c += -march=armv8.5-a+memtag -fsanitize=memtag-stack \
+	$(no-stack-protector)
+LDFLAGS-tst-memtag-stack-dep1 += -Wl,-z,memtag-stack -Wl,-z,memtag-mode=sync
+$(objpfx)tst-memtag-stack-dep1: $(objpfx)tst-memtag-mod1.so
+tst-memtag-stack-dep1-ARGS = sync
+
+$(objpfx)tst-memtag-stack-dep2: $(objpfx)tst-memtag-mod2.so
+CFLAGS-tst-memtag-mod2.c += -march=armv8.5-a+memtag -fsanitize=memtag-stack
+LDFLAGS-tst-memtag-mod2.so += -Wl,-z,memtag-stack -Wl,-z,memtag-mode=sync
+$(objpfx)tst-memtag-mod2.so: $(libsupport)
+
+$(objpfx)tst-memtag-stack-dlopen.out: $(objpfx)tst-memtag-mod2.so
+
+$(objpfx)tst-memtag-stack-sync-tunable.out: $(objpfx)tst-memtag-stack-async
+	$(test-program-cmd-before-env) \
+		$(run-program-env) \
+		GLIBC_TUNABLES=glibc.mem.tagging=2 \
+		$(test-program-cmd-after-env) sync $< \
+		> $@ 2>&1; echo "status: $$?" >> $@; \
+	$(evaluate-test)
+endif # ifeq ($(aarch64-memtag-abi)$(have-test-memtag-stack),yesyes)
+
 endif # ifeq ($(subdir),misc)
 
 ifeq ($(subdir),stdlib)
diff --git a/sysdeps/unix/sysv/linux/aarch64/cpu-features.c b/sysdeps/unix/sysv/linux/aarch64/cpu-features.c
index 36bd72bb12..2ee60d5520 100644
--- a/sysdeps/unix/sysv/linux/aarch64/cpu-features.c
+++ b/sysdeps/unix/sysv/linux/aarch64/cpu-features.c
@@ -96,33 +96,6 @@ 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_MTAG
-  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);
-#endif
-
   /* Check if SVE is supported.  */
   cpu_features->sve = GLRO (dl_hwcap) & HWCAP_SVE;
 
diff --git a/sysdeps/unix/sysv/linux/aarch64/dl-mte-stack.c b/sysdeps/unix/sysv/linux/aarch64/dl-mte-stack.c
new file mode 100644
index 0000000000..5564c25cfe
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/aarch64/dl-mte-stack.c
@@ -0,0 +1,31 @@
+/* Memory tagging handling for GNU dynamic linker.  AArch64 version.
+   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/>.  */
+
+#ifdef USE_AARCH64_MEMTAG_ABI
+#include <ldsodefs.h>
+#include <dl-prop.h>
+
+bool
+_dl_mte_setup_stack (void)
+{
+  GL(dl_stack_prot_flags) |= PROT_MTE;
+  void *page = PTR_ALIGN_DOWN (__libc_stack_end, GLRO (dl_pagesize));
+  return  __mprotect (page, GLRO (dl_pagesize),
+		      GL(dl_stack_prot_flags) | PROT_GROWSDOWN) == 0;
+}
+#endif
diff --git a/sysdeps/unix/sysv/linux/aarch64/dl-procruntime.c b/sysdeps/unix/sysv/linux/aarch64/dl-procruntime.c
index 1f3b58d0fc..f7f210c699 100644
--- a/sysdeps/unix/sysv/linux/aarch64/dl-procruntime.c
+++ b/sysdeps/unix/sysv/linux/aarch64/dl-procruntime.c
@@ -34,4 +34,18 @@ PROCINFO_CLASS unsigned long _dl_aarch64_gcs
 # else
 ,
 # endif
+
+# 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..1c51482374 100644
--- a/sysdeps/unix/sysv/linux/aarch64/libc-start.h
+++ b/sysdeps/unix/sysv/linux/aarch64/libc-start.h
@@ -72,6 +72,8 @@ aarch64_libc_setup_tls (void)
 	    _dl_fatal_printf ("failed to lock GCS: %d\n", -ret);
 	}
     }
+
+  _dl_mte_init ();
 }
 
 # define ARCH_SETUP_IREL() apply_irel ()
diff --git a/sysdeps/unix/sysv/linux/aarch64/tst-memtag-mod1.c b/sysdeps/unix/sysv/linux/aarch64/tst-memtag-mod1.c
new file mode 100644
index 0000000000..07f2a2d7ba
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/aarch64/tst-memtag-mod1.c
@@ -0,0 +1 @@
+int foo (void) { return 0; }
diff --git a/sysdeps/unix/sysv/linux/aarch64/tst-memtag-mod2.c b/sysdeps/unix/sysv/linux/aarch64/tst-memtag-mod2.c
new file mode 100644
index 0000000000..36d3f4f2e1
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/aarch64/tst-memtag-mod2.c
@@ -0,0 +1 @@
+#include "tst-memtag-test-skeleton.c"
diff --git a/sysdeps/unix/sysv/linux/aarch64/tst-memtag-stack-async.c b/sysdeps/unix/sysv/linux/aarch64/tst-memtag-stack-async.c
new file mode 100644
index 0000000000..e68fcccab5
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/aarch64/tst-memtag-stack-async.c
@@ -0,0 +1,2 @@
+/* Check DT_AARCH64_MEMTAG_STACK with DT_AARCH64_MEMTAG_MODE equal 0.  */
+#include "tst-memtag-stack-skeleton.c"
diff --git a/sysdeps/unix/sysv/linux/aarch64/tst-memtag-stack-dep1.c b/sysdeps/unix/sysv/linux/aarch64/tst-memtag-stack-dep1.c
new file mode 100644
index 0000000000..65c5bb6591
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/aarch64/tst-memtag-stack-dep1.c
@@ -0,0 +1,3 @@
+/* Check if dependencies without DT_AARCH64_MEMTAG_STACK still enables MTE
+   support.  */
+#include "tst-memtag-stack-skeleton.c"
diff --git a/sysdeps/unix/sysv/linux/aarch64/tst-memtag-stack-dep2.c b/sysdeps/unix/sysv/linux/aarch64/tst-memtag-stack-dep2.c
new file mode 100644
index 0000000000..e383a83328
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/aarch64/tst-memtag-stack-dep2.c
@@ -0,0 +1,59 @@
+/* Tests for MEMTAG support.
+   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/auxv.h>
+#include <support/capture_subprocess.h>
+#include <support/check.h>
+
+#include "tst-mte-helper.h"
+
+static int
+do_test (void)
+{
+  if (!(getauxval (AT_HWCAP2) & HWCAP2_MTE))
+    FAIL_UNSUPPORTED ("kernel or CPU does not support or enable MTE");
+
+  /* If any shared library requires MTE stack support, but the main binary has
+     not DT_AARCH64_MEMTAG_MODE, defaults to asynchronous mode.  */
+  TEST_VERIFY_EXIT (mte_enable ());
+  TEST_VERIFY_EXIT (mte_mode () == PR_MTE_TCF_ASYNC);
+
+  {
+    struct support_capture_subprocess result =
+      support_capture_subprocess (run_mte_test, (void*)TEST_MAIN);
+
+    support_capture_subprocess_check (&result, "MTE main stack",
+				      EXIT_MTESERR, sc_allow_none);
+
+    support_capture_subprocess_free (&result);
+  }
+
+  {
+    struct support_capture_subprocess result =
+      support_capture_subprocess (run_mte_test, (void*)TEST_THREAD);
+
+    support_capture_subprocess_check (&result, "MTE thread stack",
+				      EXIT_MTESERR, sc_allow_none);
+
+    support_capture_subprocess_free (&result);
+  }
+
+  return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/sysdeps/unix/sysv/linux/aarch64/tst-memtag-stack-dlopen.c b/sysdeps/unix/sysv/linux/aarch64/tst-memtag-stack-dlopen.c
new file mode 100644
index 0000000000..ad103356e9
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/aarch64/tst-memtag-stack-dlopen.c
@@ -0,0 +1,45 @@
+/* Tests for MEMTAG support.
+   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 <dlfcn.h>
+#include <string.h>
+#include <sys/auxv.h>
+#include <support/check.h>
+
+#include "tst-mte-helper.h"
+
+static int
+do_test (void)
+{
+  if (!(getauxval (AT_HWCAP2) & HWCAP2_MTE))
+    FAIL_UNSUPPORTED ("kernel or CPU does not support or enable MTE");
+
+  TEST_VERIFY_EXIT (!mte_enable ());
+
+  /* Verify that if MTE is not enabled at startup, trying to load a DSO that
+     requires MTE should fail.  */
+  void *h = dlopen ("tst-memtag-mod2.so", RTLD_NOW);
+  TEST_VERIFY (h == NULL);
+  const char *message = dlerror ();
+  if (strstr (message, "MTE is not enabled") == 0)
+    FAIL_EXIT1 ("invalid dlopen error message");
+
+  return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/sysdeps/unix/sysv/linux/aarch64/tst-memtag-stack-skeleton.c b/sysdeps/unix/sysv/linux/aarch64/tst-memtag-stack-skeleton.c
new file mode 100644
index 0000000000..599074356c
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/aarch64/tst-memtag-stack-skeleton.c
@@ -0,0 +1,74 @@
+/* Tests for MEMTAG support.
+   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 <string.h>
+#include <sys/auxv.h>
+#include <support/capture_subprocess.h>
+#include <support/check.h>
+
+#include "tst-memtag-test-skeleton.c"
+
+static int
+do_test (int argc, char *argv[])
+{
+  if (!(getauxval (AT_HWCAP2) & HWCAP2_MTE))
+    FAIL_UNSUPPORTED ("kernel or CPU does not support or enable MTE");
+
+  TEST_VERIFY_EXIT (mte_enable ());
+   /* We must have
+     - one or four parameters left if called initially
+       + path for ld.so         optional
+       + "--library-path"       optional
+       + the library path       optional
+       + the application name
+       + the expected MTE mode
+  */
+  TEST_VERIFY_EXIT (argc == 2);
+  int mode = mte_mode ();
+  if (strcmp (argv[1], "sync") == 0)
+    TEST_VERIFY_EXIT (mode == PR_MTE_TCF_SYNC);
+  else if (strcmp (argv[1], "async") == 0)
+    TEST_VERIFY_EXIT (mode == PR_MTE_TCF_ASYNC);
+  /* glibc.mem.tagging=0x3 ("auto") can e either sync, async, or asymm; so
+     there it no point of checking it.  */
+
+  {
+    struct support_capture_subprocess result =
+      support_capture_subprocess (run_mte_test, (void*)TEST_MAIN);
+
+    support_capture_subprocess_check (&result, "MTE main stack",
+				      EXIT_MTESERR, sc_allow_none);
+
+    support_capture_subprocess_free (&result);
+  }
+
+  {
+    struct support_capture_subprocess result =
+      support_capture_subprocess (run_mte_test, (void*)TEST_THREAD);
+
+    support_capture_subprocess_check (&result, "MTE thread stack",
+				      EXIT_MTESERR, sc_allow_none);
+
+    support_capture_subprocess_free (&result);
+  }
+
+  return 0;
+}
+#define TEST_FUNCTION_ARGV do_test
+
+#include <support/test-driver.c>
diff --git a/sysdeps/unix/sysv/linux/aarch64/tst-memtag-stack-sync.c b/sysdeps/unix/sysv/linux/aarch64/tst-memtag-stack-sync.c
new file mode 100644
index 0000000000..e68fcccab5
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/aarch64/tst-memtag-stack-sync.c
@@ -0,0 +1,2 @@
+/* Check DT_AARCH64_MEMTAG_STACK with DT_AARCH64_MEMTAG_MODE equal 0.  */
+#include "tst-memtag-stack-skeleton.c"
diff --git a/sysdeps/unix/sysv/linux/aarch64/tst-memtag-test-skeleton.c b/sysdeps/unix/sysv/linux/aarch64/tst-memtag-test-skeleton.c
new file mode 100644
index 0000000000..52e4f9fc31
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/aarch64/tst-memtag-test-skeleton.c
@@ -0,0 +1,85 @@
+/* Tests skeleton for MEMTAG tests.
+   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 <stdlib.h>
+#include <libc-diag.h>
+#include <support/check.h>
+#include <support/xsignal.h>
+#include <support/xthread.h>
+#include <unistd.h>
+
+#include "tst-mte-helper.h"
+
+static void
+sigsegv_handler (int signum, siginfo_t *si, void *context)
+{
+  if (si->si_signo == SIGSEGV
+      && (si->si_code == SEGV_MTESERR || si->si_code == SEGV_MTEAERR))
+    _exit (EXIT_MTESERR);
+  else
+    _exit (EXIT_FAILURE);
+}
+
+/* Prevent inlining so the stack frame is definitively constructed to
+   trigger a stack frame, and optimization to avoid compiler optimize
+   away the invalid stack operation.  */
+static void *
+__attribute_noinline__
+__attribute_optimization_barrier__
+trigger_mte_fault (void *closure)
+{
+  DIAG_PUSH_NEEDS_COMMENT;
+  DIAG_IGNORE_NEEDS_COMMENT_GCC (16, "-Warray-bounds");
+
+  sigset_t mask;
+  sigemptyset (&mask);
+  sigaddset (&mask, SIGSEGV);
+
+  /* Aling to a MTE tag granule.  */
+  _Alignas (16) char stack_buffer[16];
+  volatile char *ptr = &stack_buffer[16];
+
+  *(volatile char *)ptr;
+
+  return NULL;
+}
+
+void
+run_mte_test (void *closure)
+{
+  test_mode mode = (test_mode)(uintptr_t)closure;
+
+  {
+    struct sigaction sa = {
+      .sa_sigaction = sigsegv_handler,
+      .sa_flags = SA_NODEFER | SA_SIGINFO,
+    };
+    sigemptyset (&sa.sa_mask);
+    xsigaction (SIGSEGV, &sa, NULL);
+  }
+
+  if (mode == TEST_MAIN)
+    trigger_mte_fault (NULL);
+  else
+    {
+      pthread_t t = xpthread_create (NULL, trigger_mte_fault, NULL);
+      xpthread_join (t);
+    }
+
+  _exit (EXIT_FAILURE);
+}
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..bcf324a36f
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/aarch64/tst-mte-helper.h
@@ -0,0 +1,57 @@
+/* Helper routines to check MEMTAG support.
+   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 <fcntl.h>
+#include <limits.h>
+#include <string.h>
+#include <stdbool.h>
+#include <sys/prctl.h>
+#include <stdio.h>
+
+typedef enum
+{
+  TEST_MAIN = 0,
+  TEST_THREAD = 1,
+} test_mode;
+
+#define EXIT_MTESERR 79
+
+void run_mte_test (void *);
+
+static inline bool
+mte_enable (void)
+{
+  int ctrl = prctl (PR_GET_TAGGED_ADDR_CTRL, 0, 0, 0, 0);
+  return ctrl > 0 && (ctrl & PR_MTE_TCF_MASK) != PR_MTE_TCF_NONE;
+}
+
+static inline int
+mte_mode (void)
+{
+  int ctrl = prctl (PR_GET_TAGGED_ADDR_CTRL, 0, 0, 0, 0);
+  TEST_VERIFY_EXIT (ctrl >= 0);
+  return ctrl & PR_MTE_TCF_MASK;
+}
+
+#define __attribute_disable_mte_stack__ \
+ __attribute__((no_sanitize("memtag-stack")))
+
+#endif
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.