Re: aarch64: Memory tagging modes proposal

Yury Khrustalev <[email protected]>
Newsgroups gmane.comp.lib.glibc.alpha
Message-ID <[email protected]>
On Tue, Aug 18, 2026 at 10:22:34AM -0300, Adhemerval Zanella Netto wrote:
> 
> On 18/08/26 07:03, Yury Khrustalev wrote:
> > On Mon, Aug 17, 2026 at 04:14:17PM -0300, Adhemerval Zanella Netto wrote:
> >>
> >> On 11/08/26 10:40, Yury Khrustalev wrote:
> >>> Request for feedback.
> >>>
> >>> This is a proposal for how MTE binary marking from [1] should co-exist with
> >>> the MTE Glibc tunable (not yet implemented). In my opinion, both are necessary
> >>> to cover all practical use cases and provide both flexibility of future
> >>> experiments and ease of configuration for developers and end users. This
> >>> explain how it all should work.
> >>>
> >>
> >> Hi Yury,
> >>
> >> I did a POC to check how it would play with glibc [1], mostly to check how
> >> to simplify the first MTE proposal but also to check if current ideas from
> >> other system would make sense for glibc. 
> >>
> >>> [1]: https://github.com/ARM-software/abi-aa/blob/main/memtagabielf64/memtagabielf64.rst
> >>>
> >>> The set of rules below:
> >>>
> >>> 1) Respects binary marking when it's present.
> >>> 2) Tunable cannot disable tagging, only change MTE mode.
> 
> I just realized that this can be arguably a security hazard, like downgrade a
> DT_AARCH64_MEMTAG_MODE set to 'sync' to 'async'.

I thought we resolved this with system-wide tunables. If switching from
'sync' to 'async' is an issue on some system, this tunable should be
configured as not final in /etc/tunables.conf. In my proposal "downgrading"
is allowed but switching off is not.

If we ignore the tunable when marking is present, the question is what
to do if the user wants to used different mode (e.g. to use 'sync' when
marking is set to 'async')?

> >>> 3) What kind of memory tagging is enabled (i.e. the scope) is solely
> >>>    determined either by the binary marking (when it's present) or by
> >>>    the tunable (when marking is not present).
> >>> 4) You cannot enable stack and globals tagging unless corresponding
> >>>    binary marking is present (it requires support by compiler and static
> >>>    linker), but you can choose if you enable heap tagging for unmarked
> >>>    binaries.
> >>
> >> Afaiu it should be *functional* safe to enable MTE even though the process itself
> >> does not issue any MTE instructions.  It would incur in the performance hit
> >> (kernel-side page cost, non-KSM merging, extra DRAM usage), but it would be a way
> >> to make non-marked binaries to allow dlopen marked DSO.
> > 
> > It would be functional but the functionality that is "enabled" this way
> > would not be in fact enabled, and I don't want this to happen silently.
> 
> It is not clear what your meant by 'not in fact enabled'

If the program doesn't have DT_AARCH64_MEMTAG_STACK marking, it's likely
that it's not properly instrumented for stack tagging. Even if we do
initial setup, stack tagging will not work because there is no
instrumentation.

>, my understanding is
> in theory we can add a tunable that will essentially mimic what the 
> DT_AARCH64_MEMTAG_* does for process startup (prctrl plus PROT_MTE on mmap calls).

This initial setup is always done of tagging needs to be enabled for at
least one of the components (heap, stack, etc). It's not a problem to do
it but there would be no stack tagging if there is no code instrumentation,
and we can't do anything about it on the Glibc side. That is why I think
it would be better to be explicit that we don't enable stack tagging
unless there is corresponding binary marking.

> But I think it would only make sense for dlopen DT_AARCH64_MEMTAG_{STACK,GLOBAL},
> and we can also just avoid this if the process has not MTE enabled at program
> startup.
> 
> > 
> >>> 5) If you want heap tagging for marked binaries, the DT_AARCH64_MEMTAG_HEAP
> >>>    tag must be set explicitly.
> >>
> >> I think different than DT_AARCH64_MEMTAG_STACK, DT_AARCH64_MEMTAG_HEAP can be
> >> enabled independently whether the process has the marking or not. Afaiu it is
> >> property of the malloc implementation instead of the binary (different than
> >> the stack support).
> > 
> > Heap stacking *can* be enabled independently, I don't argue with that.
> > What I am saying is that binary marking, if present, should take
> > precedence. So, if out of components X, Y, and Z we have marking only
> > for Y, we only enabled Y because this is what marking dictates. I think
> > this provides for a clearer interface where users know what to expect.
> 
> Agreed, but the way 5. is written hints that memory tagging for malloc is *only*
> enabled through the markings. But I am also not sure how do you plan to re-enable
> malloc tagging.

Item 5) only applies to marked binaries. It follows from the idea that
marked binaries fully control scope of MTE (i.e. which components enable memtag
protection).

