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

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=8db1bd3af55aa2438642de773fb4a805f5972edf

commit 8db1bd3af55aa2438642de773fb4a805f5972edf
Author: Adhemerval Zanella <[email protected]>
Date:   Mon Aug 24 20:43:28 2026 -0300

    aarch64: Add initial Memtag ABI Extension to ELF (stack only)
    
    This patch introduces initial support for the AArch64 Memory Tagging
    Extension (MTE) in the dynamic linker, 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].
    
    The feature is guarded by a new configure option,
    --enable-aarch64-memtag-abi, and it is disabled by default.
    
    Semantics:
    
    * The MTE stack protection is enabled if and only if the main
      executable (PDE/PIE) is marked with DT_AARCH64_MEMTAG_STACK.
    
    * If the main executable is not marked and any dependency requires
      MTE stack protection, the dynamic loader aborts at startup.
    
    * Any unsupported marking (DT_AARCH64_MEMTAG_HEAP or
      DT_AARCH64_MEMTAG_GLOBALS), and any invalid or inconsistent
      (orphan) set of DT_AARCH64_MEMTAG_xxx dynamic tags, on either the
      main executable or any dependency, aborts the startup.
    
    * The MTE execution mode is defined by DT_AARCH64_MEMTAG_MODE of the
      main executable; if absent, asynchronous mode is used.
    
    * If the main executable did not enable the MTE stack protection at
      startup, any subsequent dlopen of a shared object that requires it
      is rejected to prevent unsafe memory access.
    
    Static PDEs are not supported since they carry no dynamic tags.
    
    [1] https://github.com/ARM-software/abi-aa/blob/main/memtagabielf64/memtagabielf64.rst

Diff:
---
 INSTALL                                            |   9 +
 NEWS                                               |   5 +
 config.h.in                                        |   3 +
 config.make.in                                     |   2 +
 configure                                          |  24 +++
 configure.ac                                       |  14 ++
 elf/Versions                                       |   7 +
 elf/elf.h                                          |   7 +-
 manual/install.texi                                |  23 +++
 sysdeps/aarch64/Makefile                           |   1 +
 sysdeps/aarch64/configure                          | 191 ++++++++++++++++++++
 sysdeps/aarch64/configure.ac                       |  51 ++++++
 sysdeps/aarch64/cpu-features.h                     |   7 +
 sysdeps/aarch64/dl-mte.c                           | 199 +++++++++++++++++++++
 sysdeps/aarch64/dl-mte.h                           |  35 ++++
 sysdeps/aarch64/dl-prop.h                          |   7 +
 sysdeps/aarch64/dl-start.S                         |   5 +
 sysdeps/unix/sysv/linux/aarch64/Makefile           | 106 +++++++++++
 sysdeps/unix/sysv/linux/aarch64/dl-mte-stack.c     |  31 ++++
 sysdeps/unix/sysv/linux/aarch64/dl-procruntime.c   |  14 ++
 .../unix/sysv/linux/aarch64/tst-memtag-abort-dep.c |  32 ++++
 .../sysv/linux/aarch64/tst-memtag-abort-heap.c     |  28 +++
 .../sysv/linux/aarch64/tst-memtag-abort-orphan.c   |  29 +++
 .../unix/sysv/linux/aarch64/tst-memtag-abort.sh    |  40 +++++
 .../sysv/linux/aarch64/tst-memtag-dlopen-orphan.c  |  47 +++++
 sysdeps/unix/sysv/linux/aarch64/tst-memtag-mod1.c  |   1 +
 sysdeps/unix/sysv/linux/aarch64/tst-memtag-mod2.c  |   1 +
 sysdeps/unix/sysv/linux/aarch64/tst-memtag-mod3.c  |  10 ++
 sysdeps/unix/sysv/linux/aarch64/tst-memtag-mod4.c  |   8 +
 .../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     |   3 +
 .../linux/aarch64/tst-memtag-stack-dlopen-dep.c    |  45 +++++
 .../aarch64/tst-memtag-stack-dlopen-enabled.c      |  57 ++++++
 .../sysv/linux/aarch64/tst-memtag-stack-dlopen.c   |  45 +++++
 .../sysv/linux/aarch64/tst-memtag-stack-skeleton.c |  72 ++++++++
 .../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 ++++++
 39 files changed, 1307 insertions(+), 1 deletion(-)

diff --git a/INSTALL b/INSTALL
index bafae84f75..1c91b75c30 100644
--- a/INSTALL
+++ b/INSTALL
@@ -279,6 +279,15 @@ 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.
+
+     This option is disabled by default and only supported 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 56c6b581df..1a660a1b52 100644
--- a/NEWS
+++ b/NEWS
@@ -90,6 +90,11 @@ Major new features:
 
 * A new locale has been added: hrx_BR (Hunsrik language spoken in Brazil).
 
