[1/2] rtld: Enable MTE for stack when specified in .dynamic.

Message ID 20250314132142.49243-2-cupertino.miranda@oracle.com (mailing list archive)
State Changes Requested
Delegated to: Adhemerval Zanella Netto
Headers
Series RFC: Support for MTE stack tagging. |

Checks

Context Check Description
redhat-pt-bot/TryBot-apply_patch fail Patch failed to apply to master at the time it was sent

Commit Message

Cupertino Miranda March 14, 2025, 1:21 p.m. UTC
  MTE stack tagging enabled executables are flagged by setting the dynamic
array tag DT_AARCH64_MEMTAG_STACK as described in section 6 from:
https://github.com/ARM-software/abi-aa/blob/main/memtagabielf64/memtagabielf64.rst

Also the mode in which MTE would react to MTE faults is set by the tag
DT_AARCH64_MEMTAG_MODE.

This patch verifies for the presence of those tags in the dynamic array
and performs mprotect on the main application stack map with the extra
PROT_MTE flag.

---
 elf/dl-mte-stack.c                            | 27 +++++++
 elf/elf.h                                     |  8 +-
 sysdeps/aarch64/Makefile                      |  3 +-
 sysdeps/aarch64/cpu-features.h                | 15 ++++
 sysdeps/aarch64/dl-mte.c                      | 50 +++++++++++++
 sysdeps/aarch64/dl-prop.h                     |  4 +
 sysdeps/unix/sysv/linux/aarch64/Makefile      |  5 ++
 .../unix/sysv/linux/aarch64/dl-mte-stack.c    | 74 +++++++++++++++++++
 8 files changed, 184 insertions(+), 2 deletions(-)
 create mode 100644 elf/dl-mte-stack.c
 create mode 100644 sysdeps/aarch64/dl-mte.c
 create mode 100644 sysdeps/unix/sysv/linux/aarch64/dl-mte-stack.c
  

Comments