> 
> > 
> >>>
> >>> Binary marking:
> >>>  - DT_AARCH64_MEMTAG_MODE=0|1: selects fault mode for MTE memory tag
> >>>    checking, i.e. MTE mode.
> >>>  - DT_AARCH64_MEMTAG_{HEAP,STACK,GLOBALS} determines the scope of
> >>>    memory tagging.
> >>
> >> I think it would be worth to add that MTE state should be setup at program
> >> *startup* and it should not be changed during process execution (like by
> >> dlopen). 
> > 
> > This is already covered by the MEMTAGABI document. This part that we're
> > discussing here only covers the initial setup.
> 
> Yes, but I think we should make clear that the glibc implementation intend
> to adhere to this. I am raising this because currently the only MTE implementation
> reference Linux developers have (Android) allows dlopen DSO with MTE
> when thread does not initially enable it.

Yeah, we'll make changes to the spec to mention this. But before that we
need to settle on the actual approach.

> > 
> >> I don't think we should replicate bionic's DT_AARCH64_MEMTAG_STACK
> >> support, where it enables MTE support for running threads if a DSO required
> >> it.
> > 
> > I agree. This should be added to the GNU/Linux part of the MEMTAG ABI
> > spec.
> > 
> >> It means that issuing dlopen on marking DSO for a process without marking
> >> should return an error.
> > 
> > DSOs are not supposed to have DT_AARCH64_MEMTAG_{HEAP,STACK,GLOBALS}
> > marking according to the spec, so if they do, it's an error like you
> > suggest. I also agree that even if we decided to load such a DSO, we
> > should not alter the state of the process.
> 
> Without the marking on DSO it would be *really* hard to avoid loading DSO that
> *required* STACK/GLOBAL support if the binary does not enable it.

Well, this is what the spec says. I don't want to spend time trying to
change is as it didn't work out in the past. This is what we have to
live with.

> I think it
> would be counter-productive to do this, since it would most like result
> hard to debug failures (SIGILL or something alike during stack or global
> operations).
> 
> And it is also not current practice to fail early to avoid process loading 
> with undefined behavior.
> 
> > 
> >>> Tunable:
> >>>  - glibc.cpu.mtemode=none|auto|sync|async: selects fault mode for MTE
> >>>    memory tag checking (also see below), default value is 'none'.
> >>
> >> Should we consider asymm mode? This is not defined by the ABI but it supported
> >> by Linux and defined for ARM v8.7.
> > 
> > AFAIK, prctl flags don't have it, but if they do, we surely can support
> > it (now or later).
> 
> It does, although indirectly:
> 
> Documentation/arch/arm64/memory-tagging-extension.rst
> 
> 149 To allow a program to potentially run in the CPU's preferred tag
> 150 checking mode, the user program may set multiple tag check fault mode
> 151 bits in the ``flags`` argument to the ``prctl(PR_SET_TAGGED_ADDR_CTRL,
> 152 flags, 0, 0, 0)`` system call. If both synchronous and asynchronous
> 153 modes are requested then asymmetric mode may also be selected by the
> 154 kernel. If the CPU's preferred tag checking mode is in the task's set
> 155 of provided tag checking modes, that mode will be selected. Otherwise,
> 156 one of the modes in the task's mode will be selected by the kernel
> 157 from the task's mode set using the preference order:
> 158
> 159         1. Asynchronous
> 160         2. Asymmetric
> 161         3. Synchronous
> 162
> 163 Note that there is no way for userspace to request multiple modes and
> 164 also disable asymmetric mode.
> 
> We can map to some like 'glibc.cpu.mtemode=sync+async' to mimic the kernel,
> but it makes tunable parsing a bit more complex and exposes how Linux
> implements it instead of hardware support.

I don't want Turing-complete expressions in tunables :)

Is there a macro for asymmetric in the UAPI headers?

> > 
> >>> If both the tunable and the DT_AARCH64_MEMTAG_MODE tag are explicitly
> >>> set, the tunable takes precedence (allows to override mode that is built
> >>> into the binary) except when tunable is set to 'none' in which case the
> >>> tunable has no effect.
> >>>
> >>> Tunable cannot disable MTE memory tagging when binary marking requires it,
> >>> it can only change the mode.
> >>>
> >>> If none of DT_AARCH64_MEMTAG_{HEAP,STACK,GLOBALS} is present, the tunable,
> >>> when set to a value other than 'none', will enable the "standard" set of
> >>> memory tagging features (currently HEAP).
> >>
> >> I think this a bit confusing, where it mixes the MTE mode (auto, sync, async)
> >> with a MTE support.
> > 
> > I don't want to add another tunable for on / off of MTE given that if
> > the user requests some MTE mode other than "none" they mean that MTE
> > should be enabled. Then the question is: What parts of Glibc should
> > enabled MTE support? My answer to this: in the absence of marking we
> > enable heap support only (but this can be extended in the future, that
> > is why I called it "standard" scope); if marking is present, we enable
> > components that are specified in the marking.
> 
> Yes, but which usercase 'none' really makes sense here?