+* 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).  It can be enabled with the
+  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 17bdd30570..5db3856ea6 100644
--- a/config.h.in
+++ b/config.h.in
@@ -182,6 +182,9 @@
 /* Define if inlined system calls are available.  */
 #undef HAVE_INLINED_SYSCALLS
 
+/* 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 0ddc24a589..f8c7fcca2d 100644
--- a/config.make.in
+++ b/config.make.in
@@ -78,6 +78,8 @@ multi-arch = @multi_arch@
 
 mach-interface-list = @mach_interface_list@
 
+aarch64-memtag-abi = @aarch64_memtag_abi@
+
 # Configuration options.
 build-shared = @shared@
 build-profile = @profile@
diff --git a/configure b/configure
index 604279533a..77f43982e8 100755
--- a/configure
+++ b/configure
@@ -702,6 +702,7 @@ INSTALL_PROGRAM
 base_machine
 build_pt_chown
 build_nscd
+aarch64_memtag_abi
 enable_werror
 force_install
 bindnow
@@ -808,6 +809,7 @@ enable_maintainer_mode
 enable_kernel
 enable_werror
 enable_multi_arch
+enable_aarch64_memtag_abi
 enable_systemtap
 enable_build_nscd
 enable_nscd
@@ -1485,6 +1487,9 @@ Optional Features:
   --disable-werror        do not build with -Werror
   --enable-multi-arch     enable single DSO with optimizations for multiple
                           architectures
+  --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
@@ -4668,6 +4673,25 @@ esac
 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 2fe7980fc9..357cc39432 100644
--- a/configure.ac
+++ b/configure.ac
@@ -315,6 +315,20 @@ AC_ARG_ENABLE([multi-arch],
 	      [multi_arch=$enableval],
 	      [multi_arch=default])
 
+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 b482fcfb64..19f310bfa2 100644
--- a/elf/elf.h
+++ b/elf/elf.h
@@ -3041,7 +3041,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/manual/install.texi b/manual/install.texi
index fe4a368385..1c1f3a642c 100644
--- a/manual/install.texi
+++ b/manual/install.texi
@@ -309,6 +309,29 @@ 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 (@code{DT_AARCH64_MEMTAG_STACK}) and MTE
+execution mode configuration (@code{DT_AARCH64_MEMTAG_MODE}) as defined in
+the Memtag ABI Extension for ELF64.
+
+The MTE stack protection is enabled if and only if the main executable is
+marked with @code{DT_AARCH64_MEMTAG_STACK}.  If the main executable is not
+marked and any dependency requires MTE stack protection, the program fails
+at startup.  The program also fails at startup if any object carries an
+unsupported marking (@code{DT_AARCH64_MEMTAG_HEAP} or
+@code{DT_AARCH64_MEMTAG_GLOBALS}), or an invalid or inconsistent set of
+@code{DT_AARCH64_MEMTAG_xxx} dynamic tags.
+
+If the execution mode is not specified with @code{DT_AARCH64_MEMTAG_MODE},
+the asynchronous mode is used.
+
+If the main executable does not enable the MTE stack protection at startup,
+any subsequent attempt to @code{dlopen} a shared object that requires it
+will be rejected.
+
+This option is disabled by default and only supported 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 87ce51c821..f046148091 100644
--- a/sysdeps/aarch64/Makefile
+++ b/sysdeps/aarch64/Makefile
@@ -4,6 +4,7 @@ ifeq ($(subdir),elf)
 sysdep-dl-routines += \
   dl-bti \
   dl-gcs \
+  dl-mte \
   # sysdep-dl-routines
 
 tests += \
diff --git a/sysdeps/aarch64/configure b/sysdeps/aarch64/configure
index 44b833ea1d..19ab6d114a 100755
--- a/sysdeps/aarch64/configure
+++ b/sysdeps/aarch64/configure
@@ -484,3 +484,194 @@ else
 have-test-gcs = no"
 fi
 
+# Check if compiler supports MEMTAG stack support.  Also check if linker
+# support the required options.
+
+{ 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
+
+# lld requires the memtag-mode to be used along memtag-stack
+{ 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,--fatal-warnings -Wl,-z,memtag-stack -z memtag-mode=sync -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,--fatal-warnings -Wl,-z,memtag-stack -z memtag-mode=sync -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
+
+# Check if linker supports Memtag Heap support.  lld requires the memtag-mode.
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for linker that supports -z memtag-heap" >&5
+printf %s "checking for linker that supports -z memtag-heap... " >&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,--fatal-warnings -Wl,-z,memtag-heap -z memtag-mode=sync -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,--fatal-warnings -Wl,-z,memtag-heap -z memtag-mode=sync -nostdlib \
+      -nostartfiles -fPIC -shared -o conftest.so conftest.c 2>&1 \
+      | grep "warning: -z memtag-heap 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_heap=yes
+else
+  libc_cv_ld_memtag_heap=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_ld_memtag_heap = yes
+then
+   config_vars="$config_vars
+have-test-memtag-heap = yes"
+else
+   config_vars="$config_vars
+have-test-memtag-heap = no"
+fi
+
+# Check if the linker accepts -z memtag-mode without any MTE feature option.
+# BFD ld emits an orphan DT_AARCH64_MEMTAG_MODE dynamic tag in this case,
+# which is used to check the inconsistent marking handling.
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for linker that supports -z memtag-mode" >&5
+printf %s "checking for linker that supports -z memtag-mode... " >&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,--fatal-warnings -Wl,-z,memtag-mode=sync -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,--fatal-warnings -Wl,-z,memtag-mode=sync -nostdlib \
+      -nostartfiles -fPIC -shared -o conftest.so conftest.c 2>&1 \
+      | grep "warning: -z memtag-mode 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_mode_only=yes
+else
+  libc_cv_ld_memtag_mode_only=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_ld_memtag_mode_only = yes
+then
+   config_vars="$config_vars
+have-test-memtag-mode-only = yes"
+else
+   config_vars="$config_vars
+have-test-memtag-mode-only = no"
+fi
+
diff --git a/sysdeps/aarch64/configure.ac b/sysdeps/aarch64/configure.ac
index b9981861f6..0614be9c0c 100644
--- a/sysdeps/aarch64/configure.ac
+++ b/sysdeps/aarch64/configure.ac
@@ -105,3 +105,54 @@ 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.
+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]
+)
+# lld requires the memtag-mode to be used along memtag-stack
+LIBC_LINKER_FEATURE(
+  [-z memtag-stack], [-Wl,--fatal-warnings -Wl,-z,memtag-stack -z memtag-mode=sync],
+  [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
+
+# Check if linker supports Memtag Heap support.  lld requires the memtag-mode.
+LIBC_LINKER_FEATURE(
+  [-z memtag-heap], [-Wl,--fatal-warnings -Wl,-z,memtag-heap -z memtag-mode=sync],
+  [libc_cv_ld_memtag_heap=yes], [libc_cv_ld_memtag_heap=no]
+)
+if test $libc_cv_ld_memtag_heap = yes
+then
+   LIBC_CONFIG_VAR([have-test-memtag-heap], [yes])
+else
+   LIBC_CONFIG_VAR([have-test-memtag-heap], [no])
+fi
+
+# Check if the linker accepts -z memtag-mode without any MTE feature option.
+# BFD ld emits an orphan DT_AARCH64_MEMTAG_MODE dynamic tag in this case,
+# which is used to check the inconsistent marking handling.
+LIBC_LINKER_FEATURE(
+  [-z memtag-mode], [-Wl,--fatal-warnings -Wl,-z,memtag-mode=sync],
+  [libc_cv_ld_memtag_mode_only=yes], [libc_cv_ld_memtag_mode_only=no]
+)
+if test $libc_cv_ld_memtag_mode_only = yes
+then
+   LIBC_CONFIG_VAR([have-test-memtag-mode-only], [yes])
+else
+   LIBC_CONFIG_VAR([have-test-memtag-mode-only], [no])
+fi
diff --git a/sysdeps/aarch64/cpu-features.h b/sysdeps/aarch64/cpu-features.h
index b7dab3dff4..fe8b58c884 100644
--- a/sysdeps/aarch64/cpu-features.h
+++ b/sysdeps/aarch64/cpu-features.h
@@ -59,6 +59,13 @@ enum {
   BTI_CHECK_ENFORCED = 1,
 };
 
+enum {
+  MTE_MODE_SYNC  = 0x1,
+  MTE_MODE_ASYNC = 0x2,
+  MTE_MODE_MASK  = MTE_MODE_SYNC | MTE_MODE_ASYNC,
+  MTE_STACK      = 0x4,
+};
+
 struct cpu_features
 {
   uint64_t midr_el1;
diff --git a/sysdeps/aarch64/dl-mte.c b/sysdeps/aarch64/dl-mte.c
new file mode 100644
index 0000000000..27f6190b9f
--- /dev/null
+++ b/sysdeps/aarch64/dl-mte.c
@@ -0,0 +1,199 @@
+/* AArch64 Memtag ABI Extension for ELF 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 <ldsodefs.h>
+#include <sys/auxv.h>
+#include <dl-mte.h>
+#include <dl-prop.h>
+
+#ifdef USE_AARCH64_MEMTAG_ABI
+
+/* The maximal set of permitted tags that the MTE random tag generation
+   instruction may use.  */
+# define MTE_ALLOWED_TAGS (0xfffe << PR_MTE_TAG_SHIFT)
+
+# define DT_AARCH64(x) (DT_AARCH64_##x - DT_LOPROC + DT_NUM)
+
+/* Parsed view of the DT_AARCH64_MEMTAG_xxx dynamic tags of an object.  */
+struct memtag_marking
+{
+  /* DT_AARCH64_MEMTAG_MODE, valid iff MODE_PRESENT.  */
+  uint64_t mode;
+  bool mode_present;
+  /* The object requires the respective MTE protection.  */
+  bool stack;
+  bool heap;
+  bool globals;
+  /* A tag has a value outside the ones defined by the ABI.  */
+  bool invalid;
+  /* MEMTAG_MODE without any protection being requested, or only one of
+     MEMTAG_GLOBALS and MEMTAG_GLOBALSSZ.  */
+  bool orphan;
+};
+
+static struct memtag_marking
+read_marking (const struct link_map *l)
+{
+  struct memtag_marking m = { 0 };
+  const ElfW(Dyn) *d;
+
+  bool stack_present = false;
+  bool heap_present = false;
+
+  if ((d = l->l_info[DT_AARCH64 (MEMTAG_MODE)]) != NULL)
+    {
+      m.mode_present = true;
+      m.mode = d->d_un.d_val;
+      if (m.mode > 1)
+	m.invalid = true;
+    }
+
+  if ((d = l->l_info[DT_AARCH64 (MEMTAG_STACK)]) != NULL)
+    {
+      stack_present = true;
+      if (d->d_un.d_val > 1)
+	m.invalid = true;
+      m.stack = d->d_un.d_val != 0;
+    }
+
+  if ((d = l->l_info[DT_AARCH64 (MEMTAG_HEAP)]) != NULL)
+    {
+      heap_present = true;
+      if (d->d_un.d_val > 1)
+	m.invalid = true;
+      m.heap = d->d_un.d_val != 0;
+    }
+
+  bool globals_present = l->l_info[DT_AARCH64 (MEMTAG_GLOBALS)] != NULL;
+  bool globalssz_present = l->l_info[DT_AARCH64 (MEMTAG_GLOBALSSZ)] != NULL;
+  m.globals = globals_present;
+
+  if (globals_present != globalssz_present)
+    m.orphan = true;
+  if (m.mode_present && !stack_present && !heap_present && !globals_present)
+    m.orphan = true;
+
+  return m;
+}
+
+/* PROGRAM is non-NULL during program startup, where any failure is fatal;
+   otherwise the object comes from dlopen and the error can be handled by
+   the caller.  */
+static void _Noreturn
+fail (const struct link_map *l, const char *program, const char *reason)
+{
+  if (program == NULL)
+    _dl_signal_error (0, l->l_name, "dlopen", reason);
+
+  if (l->l_name[0] != '\0')
+    _dl_fatal_printf ("%s: error: %s: %s\n", program, l->l_name, reason);
+  _dl_fatal_printf ("%s: error: %s\n", program, reason);
+}
+
+static void
+check_marking (const struct link_map *l, const char *program,
+	       const struct memtag_marking *m)
+{
+  if (m->invalid)
+    fail (l, program, "invalid DT_AARCH64_MEMTAG_xxx dynamic tag value");
+  if (m->orphan)
+    fail (l, program, "inconsistent DT_AARCH64_MEMTAG_xxx dynamic tags");
+  if (m->heap)
+    fail (l, program, "MTE protection heap is not supported");
+  if (m->globals)
+    fail (l, program, "MTE protection globals is not supported");
+}
+#endif /* USE_AARCH64_MEMTAG_ABI */
+
+void
+_dl_mte_check (struct link_map *l, const char *program)
+{
+#ifdef USE_AARCH64_MEMTAG_ABI
+  /* During startup L is the main executable map, otherwise it is the
+     dlopen'ed object.  */
+  bool startup = program != NULL;
+
+  /* Whether the MTE stack protection was enabled at startup.  */
+  bool mte_stack = (GL(dl_aarch64_mte) & MTE_STACK) != 0;
+
+  struct memtag_marking m = read_marking (l);
+  check_marking (l, program, &m);
+
+  /* Only the main executable marking enables the MTE stack protection.  If
+     the main executable is not marked, any dependency that requires it makes
+     the process to fail (either at startup or on dlopen).  */
+  if (startup)
+    mte_stack = m.stack;
+  else if (m.stack && !mte_stack)
+    fail (l, program,
+	  "shared object requires MTE stack protection, but it was not "
+	  "enabled at program startup");
+
+  for (unsigned int i = 0; i < l->l_searchlist.r_nlist; i++)
+    {
+      const struct link_map *dep = l->l_searchlist.r_list[i];
+      if (dep == l)
+	continue;
+      struct memtag_marking dm = read_marking (dep);
+      check_marking (dep, program, &dm);
+      if (dm.stack && !mte_stack)
+	fail (dep, program,
+	      startup
+	      ? "shared object requires MTE stack protection, but it is not "
+		"enabled by the main executable"
+	      : "shared object requires MTE stack protection, but it was not "
+		"enabled at program startup");
+    }
+
+  if (startup && mte_stack)
+    {
+      GL(dl_aarch64_mte) |= MTE_STACK;
+      /* Use asynchronous mode if DT_AARCH64_MEMTAG_MODE is not present.  */
+      GL(dl_aarch64_mte) |= (m.mode_present && m.mode == 0)
+			    ? MTE_MODE_SYNC : MTE_MODE_ASYNC;
+    }
+#endif
+}
+
+/* Enable the MTE state required by the DT_AARCH64_MEMTAG_xxx dynamic tags of
+   the main executable, parsed by _dl_mte_check.  It is done after initial
+   relocation processing, but prior any user code execution (including ELF
+   constructors).  */
+void
+_dl_mte_init (void)
+{
+#ifdef USE_AARCH64_MEMTAG_ABI
+  if ((GL(dl_aarch64_mte) & MTE_STACK) == 0)
+    return;
+
+  if ((GLRO (dl_hwcap2) & HWCAP2_MTE) == 0)
+    _dl_fatal_printf ("Fatal glibc error: MTE stack protection required, "
+		      "but not supported by the kernel or CPU\n");
+
+  uint64_t flags = PR_TAGGED_ADDR_ENABLE | MTE_ALLOWED_TAGS;
+  flags |= (GL(dl_aarch64_mte) & MTE_MODE_SYNC) != 0
+	   ? PR_MTE_TCF_SYNC : PR_MTE_TCF_ASYNC;
+
+  int r = INLINE_SYSCALL_CALL (prctl, PR_SET_TAGGED_ADDR_CTRL, flags, 0, 0, 0);
+  if (r < 0)
+    _dl_fatal_printf ("Fatal glibc error: failed to enable MTE: %d\n", -r);
+
+  if (!_dl_mte_setup_stack ())
+    _dl_fatal_printf ("Fatal glibc error: MTE stack protection 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 0d2672d32d..a914615a45 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 int
diff --git a/sysdeps/aarch64/dl-start.S b/sysdeps/aarch64/dl-start.S
index c278485cd3..336057ad04 100644
--- a/sysdeps/aarch64/dl-start.S
+++ b/sysdeps/aarch64/dl-start.S
@@ -65,6 +65,11 @@ ENTRY (_start)
 	svc	0x0
 	cbnz	w0, L(failed_gcs_lock)
 L(skip_gcs_enable):
+	/* Enable MTE if required by the Memtag ABI ELF marking.  */
+	bl	_dl_mte_init
+	/* Reload GL, x16 might be clobbered by the call above.  */
+	adrp	x16, _rtld_local
+	add	x16, x16, :lo12:_rtld_local
 
 .globl _dl_start_user
 .type _dl_start_user, %function
diff --git a/sysdeps/unix/sysv/linux/aarch64/Makefile b/sysdeps/unix/sysv/linux/aarch64/Makefile
index baefac15b5..42aa048446 100644
--- a/sysdeps/unix/sysv/linux/aarch64/Makefile
+++ b/sysdeps/unix/sysv/linux/aarch64/Makefile
@@ -19,6 +19,10 @@ tests-static += \
   # tests-static
 tst-cpu-tunable-static-pie-TUNABLES = glibc.cpu.hwcaps=-midr,-sve,-mops
 endif
+
+sysdep-dl-routines += \
+  dl-mte-stack \
+  # sysdep-dl-routines
 endif
 
 ifeq ($(subdir),misc)
@@ -375,6 +379,108 @@ 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-abort-dep \
+  tst-memtag-stack-async \
+  tst-memtag-stack-dep1 \
+  tst-memtag-stack-dep2 \
+  tst-memtag-stack-dlopen \
+  tst-memtag-stack-dlopen-dep \
+  tst-memtag-stack-dlopen-enabled \
+  tst-memtag-stack-sync \
+  # tests
+
+modules-names += \
+  tst-memtag-mod1 \
+  tst-memtag-mod2 \
+  tst-memtag-mod3 \
+  # modules-names
+
+# Disable the stack protector for the stack tagging tests.  Some GCC versions
+# have a bug where the stack frame untagging at the function epilogue is done
+# before the stack canary check.
+memtag-stack-cflags = -march=armv8.5-a+memtag -fsanitize=memtag-stack \
+		      $(no-stack-protector)
+
+CFLAGS-tst-memtag-stack-sync.c += $(memtag-stack-cflags)
+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 += $(memtag-stack-cflags)
+LDFLAGS-tst-memtag-stack-async += -Wl,-z,memtag-stack -Wl,-z,memtag-mode=async
+tst-memtag-stack-async-ARGS = async
+
+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)
+
+CFLAGS-tst-memtag-stack-dep1.c += $(memtag-stack-cflags)
+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
+
+CFLAGS-tst-memtag-stack-dep2.c += $(memtag-stack-cflags)
+LDFLAGS-tst-memtag-stack-dep2 += -Wl,-z,memtag-stack -Wl,-z,memtag-mode=sync \
+				 -Wl,--no-as-needed
+$(objpfx)tst-memtag-stack-dep2: $(objpfx)tst-memtag-mod2.so
+tst-memtag-stack-dep2-ARGS = sync
+
+$(objpfx)tst-memtag-stack-dlopen.out: $(objpfx)tst-memtag-mod2.so
+
+LDFLAGS-tst-memtag-mod3.so += -Wl,--no-as-needed
+$(objpfx)tst-memtag-mod3.so: $(objpfx)tst-memtag-mod2.so
+$(objpfx)tst-memtag-stack-dlopen-dep.out: $(objpfx)tst-memtag-mod3.so \
+  $(objpfx)tst-memtag-mod1.so
+
+CFLAGS-tst-memtag-stack-dlopen-enabled.c += $(memtag-stack-cflags)
+LDFLAGS-tst-memtag-stack-dlopen-enabled += -Wl,-z,memtag-stack \
+					   -Wl,-z,memtag-mode=sync
+$(objpfx)tst-memtag-stack-dlopen-enabled.out: $(objpfx)tst-memtag-mod2.so
+
+LDFLAGS-tst-memtag-abort-dep += -Wl,--no-as-needed
+$(objpfx)tst-memtag-abort-dep: $(objpfx)tst-memtag-mod2.so
+tst-memtag-abort-dep-ERROR = requires MTE stack protection
+
+ifeq ($(have-test-memtag-heap),yes)
+tests += \
+  tst-memtag-abort-heap \
+  # tests
+
+LDFLAGS-tst-memtag-abort-heap += -Wl,-z,memtag-heap -Wl,-z,memtag-mode=sync
+tst-memtag-abort-heap-ERROR = MTE protection heap is not supported
+endif # ifeq ($(have-test-memtag-heap),yes)
+
+ifeq ($(have-test-memtag-mode-only),yes)
+tests += \
+  tst-memtag-abort-orphan \
+  tst-memtag-dlopen-orphan \
+  # tests
+
+modules-names += \
+  tst-memtag-mod4 \
+  # modules-names
+
+LDFLAGS-tst-memtag-abort-orphan += -Wl,-z,memtag-mode=sync
+tst-memtag-abort-orphan-ERROR = inconsistent DT_AARCH64_MEMTAG_xxx dynamic tags
+
+LDFLAGS-tst-memtag-mod4.so += -Wl,-z,memtag-mode=sync
+$(objpfx)tst-memtag-dlopen-orphan.out: $(objpfx)tst-memtag-mod4.so \
+  $(objpfx)tst-memtag-mod1.so
+endif # ifeq ($(have-test-memtag-mode-only),yes)
+
+define run-memtag-abort-test
+  $(test-wrapper-env) $(run-program-env) $(host-test-program-cmd)
+endef
+
+$(objpfx)tst-memtag-abort-%.out: \
+  $(..)sysdeps/unix/sysv/linux/aarch64/tst-memtag-abort.sh \
+  $(objpfx)tst-memtag-abort-%
+	$(SHELL) $< $(common-objpfx) $(test-name) \
+	  '$(tst-memtag-abort-$*-ERROR)' '$(run-memtag-abort-test)'; \
+	$(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/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/tst-memtag-abort-dep.c b/sysdeps/unix/sysv/linux/aarch64/tst-memtag-abort-dep.c
new file mode 100644
index 0000000000..8459253678
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/aarch64/tst-memtag-abort-dep.c
@@ -0,0 +1,32 @@
+/* Check that an unmarked main executable with a dependency that requires
+   MTE stack protection aborts at startup.
+   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>
+
+void run_mte_test (void *);
+
+static int
+do_test (void)
+{
+  void (*fp) (void *) = run_mte_test;
+  FAIL_EXIT1 ("dynamic loader did not abort with a MTE marked dependency "
+	      "(%p)", fp);
+}
+
+#include <support/test-driver.c>
diff --git a/sysdeps/unix/sysv/linux/aarch64/tst-memtag-abort-heap.c b/sysdeps/unix/sysv/linux/aarch64/tst-memtag-abort-heap.c
new file mode 100644
index 0000000000..f0486442b9
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/aarch64/tst-memtag-abort-heap.c
@@ -0,0 +1,28 @@
+/* Check that a main executable marked with DT_AARCH64_MEMTAG_HEAP aborts
+   at startup, since only the MTE stack protection is supported.
+   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>
+
+static int
+do_test (void)
+{
+  FAIL_EXIT1 ("dynamic loader did not abort with a MTE heap marking");
+}
+
+#include <support/test-driver.c>
diff --git a/sysdeps/unix/sysv/linux/aarch64/tst-memtag-abort-orphan.c b/sysdeps/unix/sysv/linux/aarch64/tst-memtag-abort-orphan.c
new file mode 100644
index 0000000000..f7c08fb805
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/aarch64/tst-memtag-abort-orphan.c
@@ -0,0 +1,29 @@
+/* Check that a main executable with an orphan DT_AARCH64_MEMTAG_MODE
+   dynamic tag (without any MTE protection being requested) aborts at
+   startup.
+   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>
+
+static int
+do_test (void)
+{
+  FAIL_EXIT1 ("dynamic loader did not abort with an orphan MTE marking");
+}
+
+#include <support/test-driver.c>
diff --git a/sysdeps/unix/sysv/linux/aarch64/tst-memtag-abort.sh b/sysdeps/unix/sysv/linux/aarch64/tst-memtag-abort.sh
new file mode 100644
index 0000000000..8508730415
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/aarch64/tst-memtag-abort.sh
@@ -0,0 +1,40 @@
+#!/bin/sh
+# Test wrapper for AArch64 Memtag ABI tests that are expected to abort
+# at startup.
+# 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/>.
+
+objpfx=$1; shift
+tstname=$1; shift
+expected=$1; shift
+tstrun=$1; shift
+
+logfile=$objpfx/$tstname.out
+
+rm -f $logfile
+touch $logfile
+
+${tstrun} 2>> $logfile >> $logfile; status=$?
+
+if test $status -eq 127 && grep -q "$expected" "$logfile" ; then
+  exit 0
+elif test $status -eq 77; then
+  exit 77
+else
+  echo "expected '$expected' with 127, not $status return status"
+  exit 1
+fi
diff --git a/sysdeps/unix/sysv/linux/aarch64/tst-memtag-dlopen-orphan.c b/sysdeps/unix/sysv/linux/aarch64/tst-memtag-dlopen-orphan.c
new file mode 100644
index 0000000000..4ec4414672
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/aarch64/tst-memtag-dlopen-orphan.c
@@ -0,0 +1,47 @@
+/* Check that dlopen of a module with an orphan DT_AARCH64_MEMTAG_MODE
+   dynamic tag fails instead of aborting the process.
+   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 <support/check.h>
+#include <support/xdlfcn.h>
+
+#include "tst-mte-helper.h"
+
+static int
+do_test (void)
+{
+  TEST_VERIFY_EXIT (!mte_enable ());
+
+  /* The module marking is inconsistent (DT_AARCH64_MEMTAG_MODE without any
+     MTE protection request).  The dlopen should fail without aborting the
+     process.  */
+  void *h = dlopen ("tst-memtag-mod4.so", RTLD_NOW);
+  TEST_VERIFY (h == NULL);
+  const char *message = dlerror ();
+  if (strstr (message, "inconsistent DT_AARCH64_MEMTAG_xxx") == NULL)
+    FAIL_EXIT1 ("invalid dlopen error message: %s", message);
+
+  /* The process should still work, including loading unmarked modules.  */
+  xdlclose (xdlopen ("tst-memtag-mod1.so", RTLD_NOW));
+
+  return 0;
+}
+
+#include <support/test-driver.c>
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-mod3.c b/sysdeps/unix/sysv/linux/aarch64/tst-memtag-mod3.c
new file mode 100644
index 0000000000..a89337d3b9
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/aarch64/tst-memtag-mod3.c
@@ -0,0 +1,10 @@
+/* Unmarked module with a dependency (tst-memtag-mod2.so) that requires
+   MTE stack protection.  */
+
+extern void run_mte_test (void *);
+
+void
+mod3_func (void *closure)
+{
+  run_mte_test (closure);
+}
diff --git a/sysdeps/unix/sysv/linux/aarch64/tst-memtag-mod4.c b/sysdeps/unix/sysv/linux/aarch64/tst-memtag-mod4.c
new file mode 100644
index 0000000000..fdea30df4a
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/aarch64/tst-memtag-mod4.c
@@ -0,0 +1,8 @@
+/* Module linked with an orphan DT_AARCH64_MEMTAG_MODE dynamic tag (without
+   any MTE protection request).  */
+
+int
+mod4_func (void)
+{
+  return 0;
+}
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..29c3bb48bf
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/aarch64/tst-memtag-stack-dep2.c
@@ -0,0 +1,3 @@
+/* Check that a marked main executable with a dependency that also requires
+   MTE stack protection enables the MTE stack protection.  */
+#include "tst-memtag-stack-skeleton.c"
diff --git a/sysdeps/unix/sysv/linux/aarch64/tst-memtag-stack-dlopen-dep.c b/sysdeps/unix/sysv/linux/aarch64/tst-memtag-stack-dlopen-dep.c
new file mode 100644
index 0000000000..803abf000e
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/aarch64/tst-memtag-stack-dlopen-dep.c
@@ -0,0 +1,45 @@
+/* Check that without MTE stack protection enabled at startup.
+   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 <support/check.h>
+#include <support/xdlfcn.h>
+
+#include "tst-mte-helper.h"
+
+static int
+do_test (void)
+{
+  TEST_VERIFY_EXIT (!mte_enable ());
+
+  /* The module itself is unmarked, but its dependency requires MTE stack
+     protection.  The dlopen should fail without aborting the process.  */
+  void *h = dlopen ("tst-memtag-mod3.so", RTLD_NOW);
+  TEST_VERIFY (h == NULL);
+  const char *message = dlerror ();
+  if (strstr (message, "requires MTE stack protection") == NULL)
+    FAIL_EXIT1 ("invalid dlopen error message: %s", message);
+
+  /* The process should still work, including loading unmarked modules.  */
+  xdlclose (xdlopen ("tst-memtag-mod1.so", RTLD_NOW));
+
+  return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/sysdeps/unix/sysv/linux/aarch64/tst-memtag-stack-dlopen-enabled.c b/sysdeps/unix/sysv/linux/aarch64/tst-memtag-stack-dlopen-enabled.c
new file mode 100644
index 0000000000..b5383edb85
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/aarch64/tst-memtag-stack-dlopen-enabled.c
@@ -0,0 +1,57 @@
+/* Check that a marked main executable can dlopen a module that requires
+   MTE stack protection.
+   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 <support/xdlfcn.h>
+
+#include "tst-mte-helper.h"
+
+static void (*mod_run_mte_test) (void *);
+
+static void
+run_dlopened_test (void *closure)
+{
+  mod_run_mte_test (closure);
+}
+
+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 ());
+
+  void *h = xdlopen ("tst-memtag-mod2.so", RTLD_NOW);
+  mod_run_mte_test = xdlsym (h, "run_mte_test");
+
+  struct support_capture_subprocess result =
+    support_capture_subprocess (run_dlopened_test, (void *) TEST_MAIN);
+  support_capture_subprocess_check (&result, "MTE stack fault in dlopened "
+				    "module", EXIT_MTESERR, sc_allow_none);
+  support_capture_subprocess_free (&result);
+
+  xdlclose (h);
+
+  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..071efefa42
--- /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 the MTE stack protection is not enabled at startup,
+     trying to load a DSO that requires it should fail.  */
+  void *h = dlopen ("tst-memtag-mod2.so", RTLD_NOW);
+  TEST_VERIFY (h == NULL);
+  const char *message = dlerror ();
+  if (strstr (message, "requires MTE stack protection") == NULL)
+    FAIL_EXIT1 ("invalid dlopen error message: %s", 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..229664a617
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/aarch64/tst-memtag-stack-skeleton.c
@@ -0,0 +1,72 @@
+/* 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);
+
+  {
+    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.