Adhemerval Zanella Netto March 19, 2025, 7:05 p.m. UTC | #1
On 14/03/25 10:21, Cupertino Miranda wrote:
> MTE stack tagging enabled executables are flagged by setting the dynamic
> array tag DT_AARCH64_MEMTAG_STACK as described in section 6 from:
> https://github.com/ARM-software/abi-aa/blob/main/memtagabielf64/memtagabielf64.rst
> 
> Also the mode in which MTE would react to MTE faults is set by the tag
> DT_AARCH64_MEMTAG_MODE.
> 
> This patch verifies for the presence of those tags in the dynamic array
> and performs mprotect on the main application stack map with the extra
> PROT_MTE flag.
> 
> ---
>  elf/dl-mte-stack.c                            | 27 +++++++
>  elf/elf.h                                     |  8 +-
>  sysdeps/aarch64/Makefile                      |  3 +-
>  sysdeps/aarch64/cpu-features.h                | 15 ++++
>  sysdeps/aarch64/dl-mte.c                      | 50 +++++++++++++
>  sysdeps/aarch64/dl-prop.h                     |  4 +
>  sysdeps/unix/sysv/linux/aarch64/Makefile      |  5 ++
>  .../unix/sysv/linux/aarch64/dl-mte-stack.c    | 74 +++++++++++++++++++
>  8 files changed, 184 insertions(+), 2 deletions(-)
>  create mode 100644 elf/dl-mte-stack.c
>  create mode 100644 sysdeps/aarch64/dl-mte.c
>  create mode 100644 sysdeps/unix/sysv/linux/aarch64/dl-mte-stack.c
> 
> diff --git a/elf/dl-mte-stack.c b/elf/dl-mte-stack.c
> new file mode 100644
> index 0000000000..f0063b54a0
> --- /dev/null
> +++ b/elf/dl-mte-stack.c
> @@ -0,0 +1,27 @@
> +/* Stack memory tagging support.  Stub version.
> +   Copyright (C) 2003-2025 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 <ldsodefs.h>
> +#include <errno.h>
> +
> +void
> +_dl_mte_stack_protect (void *stack_end)
> +{
> +  return ENOSYS;
> +}
> +rtld_hidden_def (_dl_mte_stack_protect)

I don't think it makes sense now to export the memory tagging support as
a generic ABI, even though this is not really aarch64 specific (RISCV
has Zimte).

The _dl_mte_stack_protect is only called in aarch64 code anyway.


> diff --git a/elf/elf.h b/elf/elf.h
> index 96df2eec01..daf0e52b15 100644
> --- a/elf/elf.h
> +++ b/elf/elf.h
> @@ -1392,6 +1392,7 @@ typedef struct
>  #define GNU_PROPERTY_AARCH64_FEATURE_1_BTI	(1U << 0)
>  #define GNU_PROPERTY_AARCH64_FEATURE_1_PAC	(1U << 1)
>  #define GNU_PROPERTY_AARCH64_FEATURE_1_GCS	(1U << 2)
> +#define GNU_PROPERTY_AARCH64_FEATURE_1_MTE_STACK	(1U << 3)
>  
>  /* The x86 instruction sets indicated by the corresponding bits are
>     used in program.  Their support in the hardware is optional.  */
> @@ -3049,7 +3050,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/sysdeps/aarch64/Makefile b/sysdeps/aarch64/Makefile
> index 4b7f8a5c07..f93ef636ac 100644
> --- a/sysdeps/aarch64/Makefile
> +++ b/sysdeps/aarch64/Makefile
> @@ -11,7 +11,8 @@ endif
>  ifeq ($(subdir),elf)
>  sysdep-dl-routines += \
>    dl-bti \
> -  dl-gcs
> +  dl-gcs \
> +  dl-mte
>  
>  tests += tst-audit26 \
>  	 tst-audit27
> diff --git a/sysdeps/aarch64/cpu-features.h b/sysdeps/aarch64/cpu-features.h
> index ef4e947e8c..0761549718 100644
> --- a/sysdeps/aarch64/cpu-features.h
> +++ b/sysdeps/aarch64/cpu-features.h
> @@ -66,4 +66,19 @@ struct cpu_features
>    bool mops;
>  };
>  
> +#define ARCH_MTE_MODE_SYNC (1 << 1)
> +#define ARCH_MTE_MODE_ASYNC (1 << 2)
> +#define AARCH64_CPU_FEATURE_MTE_STATE_STACK (1 << 3)
> +
> +#define AARCH64_CPU_FEATURE_MTE_STATE_MODE_MASK (0x3)
> +
> +#define AARCH64_CPU_FEATURE_MTE_STATE_MODE_MASK (0x3)
> +#define AARCH64_CPU_FEATURE_MTE_STATE_MODE_ASYNC (1)
> +#define AARCH64_CPU_FEATURE_MTE_STATE_MODE_SYNC (1 << 1)
> +
> +
> +#ifndef ARCH_INIT_MEMORY_STACK_TAGGING
> +#define ARCH_INIT_MEMORY_STACK_TAGGING _dl_mte_stack_protect
> +#endif
> +

This is not any used anywhere.

>  #endif /* _CPU_FEATURES_AARCH64_H  */
> diff --git a/sysdeps/aarch64/dl-mte.c b/sysdeps/aarch64/dl-mte.c
> new file mode 100644
> index 0000000000..30740d1eca
> --- /dev/null
> +++ b/sysdeps/aarch64/dl-mte.c
> @@ -0,0 +1,50 @@
> +/* AArch64 MTE Stack functions.
> +   Copyright (C) 2020-2025 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 <unistd.h>
> +#include <ldsodefs.h>
> +#include <assert.h>
> +
> +extern void
> +_dl_mte_stack_protect (void *stack_end);
> +
> +void
> +_dl_mte_stack_check (struct link_map *l, const char *program)
> +{
> +  /* TODO: Replace the dynamic info by gnu.property entry.  */

Not sure what this comment means, is the idea to add GNU_PROPERTY_AARCH64_FEATURE_1_MTE_XXX?

