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

Adhemerval Zanella Netto adhemerval.zanella@linaro.org
Fri Mar 13 18:51:10 GMT 2026



On 11/03/26 07:36, Yury Khrustalev wrote:
> On Tue, Mar 10, 2026 at 04:11:06PM +0200, claudiu.zissulescu-ianculescu@oracle.com wrote:
>> From: Cupertino Miranda <cupertino.miranda@oracle.com>
>>
>> This patch enables Memory Tag Extension (MTE) for stack tagging in the
>> rtld. Parse DT_AARCH64_MEMTAG_STACK and DT_AARCH64_MEMTAG_MODE from
>> the main program and enable stack memory tagging in the dynamic
>> loader, as described in ARM's ABI [1] documentation.
>>
>> Ref:
>> [1] https://github.com/ARM-software/abi-aa/blob/main/memtagabielf64/memtagabielf64.rst
>> ---
>>  elf/elf.h                                     |  7 +-
>>  sysdeps/aarch64/Makefile                      |  1 +
>>  sysdeps/aarch64/cpu-features.h                | 12 +++
>>  sysdeps/aarch64/dl-mte.c                      | 85 +++++++++++++++++++
>>  sysdeps/aarch64/dl-prop.h                     |  4 +
>>  sysdeps/unix/sysv/linux/aarch64/Makefile      |  4 +
>>  .../unix/sysv/linux/aarch64/dl-mte-stack.c    | 45 ++++++++++
>>  7 files changed, 157 insertions(+), 1 deletion(-)
>>  create mode 100644 sysdeps/aarch64/dl-mte.c
>>  create mode 100644 sysdeps/unix/sysv/linux/aarch64/dl-mte-stack.c
>>
> 
> This patch should have at least one test checking that the added
> functionality works as intended for each supported mode of stack
> tagging. Ideally, we should check both statically-linked and
> dynamically-linked cases.
> 
> Some notes below. In a nutshell, I think this experimental functionality
> should be hidden behind a configure flag or at least a tunable.
> 
> Note that there already is a configure flag "--enable-memory-tagging"
> which adds -DUSE_MTAG macro and unlock heap tagging in malloc which is
> why it's probably not a good idea to use the same guard for stack
> tagging.
> 
> Whatever way to disable stack tagging is chosen, it should somehow
> co-exist with the "--enable-memory-tagging" configure flag and the
> corresponding tunable "glibc.mem.tagging". Both of these two things
> may change soon to make memory tagging in malloc more usable. For this
> reason I would encourage a conversation about how these two things
> (tagging of heap and stack) can be controlled independently.
> 

It does not make sense to add a tunable for DT_AARCH64_MEMTAG_STACK, 
and, given that this is an opt-in feature, I am also not sure whether 
adding a configure check would be really worth it here.

A binary with DT_AARCH64_MEMTAG_STACK has text segments that always 
use MTE instruction, so running them on hardware without HWCAP2_MTE 
is expected to trigger SIGILL (with exceptions for possible 
misconfigured environments like qemu with -cpu max and -m virt,mte=off, 
and I am not sure how proper hardware is supported to work when MTE 
is not enabled by the kernel).

So, the most sensible behavior from the loader standpoint is to abort
startup with a proper error if DT_AARCH64_MEMTAG_STACK is present and 
the kernel does not support HWCAP2_MTE. A tunable will only mask this 
off and not prevent the SIGILL.

The --enable-memory-tagging option differs because glibc can control 
whether to tag memory (sysdeps/aarch64/libc-mtag.h), so it malloc can 
be used on hardware without MTE support. I would expect that 
DT_AARCH64_MEMTAG_HEAP would enforce its usage.

And I think we also need to define how DT_AARCH64_MEMTAG_STACK will 
play along with dlopen. The current approach will:

  1. On hardware without MTE, triggers a SIGILL when a binary without 
     MEMTAG_STACK dlopen a library with MEMTAG_STACK

  2. On hardware with MTE, dlopen a MTE will succeed without enabling 
     MTE on the stack.

For 1. dlopen requires failing similarly to how we do for GCS. The 2. 
is technically possible, but implementing it properly requires either 
stop-the-work mode (as sanitizers use with ptrace) or some synchronization 
between pthread_create and dlopen to change all thread stacks during 
process execution without any thread or dlopen altering the global 
thread state. 

