[PATCH v2] aarch64: Add MTE mode tunable
Adhemerval Zanella Netto
adhemerval.zanella@linaro.org
Thu Sep 10 14:30:01 GMT 2026
On 07/09/26 11:02, Yury Khrustalev wrote:
> Add tunable glibc.cpu.mtemode for selecting tag checking fault mode
> for Memory Tagging Extension (MTE): disabled, enabled, sync, or async.
> Default is disabled.
>
> ---
> Passes regression in aarch64. OK for trunk?
>
> Please note that this patch does not include support for MTE binary
> marking as there is no intention to implement it at this stage.
> However, this patch is designed to support such an extension in the
> future.
>
> Relevant discussion:
> https://inbox.sourceware.org/libc-alpha/ansmVLinBE86-SZ4@arm.com/
>
> Base commit: fe03757f67
>
> Changes in v2:
> - Don't add the 'mte' property to the cpu_features struct.
> - Define bool variable for MTE for ifunc resolvers based on
> the MTE mode value.
> - v1: https://inbox.sourceware.org/libc-alpha/20260904094800.1810189-1-yury.khrustalev@arm.com/
It is not clear to me why add this support without any MTE usage within
glibc. All these MTE enablement can done by the application itself, if
it requires so (like a MTE aware malloc).
I think this feature should added along the MTE malloc you seems to be
working on.
>
> ---
> manual/tunables.texi | 27 ++++++
> sysdeps/aarch64/Makefile | 1 +
> sysdeps/aarch64/cpu-features.h | 17 ++++
> sysdeps/aarch64/dl-mte.c | 77 ++++++++++++++++
> sysdeps/aarch64/dl-prop.h | 4 +
> sysdeps/aarch64/dl-start.S | 10 ++-
> sysdeps/aarch64/dl-tunables.list | 3 +
> sysdeps/aarch64/multiarch/init-arch.h | 3 +-
> sysdeps/unix/sysv/linux/aarch64/Makefile | 5 ++
> .../unix/sysv/linux/aarch64/cpu-features.c | 22 +++++
> .../unix/sysv/linux/aarch64/dl-procruntime.c | 32 +++++++
> sysdeps/unix/sysv/linux/aarch64/libc-start.h | 6 ++
> .../sysv/linux/aarch64/tst-mtemode-tunable.c | 90 +++++++++++++++++++
> 13 files changed, 292 insertions(+), 5 deletions(-)
> create mode 100644 sysdeps/aarch64/dl-mte.c
> create mode 100644 sysdeps/unix/sysv/linux/aarch64/tst-mtemode-tunable.c
>
> diff --git a/manual/tunables.texi b/manual/tunables.texi
> index 9052c56d9b..bbff2f5fdd 100644
> --- a/manual/tunables.texi
> +++ b/manual/tunables.texi
> @@ -760,6 +760,33 @@ locked. GCS markings will not be checked for any binaries.
>
> @end deftp
>
> +@deftp Tunable glibc.cpu.mtemode
> +On AArch64 Linux systems that support Memory Tagging Extension (MTE) this
> +tunable allows to select the fault mode for memory tag checking (MTE mode).
> +
> +Supported values are:
> +
> +@itemize @bullet
> +@item @code{disabled}: (the default), memory tagging is not enabled and the
> +CPU and the kernel ignore tag checking faults.
> +@item @code{enabled}: Use system-preferred tag checking mode. This value is
> +intended for general purpose use cases.
> +@item @code{sync}: Select synchronous tag checking fault mode: when tag check
> +fault occurs, a @code{SIGSEGV} is raised synchronously and memory access is not
> +performed. This value is useful for debugging.
> +@item @code{async}: Use asynchronous tag checking fault mode: a @code{SIGSEGV}
> +is raised asynchronously following one or multiple tag check faults.
> +@end itemize
> +
> +Any incorrect value or an empty value of this tunable will result in
> +the @code{enabled} value being used. When tunable is not provided, the
> +default value @code{disabled} is used.
> +
> +The system-preferred MTE mode can be configured via sysfs as described in
> +@url{https://www.kernel.org/doc/html/latest/arch/arm64/memory-tagging-extension.html}.
> +
> +@end deftp
> +
> @node Memory Related Tunables
> @section Memory Related Tunables
> @cindex memory related tunables
> 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/cpu-features.h b/sysdeps/aarch64/cpu-features.h
> index b7dab3dff4..022335405c 100644
> --- a/sysdeps/aarch64/cpu-features.h
> +++ b/sysdeps/aarch64/cpu-features.h
> @@ -59,6 +59,23 @@ enum {
> BTI_CHECK_ENFORCED = 1,
> };
>
> +enum {
> + MTE_MODE_DISABLED = 0,
> + MTE_MODE_ENABLED = 1,
> + MTE_MODE_SYNC = 2,
> + MTE_MODE_ASYNC = 3,
> +};
> +
> +enum
> +{
> + MTE_SCOPE_NONE = 0,
> + MTE_SCOPE_HEAP = 1 << 0,
> + MTE_SCOPE_STACK = 1 << 1,
> + MTE_SCOPE_GLOBALS = 1 << 2,
> +};
> +
> +#define MTE_SCOPE_STANDARD (MTE_SCOPE_HEAP)
> +
> 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..e4650ed22e
> --- /dev/null
> +++ b/sysdeps/aarch64/dl-mte.c
> @@ -0,0 +1,77 @@
> +/* AArch64 implementation for MTE (memory tagging).
> + Copyright (C) 2026 Free Software Foundation, Inc.
> + This file is part of the GNU C Library.
> +
> + The GNU C Library is free software; you can redistribute it and/or
> + modify it under the terms of the GNU Lesser General Public
> + License as published by the Free Software Foundation; either
> + version 2.1 of the License, or (at your option) any later version.
> +
> + The GNU C Library is distributed in the hope that it will be useful,
> + but WITHOUT ANY WARRANTY; without even the implied warranty of
> + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + Lesser General Public License for more details.
> +
> + You should have received a copy of the GNU Lesser General Public
> + License along with the GNU C Library; if not, see
> + <https://www.gnu.org/licenses/>. */
> +
> +#include <sys/cdefs.h>
> +#include <ldsodefs.h>
> +
> +/* For the MTE prctl syscall. */
> +#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)
> +
> +/* The maximal set of permitted tags that the MTE random tag generation
> + instruction may use. We exclude tag 0 because a) we want to reserve
> + that for the libc heap structures and b) because it makes it easier
> + to see when pointer have been correctly tagged. */
> +#define MTE_ALLOWED_TAGS (0xfffe << PR_MTE_TAG_SHIFT)
> +
> +void __mte_init (void);
> +rtld_hidden_proto (__mte_init)
> +
> +void __mte_init (void)
> +{
> + unsigned int mode = GL (dl_aarch64_mte_mode);
> + if (mode == MTE_MODE_DISABLED)
> + return;
> + uint64_t flags = PR_TAGGED_ADDR_ENABLE | MTE_ALLOWED_TAGS;
> + switch (mode)
> + {
> + case MTE_MODE_ENABLED:
> + flags |= PR_MTE_TCF_SYNC | PR_MTE_TCF_ASYNC;
> + break;
> + case MTE_MODE_SYNC:
> + flags |= PR_MTE_TCF_SYNC;
> + break;
> + case MTE_MODE_ASYNC:
> + flags |= PR_MTE_TCF_ASYNC;
> + break;
> + default:
> + _dl_fatal_printf ("unknown MTE mode: %d\n", mode);
> + }
> + /* We use inline system call to avoid unnecessary dependency
> + on the sys/prctl.h header. */
> + 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\n");
> +}
> +rtld_hidden_def (__mte_init)
> +
> +void
> +_dl_mte_check (struct link_map *l, const char *program)
> +{
> + /* MTE binary marking can be checked here when support for memtag ABI
> + is added. When the main binary has no MTE marking, we enable only
> + standard protection when requested MTE mode is not 'none'.
> + Currently standard protection includes: heap. */
> + if (GL (dl_aarch64_mte_mode) == MTE_MODE_DISABLED)
> + GL (dl_aarch64_mte_scope) = MTE_SCOPE_NONE;
> + else
> + GL (dl_aarch64_mte_scope) = MTE_SCOPE_STANDARD;
> +}
> diff --git a/sysdeps/aarch64/dl-prop.h b/sysdeps/aarch64/dl-prop.h
> index 0d2672d32d..26fc639405 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 *, int)
> attribute_hidden;
>
> +extern void _dl_mte_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, 0);
> + _dl_mte_check (m, program);
> }
>
> static inline void __attribute__ ((always_inline))
> diff --git a/sysdeps/aarch64/dl-start.S b/sysdeps/aarch64/dl-start.S
> index c278485cd3..2d8a2710a6 100644
> --- a/sysdeps/aarch64/dl-start.S
> +++ b/sysdeps/aarch64/dl-start.S
> @@ -33,9 +33,9 @@ ENTRY (_start)
> mov x21, x0
>
> /* Use GL(dl_aarch64_gcs) to set the shadow stack status. */
> - adrp x16, _rtld_local
> - add x16, x16, :lo12:_rtld_local
> - ldr x22, [x16, GL_DL_AARCH64_GCS_OFFSET]
> + adrp x23, _rtld_local
> + add x23, x23, :lo12:_rtld_local
> + ldr x22, [x23, GL_DL_AARCH64_GCS_OFFSET]
> cbz x22, L(skip_gcs_enable)
>
> /* Enable GCS before user code runs. Note that IFUNC resolvers and
> @@ -66,6 +66,8 @@ ENTRY (_start)
> cbnz w0, L(failed_gcs_lock)
> L(skip_gcs_enable):
>
> + bl HIDDEN_JUMPTARGET(__mte_init)
> +
> .globl _dl_start_user
> .type _dl_start_user, %function
> _dl_start_user:
> @@ -77,7 +79,7 @@ _dl_start_user:
> add x3, x2, x1, lsl 3
> add x3, x3, 8
> /* Run the init functions of the loaded modules. */
> - ldr x0, [x16]
> + ldr x0, [x23]
> bl _dl_init
> /* Load the finalizer function. */
> adrp x0, _dl_fini
> diff --git a/sysdeps/aarch64/dl-tunables.list b/sysdeps/aarch64/dl-tunables.list
> index c876f3fe0d..03491ca2c2 100644
> --- a/sysdeps/aarch64/dl-tunables.list
> +++ b/sysdeps/aarch64/dl-tunables.list
> @@ -33,5 +33,8 @@ glibc {
> maxval: 3
> default: 0
> }
> + mtemode {
> + type: STRING
> + }
> }
> }
> diff --git a/sysdeps/aarch64/multiarch/init-arch.h b/sysdeps/aarch64/multiarch/init-arch.h
> index e00d1746d8..4ed3dbfc8b 100644
> --- a/sysdeps/aarch64/multiarch/init-arch.h
> +++ b/sysdeps/aarch64/multiarch/init-arch.h
> @@ -25,7 +25,8 @@
> unsigned __attribute__((unused)) zva_size = \
> GLRO(dl_aarch64_cpu_features).zva_size; \
> bool __attribute__((unused)) bti = GLRO(dl_aarch64_cpu_features).bti; \
> - bool __attribute__((unused)) mte = GLRO(dl_hwcap2) & HWCAP2_MTE; \
> + bool __attribute__((unused)) mte = \
> + (GL (dl_aarch64_mte_mode) != MTE_MODE_DISABLED); \
> bool __attribute__((unused)) sve = GLRO(dl_aarch64_cpu_features).sve; \
> bool __attribute__((unused)) sve2 = GLRO(dl_aarch64_cpu_features).sve2; \
> bool __attribute__((unused)) mops = GLRO(dl_aarch64_cpu_features).mops;
> diff --git a/sysdeps/unix/sysv/linux/aarch64/Makefile b/sysdeps/unix/sysv/linux/aarch64/Makefile
> index baefac15b5..4debe41df6 100644
> --- a/sysdeps/unix/sysv/linux/aarch64/Makefile
> +++ b/sysdeps/unix/sysv/linux/aarch64/Makefile
> @@ -19,6 +19,11 @@ tests-static += \
> # tests-static
> tst-cpu-tunable-static-pie-TUNABLES = glibc.cpu.hwcaps=-midr,-sve,-mops
> endif
> +
> +tests-internal += \
> + tst-mtemode-tunable \
> + # tests-internal
> +
> endif
>
> ifeq ($(subdir),misc)
> diff --git a/sysdeps/unix/sysv/linux/aarch64/cpu-features.c b/sysdeps/unix/sysv/linux/aarch64/cpu-features.c
> index 6a13638368..b1da4b0038 100644
> --- a/sysdeps/unix/sysv/linux/aarch64/cpu-features.c
> +++ b/sysdeps/unix/sysv/linux/aarch64/cpu-features.c
> @@ -59,6 +59,21 @@ TUNABLE_CALLBACK (set_hwcaps) (tunable_val_t *val)
> }
> }
>
> +static void
> +TUNABLE_CALLBACK (set_aarch64_mte_mode) (tunable_val_t *val)
> +{
> + if (tunable_strcmp_cte (val, "enabled"))
> + GL (dl_aarch64_mte_mode) = MTE_MODE_ENABLED;
> + else if (tunable_strcmp_cte (val, "sync"))
> + GL (dl_aarch64_mte_mode) = MTE_MODE_SYNC;
> + else if (tunable_strcmp_cte (val, "async"))
> + GL (dl_aarch64_mte_mode) = MTE_MODE_ASYNC;
> + else if (tunable_strcmp_cte (val, "disabled"))
> + GL (dl_aarch64_mte_mode) = MTE_MODE_DISABLED;
> + else
> + GL (dl_aarch64_mte_mode) = MTE_MODE_ENABLED;
> +}
> +
> static inline void
> init_cpu_features (struct cpu_features *cpu_features)
> {
> @@ -81,6 +96,13 @@ 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);
>
> + /* Check if MTE is supported. */
> + if (GLRO (dl_hwcap2) & HWCAP2_MTE)
> + TUNABLE_GET (glibc, cpu, mtemode, tunable_val_t *,
> + TUNABLE_CALLBACK (set_aarch64_mte_mode));
> + else
> + GL (dl_aarch64_mte_mode) = MTE_MODE_DISABLED;
> +
> /* Check if SVE is supported. */
> cpu_features->sve = GLRO (dl_hwcap) & HWCAP_SVE;
> cpu_features->sve2 = GLRO (dl_hwcap2) & HWCAP2_SVE2;
> diff --git a/sysdeps/unix/sysv/linux/aarch64/dl-procruntime.c b/sysdeps/unix/sysv/linux/aarch64/dl-procruntime.c
> index 1f3b58d0fc..3b1f8b93aa 100644
> --- a/sysdeps/unix/sysv/linux/aarch64/dl-procruntime.c
> +++ b/sysdeps/unix/sysv/linux/aarch64/dl-procruntime.c
> @@ -35,3 +35,35 @@ PROCINFO_CLASS unsigned long _dl_aarch64_gcs
> ,
> # endif
> #endif
> +
> +#if !IS_IN (ldconfig)
> +# if !defined PROCINFO_DECL && defined SHARED
> + ._dl_aarch64_mte_mode
> +# else
> +PROCINFO_CLASS unsigned int _dl_aarch64_mte_mode
> +# endif
> +# ifndef PROCINFO_DECL
> += MTE_MODE_DISABLED
> +# endif
> +# if !defined SHARED || defined PROCINFO_DECL
> +;
> +# else
> +,
> +# endif
> +#endif
> +
> +#if !IS_IN (ldconfig)
> +# if !defined PROCINFO_DECL && defined SHARED
> + ._dl_aarch64_mte_scope
> +# else
> +PROCINFO_CLASS unsigned int _dl_aarch64_mte_scope
> +# endif
> +# ifndef PROCINFO_DECL
> += MTE_SCOPE_NONE
> +# endif
> +# if !defined SHARED || defined PROCINFO_DECL
> +;
> +# else
> +,
> +# endif
> +#endif
> \ No newline at end of file
> diff --git a/sysdeps/unix/sysv/linux/aarch64/libc-start.h b/sysdeps/unix/sysv/linux/aarch64/libc-start.h
> index 53683ee511..3a7422f70c 100644
> --- a/sysdeps/unix/sysv/linux/aarch64/libc-start.h
> +++ b/sysdeps/unix/sysv/linux/aarch64/libc-start.h
> @@ -36,6 +36,9 @@
> # define GCS_POLICY_OPTIONAL 2
> # endif
>
> +void __mte_init (void);
> +rtld_hidden_proto (__mte_init)
> +
> /* Must be on a top-level stack frame that does not return. */
> static inline void __attribute__((always_inline))
> aarch64_libc_setup_tls (void)
> @@ -72,6 +75,9 @@ aarch64_libc_setup_tls (void)
> _dl_fatal_printf ("failed to lock GCS: %d\n", -ret);
> }
> }
> +
> + __mte_init ();
> +
> }
>
> # define ARCH_SETUP_IREL() apply_irel ()
> diff --git a/sysdeps/unix/sysv/linux/aarch64/tst-mtemode-tunable.c b/sysdeps/unix/sysv/linux/aarch64/tst-mtemode-tunable.c
> new file mode 100644
> index 0000000000..f23a794a17
> --- /dev/null
> +++ b/sysdeps/unix/sysv/linux/aarch64/tst-mtemode-tunable.c
> @@ -0,0 +1,90 @@
> +/* Test that glibc.cpu.mtemode selects corect mode for MTE tag checking.
> + 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 <stdio.h>
> +#include <unistd.h>
> +#include <sys/auxv.h>
> +
> +#include <array_length.h>
> +#include <support/check.h>
> +#include <support/xdlfcn.h>
> +#include <support/support.h>
> +#include <support/capture_subprocess.h>
> +
> +#include <cpu-features.h>
> +#define SHARED
> +#include <ldsodefs.h>
> +
> +static int
> +work (unsigned int expected)
> +{
> + /* Avoid introducing a copy relocation due to the hidden alias in
> + ld.so. */
> + struct rtld_global *gl = xdlsym (NULL, "_rtld_global");
> + TEST_COMPARE (gl->_dl_aarch64_mte_mode, expected);
> + return 0;
> +}
> +
> +static int
> +do_test_argv (int argc, char *argv[])
> +{
> + if (argc == 2)
> + return work (atol (argv[1]));
> +
> + unsigned long hwcap2 = getauxval (AT_HWCAP2);
> + if ((hwcap2 & HWCAP2_MTE) == 0)
> + FAIL_UNSUPPORTED ("MTE is not supported by this system");
> +
> + struct test_entry
> + {
> + unsigned int mode;
> + const char *tunable;
> + };
> +
> + struct test_entry tests[] = {
> + /* Test each explicit tunable value. */
> + { .mode = MTE_MODE_DISABLED, .tunable = "glibc.cpu.mtemode=disabled" },
> + { .mode = MTE_MODE_ENABLED, .tunable = "glibc.cpu.mtemode=enabled" },
> + { .mode = MTE_MODE_SYNC, .tunable = "glibc.cpu.mtemode=sync" },
> + { .mode = MTE_MODE_ASYNC, .tunable = "glibc.cpu.mtemode=async" },
> + /* Test wrong value and no tunable set. */
> + { .mode = MTE_MODE_ENABLED, .tunable = "glibc.cpu.mtemode=gibberish" },
> + { .mode = MTE_MODE_DISABLED, .tunable = "" },
> + };
> +
> + array_foreach (t, tests)
> + {
> + char *mode = xasprintf ("%u", t->mode);
> + char *tunable = xasprintf ("GLIBC_TUNABLES=%s", t->tunable);
> + char *subproc = xasprintf ("tst-mtemode-tunable-%s", t->tunable);
> + char *spargv[] = { argv[0], mode, NULL, };
> + char *spenvp[] = { tunable, NULL, };
> + struct support_capture_subprocess result;
> + result = support_capture_subprogram (spargv[0], spargv, spenvp);
> + support_capture_subprocess_check (&result, subproc, 0, sc_allow_none);
> + support_capture_subprocess_free (&result);
> + free (mode);
> + free (tunable);
> + free (subproc);
> + }
> +
> + return 0;
> +}
> +
> +#define TEST_FUNCTION_ARGV do_test_argv
> +#include <support/test-driver.c>
More information about the Libc-alpha
mailing list