> +  ElfW(Dyn) *d;
> +  for (d = l->l_ld; d->d_tag != DT_NULL; ++d)
> +    {
> +      if (d->d_tag == DT_AARCH64_MEMTAG_STACK)
> +        GLRO(dl_aarch64_cpu_features).mte_state |= AARCH64_CPU_FEATURE_MTE_STATE_STACK;
> +      if (d->d_tag == DT_AARCH64_MEMTAG_MODE)
> +        {
> +          if (d->d_un.d_val == 1)
> +          {
> +	    GLRO(dl_aarch64_cpu_features).mte_state &= ~AARCH64_CPU_FEATURE_MTE_STATE_MODE_MASK;
> +            GLRO(dl_aarch64_cpu_features).mte_state |= AARCH64_CPU_FEATURE_MTE_STATE_MODE_ASYNC;
> +          }
> +          else
> +          {
> +            GLRO(dl_aarch64_cpu_features).mte_state &= ~AARCH64_CPU_FEATURE_MTE_STATE_MODE_MASK;
> +            GLRO(dl_aarch64_cpu_features).mte_state |= AARCH64_CPU_FEATURE_MTE_STATE_MODE_ASYNC;

Nit: line too long.

> +          }
> +        }
> +    }
> +
> +    _dl_mte_stack_protect (__libc_stack_end);
> +}
> diff --git a/sysdeps/aarch64/dl-prop.h b/sysdeps/aarch64/dl-prop.h
> index abca2be7fa..66b05c1b75 100644
> --- a/sysdeps/aarch64/dl-prop.h
> +++ b/sysdeps/aarch64/dl-prop.h
> @@ -27,11 +27,15 @@ extern void _dl_bti_check (struct link_map *, const char *)
>  extern void _dl_gcs_check (struct link_map *, const char *)
>      attribute_hidden;
>  
> +extern void _dl_mte_stack_check (struct link_map *, const char *)
> +    attribute_hidden;
> +
>  static inline void __attribute__ ((always_inline))
>  _rtld_main_check (struct link_map *m, const char *program)
>  {
>    _dl_bti_check (m, program);
>    _dl_gcs_check (m, program);
> +  _dl_mte_stack_check (m, program);
>  }
>  
>  static inline void __attribute__ ((always_inline))
> diff --git a/sysdeps/unix/sysv/linux/aarch64/Makefile b/sysdeps/unix/sysv/linux/aarch64/Makefile
> index 1fdad67fae..8fb0e65b2a 100644
> --- a/sysdeps/unix/sysv/linux/aarch64/Makefile
> +++ b/sysdeps/unix/sysv/linux/aarch64/Makefile
> @@ -5,6 +5,11 @@ tests += \
>    # tests
>  endif
>  
> +ifeq ($(subdir),elf)
> +sysdep-dl-routines += \
> +  dl-mte-stack
> +endif
> +
>  ifeq ($(subdir),stdlib)
>  gen-as-const-headers += ucontext_i.sym
>  endif
> 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..a4bc82c99b
> --- /dev/null
> +++ b/sysdeps/unix/sysv/linux/aarch64/dl-mte-stack.c
> @@ -0,0 +1,74 @@
> +/* Memory tagging handling for GNU dynamic linker.  Stub version.
> +   Copyright (C) 2003-2025 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 <ldsodefs.h>
> +#include <libintl.h>
> +#include <not-cancel.h>
> +#include <sys/prctl.h>
> +
> +#define MTE_ALLOWED_TAGS (0xfffe << PR_MTE_TAG_SHIFT)
> +
> +static int
> +_dl_mte_mode (void)

Use bool here.

> +{
> +  if (GLRO(dl_aarch64_cpu_features).mte_state & AARCH64_CPU_FEATURE_MTE_STATE_MODE_SYNC)
> +    __prctl (PR_SET_TAGGED_ADDR_CTRL,
> +	     (PR_TAGGED_ADDR_ENABLE | PR_MTE_TCF_SYNC | MTE_ALLOWED_TAGS),
> +	     0, 0, 0);
> +  else if (GLRO(dl_aarch64_cpu_features).mte_state & AARCH64_CPU_FEATURE_MTE_STATE_MODE_ASYNC)
> +    __prctl (PR_SET_TAGGED_ADDR_CTRL,
> +	     (PR_TAGGED_ADDR_ENABLE | PR_MTE_TCF_ASYNC | MTE_ALLOWED_TAGS),
> +	     0, 0, 0);
> +
> +  return ((GLRO(dl_aarch64_cpu_features).mte_state & AARCH64_CPU_FEATURE_MTE_STATE_MODE_MASK) != 0);
> +}
> +

The MTE mode is already set at init_cpu_features iff USE_MTAG (set by
--enable-memory-tagging) is used.  It is not wrong to set it again, but
I would expect that MTE stack support would be also gated by USE_MTAG.

The current MTE support on glibc is enabled by the glibc.mem.tagging
tunable; and the tunable is also the same strategy used by GCS. It is
mainly to enable a better transition once binaries start to use the new
ELF markings.  The tunable API also allows us to change it over release,
so we can eventually phase it out in favor of Memtag ABI extension 
tagging.

So I think we should use the same strategy here.  The GCS supports multiple
values, but essentially it is:

  0 = disabled: do not enable GCS.

  1 = enforced: check markings and fail if any binary is not marked.

  2 = optional: check markings but keep GCS off if any binary is unmarked.

  3 = override: enable GCS, markings are ignored.

For DT_AARCH64_MEMTAG_STACK/DT_AARCH64_MEMTAG_HEAP the Memtag ABI extension 
says the entry is only valid on the main executable, while 
DT_AARCH64_MEMTAG_GLOBALS is defined per shared objects.  So I think either
'1' (enforced) or value '2' (optional) are not applicable.

There is an extra issue, where heap support was added prior Memtag ABI
extension. I think we should keep the memory tagging malloc support, but
remove the aarch64 mode to a different tunable.

So I one suggestion would be add a new glibc.cpu.aarch64_mte tunable with
the semantic of:

  0 = disable, do not enable MTE even if ELF has the DT_AARCH64_MEMTAG
      support.  This the default.

  1 = enforce, follow the DT_AARCH64_MEMTAG marking of the binary.

  2 = override, enable MTE even if there is not DT_AARCH64_MEMTAG where
      applicable.

Along with glibc.cpu.aarch64_mte_mode:

  0 = async, PR_MTE_TCF_ASYNC.

  1 = sync, PR_MTE_TCF_SYNC.

  2 = system-preferred faulting mode

The glibc.mem.tagging will be disassociated from the MTE mode (since
it ties aarch64 specific bits to a generic interface), and it would
be just a disable (1) / enable (0) mode.

So to enable heap memory tagging, it would a matter to:

  GLIBC_TUNABLE=glibc.cpu.aarch64_mte=2:glibc.mem.tagging=1

The DT_AARCH64_MEMTAG_HEAP should be easier to implement, where is just
acts as glibc.mem.tagging.

It is also confusing that you are reusing the mte_state filed, which is
initialized by the tunable, to also keep track of MTE support. Another
option is move the glibc.mem.tagging to be aarch64 specific anyway
and rename it to glibc.aarc64.mem_tagging or glibc.aarch64.mte_heap.

> +void
> +_dl_mte_stack_protect (void *stack_end)
> +{
> +  if ((GLRO(dl_aarch64_cpu_features).mte_state & AARCH64_CPU_FEATURE_MTE_STATE_STACK) == 0
> +      ||  !_dl_mte_mode ())
> +    return;
> +
> +  int errval = 0;
> +  uintptr_t page = ((uintptr_t) stack_end
> +		    & -(intptr_t) GLRO(dl_pagesize));
> +
> +  if (__mprotect ((void *) page, GLRO(dl_pagesize),
> +		  PROT_READ | PROT_WRITE | PROT_MTE
> +		  | (GL(dl_stack_flags) & PF_X ? PROT_EXEC : 0)
> +#if _STACK_GROWS_DOWN
> +		  | PROT_GROWSDOWN
> +#elif _STACK_GROWS_UP
> +		  | PROT_GROWSUP
> +#endif
> +		  ) != 0)
> +    errval = errno;
> +
> +  if (errval)
> +    {
> +      const char *errstring = NULL;
> +     errstring = N_("\
> +cannot set stack with PROT_MTE");
> +      const char *name = "MTE Stack";
> +      struct dl_exception exception;
> +      _dl_exception_create (&exception, name, errstring);
> +      _dl_signal_exception (errval, &exception, NULL);
> +    }
> +}
> +
  

Patch

diff --git a/elf/dl-mte-stack.c b/elf/dl-mte-stack.c
new file mode 100644
index 0000000000..f0063b54a0
--- /dev/null
+++ b/elf/dl-mte-stack.c
@@ -0,0 +1,27 @@ 
+/* Stack memory tagging support.  Stub version.
+   Copyright (C) 2003-2025 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 <ldsodefs.h>
+#include <errno.h>
+
+void
+_dl_mte_stack_protect (void *stack_end)
+{
+  return ENOSYS;
+}
+rtld_hidden_def (_dl_mte_stack_protect)
diff --git a/elf/elf.h b/elf/elf.h
index 96df2eec01..daf0e52b15 100644
--- a/elf/elf.h
+++ b/elf/elf.h
@@ -1392,6 +1392,7 @@  typedef struct
 #define GNU_PROPERTY_AARCH64_FEATURE_1_BTI	(1U << 0)
 #define GNU_PROPERTY_AARCH64_FEATURE_1_PAC	(1U << 1)
 #define GNU_PROPERTY_AARCH64_FEATURE_1_GCS	(1U << 2)
+#define GNU_PROPERTY_AARCH64_FEATURE_1_MTE_STACK	(1U << 3)
 
 /* The x86 instruction sets indicated by the corresponding bits are
    used in program.  Their support in the hardware is optional.  */