Android does something like that [1] (with 
__pthread_internal_remap_stack_with_mte), with a global lock for 
pthread/dlopen. I am not sure if we should follow the same semantics.

[1] https://android.googlesource.com/platform/bionic/+/main/docs/mte.md

> 
>> diff --git a/elf/elf.h b/elf/elf.h
>> index 46a01281cb..02c4125cb1 100644
>> --- a/elf/elf.h
>> +++ b/elf/elf.h
> 
>>
>> ...
>>
>> diff --git a/sysdeps/aarch64/cpu-features.h b/sysdeps/aarch64/cpu-features.h
>> index 1fe35d986b..91e0ce9a90 100644
>> --- a/sysdeps/aarch64/cpu-features.h
>> +++ b/sysdeps/aarch64/cpu-features.h
>> @@ -74,4 +74,16 @@ struct cpu_features
>>    bool mops;
>>  };
>>  
>> +#define ARCH_MTE_MODE_SYNC (1 << 1)
>> +#define ARCH_MTE_MODE_ASYNC (1 << 2)
> 
> These two macros seem to be unused. If they are meant for the possible
> values of DT_AARCH64_MEMTAG_MODE, then they would be incorrect because
> possible values are 0 for sync and 1 for async mode.
> 
>> +#define AARCH64_CPU_FEATURE_MTE_STATE_STACK (1 << 3)
>> +
> 
>>
>> ...
>>
>> diff --git a/sysdeps/aarch64/dl-mte.c b/sysdeps/aarch64/dl-mte.c
>> new file mode 100644
>> index 0000000000..e73c5311a6
>> --- /dev/null
>> +++ b/sysdeps/aarch64/dl-mte.c
> 
>>
>> ...
>>
>> +
>> +void
>> +_dl_mte_stack_check (struct link_map *l,
>> +		     const char *program __attribute__((unused)))
>> +{
> 
> Shouldn't there be a check for
> 
>   GLRO(dl_hwcap2) & HWCAP2_MTE
> 
> somewhere (not necessarily here)?
> 
>> +  ElfW (Dyn) *d;
>> +  bool mte_enabled = false;
>> +  bool mte_mode_selected = false;
>> +
>> +  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;
> 
> This and...
> 
>> +	  mte_enabled = true;
>> +	}
>> +      else if (d->d_tag == DT_AARCH64_MEMTAG_MODE)
>> +	{
>> +	  GLRO (dl_aarch64_cpu_features).mte_state
>> +	    &= ~AARCH64_CPU_FEATURE_MTE_STATE_MODE_MASK;
> 
> this conflicts with use of GLRO (dl_aarch64_cpu_features).mte_state for
> heap memory tagging in malloc.
> 
> I think we should think about combination of these two features: how
> will they co-exist and interact.
> 
>> +	  if (d->d_un.d_val == 1)
>> +	    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_SYNC;
>> +	  mte_mode_selected = true;
>> +	}
>> +    }
>> +
> 
>>
>> ...
>>
>> 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..c93ca6a670
>> --- /dev/null
>> +++ b/sysdeps/unix/sysv/linux/aarch64/dl-mte-stack.c
> 
>>
>> ...
>>
>> +#define MTE_ALLOWED_TAGS (0xfffe << PR_MTE_TAG_SHIFT)
>> +
>> +int
>> +_dl_mte_mode (void)
>> +{
>> +  int err = 0;
>> +
>> +  if (GLRO (dl_aarch64_cpu_features).mte_state
>> +      & AARCH64_CPU_FEATURE_MTE_STATE_MODE_SYNC)
>> +    err = __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)
>> +    err = __prctl (PR_SET_TAGGED_ADDR_CTRL,
>> +		   (PR_TAGGED_ADDR_ENABLE | PR_MTE_TCF_ASYNC
>> +		    | MTE_ALLOWED_TAGS),
>> +		   0, 0, 0);
>> +
>> +  return (err != 0
>> +	  && (GLRO (dl_aarch64_cpu_features).mte_state
>> +	      & AARCH64_CPU_FEATURE_MTE_STATE_MODE_MASK) != 0);
> 
> Return value is currently unused. What's the model for a failure
> returned from the prctl syscall?
> 
>> +}
>> -- 
>> 2.53.0
>>
> 
> Kind regards,
> Yury



More information about the Libc-alpha mailing list