1) If tunable is not specified, it's 'none' and tagging protection is not
enabled for unmarked binaries. For marked, everything depends on the
marking.

2) If tunable is explicitly set to 'none', same as 1)

> For the rules you
> defined, tunables can not disable tagging (2.), and stack/global are only
> gated through markings (4.). So 'none' here only gates whether enable
> *heap* tagging for a process without marking. It is still confusing and
> conflate two different MTE functionalities. 

It's the same as having 0 for the GCS or BTI tunables. I just follow the
same approach. Not sure why it's confusing.

> > 
> >> Also, it is not clear why we need 'auto' here.

Because 'auto' is one of the modes that kernel supports. Also, see my
comment about not wanting to have expressions in the tunable values.

> > 
> > 'auto' means use mode preferred by CPU as described in:
> > https://www.kernel.org/doc/html/next/arm64/memory-tagging-extension.html
> 
> Yes, but what does it gain for the glibc usercase?

It allows to use one of the supported modes. I believe it might be the
most commonly used in practice if MTE is enabled system-wide.

> If the binary has the
> heap/global/stack marking the mode will either the mode marking of the kernel
> default, so an implicit 'auto'.

As I described, if binary has the mode marking, this mode is used (only
sync and async are supported in this case). I also suggested that we can
also use other supported modes: 'auto' and (in the future) 'asymm' by
using the tunable, and when 'auto' is selected, then prctl tell the
kernel to use its preferred mode.

> If the binary has any marking, does it enable heap with kernel default mode?

If binary has any memtag-related marking, heap tagging is only enabled
iff DT_AARCH64_MEMTAG_HEAP is present, and the mode is selected based on
the rules I've described.

> If so, this is confusing.

I think you've missed some of the points in my original email. All that
you are asking here is already addressed there.

> Maybe it would be better to have a *different* tunable to control the MTE
> support (glibc.cpu.mtesupport or something).

I'd like to avoid extra tunables given that some of the combinations of
the mode tunable and this new tunable will be redundant or make no sense
(e.g. "disable MTE with async mode").

> > 
> > ...
> >
> >>> In the table above, '(marking)' means whatever components are flagged with
> >>> the DT_AARCH64_MEMTAG_{HEAP,STACK,GLOBALS} tags and are supported by the
> >>> current version of Glibc.
> >>
> >> It still not clear what the 'none' tunable mode improves here.
> > 
> > It is the default value of the tunable when it's not set explicitly.
> 
> Yes, but why do we need to expose this internal state to tunable? Which usercase
> setting glibc.cpu.mtemode=none make sense?

Same use case as glibc.cpu.aarch64_{gcs,bti}=0. It's just convenient for
completeness, writing tests etc.

> > 
> >> For 1. it
> >> is ignored anyway,
> > 
> > For 1) it not ignored. It selects the mode that the user wants to use in
> > this specific invocation of their program.
> 
> So for 1. it is gating heap tagging.  It is not clear why there is a need for 'none'
> mode, since it means essentially to not enable heap tagging for a binary without markings.
> It seems to be no-op in this case.

I think I've already addressed why 'none' is useful above. If you think
there is no value, I get it, but I certainly see the value and it
doesn't cost anything to have it, so I don't see why we should keep
arguing about this...

> > 
> >> and for 2. it is unclear which mode will be used
> >> ('mte_tcf_preferred', async).
> > 
> > The mode that user specifies in the tunable will be used. We can support
> > more modes such as asymmetric when it's supported in the prctl interface.
> 
> But for 2. it will either the binary marking mode *or*, in its absence,
> the kernel default.

No.

> So assuming binary has either stack/global/heap:
> 
>  1. If binary has DT_AARCH64_MEMTAG_MODE, what does 'none' provides exactly?

In this case 'none' is not used because we explicitly forbid to disable
memtag protection if marking requires it (for security).

>  2. If binary has not DT_AARCH64_MEMTAG_MODE, what is the difference for 'auto'?

No difference because 'none' would be ignored.

> > 
> >>>
> >>> Status 'on' means that the prctl(PR_TAGGED_ADDR_ENABLE) will be made at the
> >>> start of the process. The mode is included in the flags for this syscall.
> >>>
> >>> The Scope determines which parts of the tagging implementation in Glibc
> >>> are actually activated.
> >>>
> >>> This may appear complicated however this is due to the difference of how
> >>> heap tagging is implemented compared to tagging of stack and globals. In
> >>> the latter case binary needs to be built with necessary instrumentation
> >>> that is not controlled by Glibc.
> >>>
> >>> Any feedback welcome!
> >> [1] https://sourceware.org/git/?p=glibc.git;a=shortlog;h=refs/heads/azanella/aarch64-memtag
> > 
> > Hope this clarifies it!
> > 
> > Thanks,
> > Yury
> >
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.