@@ -3049,7 +3050,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/sysdeps/aarch64/Makefile b/sysdeps/aarch64/Makefile
index 4b7f8a5c07..f93ef636ac 100644
--- a/sysdeps/aarch64/Makefile
+++ b/sysdeps/aarch64/Makefile
@@ -11,7 +11,8 @@  endif
 ifeq ($(subdir),elf)
 sysdep-dl-routines += \
   dl-bti \
-  dl-gcs
+  dl-gcs \
+  dl-mte
 
 tests += tst-audit26 \
 	 tst-audit27
diff --git a/sysdeps/aarch64/cpu-features.h b/sysdeps/aarch64/cpu-features.h
index ef4e947e8c..0761549718 100644
--- a/sysdeps/aarch64/cpu-features.h
+++ b/sysdeps/aarch64/cpu-features.h
@@ -66,4 +66,19 @@  struct cpu_features
   bool mops;
 };
 
+#define ARCH_MTE_MODE_SYNC (1 << 1)
+#define ARCH_MTE_MODE_ASYNC (1 << 2)
+#define AARCH64_CPU_FEATURE_MTE_STATE_STACK (1 << 3)
+
+#define AARCH64_CPU_FEATURE_MTE_STATE_MODE_MASK (0x3)
+
+#define AARCH64_CPU_FEATURE_MTE_STATE_MODE_MASK (0x3)
+#define AARCH64_CPU_FEATURE_MTE_STATE_MODE_ASYNC (1)
+#define AARCH64_CPU_FEATURE_MTE_STATE_MODE_SYNC (1 << 1)
+
+
+#ifndef ARCH_INIT_MEMORY_STACK_TAGGING
+#define ARCH_INIT_MEMORY_STACK_TAGGING _dl_mte_stack_protect
+#endif
+
 #endif /* _CPU_FEATURES_AARCH64_H  */
diff --git a/sysdeps/aarch64/dl-mte.c b/sysdeps/aarch64/dl-mte.c
new file mode 100644
index 0000000000..30740d1eca
--- /dev/null
+++ b/sysdeps/aarch64/dl-mte.c
@@ -0,0 +1,50 @@ 
+/* AArch64 MTE Stack functions.
+   Copyright (C) 2020-2025 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 <unistd.h>
+#include <ldsodefs.h>
+#include <assert.h>
+
+extern void
+_dl_mte_stack_protect (void *stack_end);
+
+void
+_dl_mte_stack_check (struct link_map *l, const char *program)
+{
+  /* TODO: Replace the dynamic info by gnu.property entry.  */
+  ElfW(Dyn) *d;
+  for (d = l->l_ld; d->d_tag != DT_NULL; ++d)
+    {
+      if (d->d_tag == DT_AARCH64_MEMTAG_STACK)
+        GLRO(dl_aarch64_cpu_features).mte_state |= AARCH64_CPU_FEATURE_MTE_STATE_STACK;
+      if (d->d_tag == DT_AARCH64_MEMTAG_MODE)
+        {
+          if (d->d_un.d_val == 1)
+          {
+	    GLRO(dl_aarch64_cpu_features).mte_state &= ~AARCH64_CPU_FEATURE_MTE_STATE_MODE_MASK;
+            GLRO(dl_aarch64_cpu_features).mte_state |= AARCH64_CPU_FEATURE_MTE_STATE_MODE_ASYNC;
+          }
+          else
+          {
+            GLRO(dl_aarch64_cpu_features).mte_state &= ~AARCH64_CPU_FEATURE_MTE_STATE_MODE_MASK;
+            GLRO(dl_aarch64_cpu_features).mte_state |= AARCH64_CPU_FEATURE_MTE_STATE_MODE_ASYNC;
+          }
+        }
+    }
+
+    _dl_mte_stack_protect (__libc_stack_end);
+}
diff --git a/sysdeps/aarch64/dl-prop.h b/sysdeps/aarch64/dl-prop.h
index abca2be7fa..66b05c1b75 100644
--- a/sysdeps/aarch64/dl-prop.h
+++ b/sysdeps/aarch64/dl-prop.h
@@ -27,11 +27,15 @@  extern void _dl_bti_check (struct link_map *, const char *)
 extern void _dl_gcs_check (struct link_map *, const char *)
     attribute_hidden;
 
+extern void _dl_mte_stack_check (struct link_map *, const char *)
+    attribute_hidden;
+
 static inline void __attribute__ ((always_inline))
 _rtld_main_check (struct link_map *m, const char *program)
 {
   _dl_bti_check (m, program);
   _dl_gcs_check (m, program);
+  _dl_mte_stack_check (m, program);
 }
 
 static inline void __attribute__ ((always_inline))
diff --git a/sysdeps/unix/sysv/linux/aarch64/Makefile b/sysdeps/unix/sysv/linux/aarch64/Makefile
index 1fdad67fae..8fb0e65b2a 100644
--- a/sysdeps/unix/sysv/linux/aarch64/Makefile
+++ b/sysdeps/unix/sysv/linux/aarch64/Makefile
@@ -5,6 +5,11 @@  tests += \
   # tests
 endif
 
+ifeq ($(subdir),elf)
+sysdep-dl-routines += \
+  dl-mte-stack
+endif
+
 ifeq ($(subdir),stdlib)
 gen-as-const-headers += ucontext_i.sym
 endif
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..a4bc82c99b
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/aarch64/dl-mte-stack.c
@@ -0,0 +1,74 @@ 
+/* Memory tagging handling for GNU dynamic linker.  Stub version.
+   Copyright (C) 2003-2025 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 <ldsodefs.h>
+#include <libintl.h>
+#include <not-cancel.h>
+#include <sys/prctl.h>
+
+#define MTE_ALLOWED_TAGS (0xfffe << PR_MTE_TAG_SHIFT)
+
+static int
+_dl_mte_mode (void)
+{
+  if (GLRO(dl_aarch64_cpu_features).mte_state & AARCH64_CPU_FEATURE_MTE_STATE_MODE_SYNC)
+    __prctl (PR_SET_TAGGED_ADDR_CTRL,
+	     (PR_TAGGED_ADDR_ENABLE | PR_MTE_TCF_SYNC | MTE_ALLOWED_TAGS),
+	     0, 0, 0);
+  else if (GLRO(dl_aarch64_cpu_features).mte_state & AARCH64_CPU_FEATURE_MTE_STATE_MODE_ASYNC)
+    __prctl (PR_SET_TAGGED_ADDR_CTRL,
+	     (PR_TAGGED_ADDR_ENABLE | PR_MTE_TCF_ASYNC | MTE_ALLOWED_TAGS),
+	     0, 0, 0);
+
+  return ((GLRO(dl_aarch64_cpu_features).mte_state & AARCH64_CPU_FEATURE_MTE_STATE_MODE_MASK) != 0);
+}
+
+void
+_dl_mte_stack_protect (void *stack_end)
+{
+  if ((GLRO(dl_aarch64_cpu_features).mte_state & AARCH64_CPU_FEATURE_MTE_STATE_STACK) == 0
+      ||  !_dl_mte_mode ())
+    return;
+
+  int errval = 0;
+  uintptr_t page = ((uintptr_t) stack_end
+		    & -(intptr_t) GLRO(dl_pagesize));
+
+  if (__mprotect ((void *) page, GLRO(dl_pagesize),
+		  PROT_READ | PROT_WRITE | PROT_MTE
+		  | (GL(dl_stack_flags) & PF_X ? PROT_EXEC : 0)
+#if _STACK_GROWS_DOWN
+		  | PROT_GROWSDOWN
+#elif _STACK_GROWS_UP
+		  | PROT_GROWSUP
+#endif
+		  ) != 0)
+    errval = errno;
+
+  if (errval)
+    {
+      const char *errstring = NULL;
+     errstring = N_("\
+cannot set stack with PROT_MTE");
+      const char *name = "MTE Stack";
+      struct dl_exception exception;
+      _dl_exception_create (&exception, name, errstring);
+      _dl_signal_exception (errval, &exception, NULL);
+    }
+}
+