aarch64: Memory tagging modes proposal
Adhemerval Zanella Netto
adhemerval.zanella@linaro.org
Tue Aug 25 13:07:31 GMT 2026
On 21/08/26 06:43, Yury Khrustalev wrote:
> On Thu, Aug 20, 2026 at 10:52:10AM -0300, Adhemerval Zanella Netto wrote:
>>
>> On 18/08/26 10:52, Yury Khrustalev wrote:
>>> On Tue, Aug 18, 2026 at 10:22:34AM -0300, Adhemerval Zanella Netto wrote:
>>>>
>>>>>>> ...
>>>>>>>
>>>>>>> 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')?
>>
>> My understanding from 1. ("Respects binary marking when it's present.") is
>> we should *always* use the DT_AARCH64_MEMTAG_MODE specified and not allow
>> a downgrade through tunables. Iff the marking is not available then the
>> tunable would be free to select a mode different than default.
>
> OK, I guess this will simplify configuration. I just thought it might
> be inconvenient for the user: if, for example, they have a program with
> in-built DT_AARCH64_MEMTAG_MODE=1 (async) and they see MTE-related error,
> they might want to re-run the program with sync to see where exactly it
> happens.
My view is async to asymm, and asymm to sync should be valid transitions, but
not the other way around. It complicates the mode semantic, but I think it
should be doable to implement (it is just tree states).
>
> Anyway, I think we can just follow the rule that binary marking
> overrides everything.
>
>> I think we should not allow a downgrade from 'sync' to 'async', regardless
>> of system-wide tunable, because the binary marking is a hint that MTE is
>> being used a hardening and not as debug feature.
>
> I don't agree that binary marking is a hint for hardening but it's not
> important for this discussion.
>
>>> ...
>>>
>>>> , 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.
>>
>> I tend to agree, I just raised the options to add an option to enable MTE (even
>> if the binary and direct dependencies does not have any marking) to allows
>> dlopen DSO with the marking. This works in theory (and android allows it), albeit
>> I am not sure how common it would be for Linux/GNU (maybe adding an extra hardening
>> for plugins or NSS modules).
>
> I see your point, but the ABI spec says that binary marking for the
> "scope" of memtag protection only applies to the main binary. So, even
> if we have a tunable to request stack tagging, I think it should be an
> error if this is requested but the main binary is not marked with the
> DT_AARCH64_MEMTAG_STACK flag.
>
>>>
>>> ...
>>>
>>>>> 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 don't recall that spec prohibits marking on DSO,
>
> See section 6:
>
> The presence of the DT_AARCH64_MEMTAG_STACK dynamic array entry indicates that
> stack allocations should be protected with memory tagging. <...> This entry is
> only valid on the main executable.
My understanding is does not prevent the *existence* of the dynamic tag
in the DSO; but rather that trying to enable stack tacking through the
DSO marking should be ignored.
>
>> and doing would restrict a
>> lot PIE and DSO with _entry calls (like libc.so itself).
>
> You don't need to convince me that the memtag ABI spec does not work
> with GNU/Linux, but I've tried changing it and got very strong
> resistance, so we have what we have...
>
>>
>> In fact, this neither what gcc nor clang implements now:
>
> Well, there is spec and there is an implementation that partially
> follows the spec :)
The problem is both GNU and clang ecosystem followed the idea a laid out
above, since both allows the memtag dynamic tag in the DSO. We can just ignore
it on glibc, but I think this will be counter-productive and lead to hard to
debug issues (for instance if someone adds memtag instrumentation on a binary
dependency without enabling on the binary itself).
>
>> ...
>>
>> We need DSO with proper marking to avoid the potential hard-to-debug issues during
>> process loading and dlopen operations.
>
> We do, but we first need a new spec. We are not going to implement an
> unwritten spec here please.
I don't think so, at least not for this specific support. For each support we can
follow the rules:
1. Only enable it if the PDE/PIE has the marking.
1.1. Abort if the binary requests an unsupported feature (like globals)
2. If the binary does not have any marking, check if any direct dependency requires
any MTE support. If so, abort startup.
2.1. Also abort marking with an inconsistent state (invalid or orphan
values).
2.2. Also abort if any dependency requires an unsupported features
(like globals).
3. If the bnary does not enable the MTE stack at startup, any subsequent
attempts to dlopen a DSO that requires MTE stack support will be rejected
to prevent unsafe memory access.
The DSO marking is already support by current toolchain, so it does not
make sense to *not* implement it. I have update my POC with these ideas [1]
(it does not add any tunable for now).
[1] https://sourceware.org/git/?p=glibc.git;a=shortlog;h=refs/heads/azanella/aarch64-memtag
>
>>>> 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?
>>
>> Unfortunately there is none.
>
> When there is one, we can always extend Glibc. I thought this was the
> common convention in Glibc: use new constants and flags only after they
> appear in the kernel.
>
>> The original kernel patch did added one, but it
>> was later revamp during review.
>>
>> I think 'sync+async' makes more sense than 'asymm'
> > I'm not sure what you mean by 'sync+async' but it is not the same as
> 'asymm'.
>
For the kernel standpoint it is, but I think we can use 'auto' here.
>> because 'asymm' is enabled
>> iff kernel/chip support (armv8.7), otherwise kernel fallbacks to either sync
>> or async. But I think we can discuss this later.
>
> Yes, let's discuss this later.
>
>>>
>>> ...
>>>
>>>> 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.
>>
>> It is just that it seems redundant, not really 'wrong'.
>
> OK, I can think of how to avoid having 'none', I just prefer to follow
> the existing convention.
The 'none' is really confusing, under rules 3–4 the value means two
different things depending on the binary:
* marked binary -> "don't override, use DT_AARCH64_MEMTAG_MODE (it must
mean this, or rule 2 is violated);
* unmarked binary -> no tagging at all — it's the enable switch, since
scope for unmarked binaries comes solely from the tunable.
With Rule 2, it highly likely someone will set 'mtemode=none' for marked
binaries that's crashing expecting to shut MTE off.
The name also collides with the kernel ABI, where PR_MTE_TCF_NONE exists
and means "tagging on, checking off", which is not what your none does.
The deeper problem is that the tunable answers two orthogonal questions
with one value:
* which fault mode?
* is heap tagging enabled for unmarked binaries?
Which means neither can be expressed independently. An admin who wants heap
tagging on unmarked binaries but has no opinion on mode to select, it will
always need to use the kernel default.
>
>>>
>>> ...
>>>
>>> Because 'auto' is one of the modes that kernel supports. Also, see my
>>> comment about not wanting to have expressions in the tunable values.
>>
>> Right, but 'auto' is a property of the 'kernel' not really glibc. And I think
>> it would be better to avoid query the kernel in this case, since kernel allows
>> a per-cpu mode.
>
> What do you mean by "query the kernel"? We don't. We just need to
> support a way to pass all the flags that the prctl syscall supports and
>
> PR_MTE_TCF_SYNC | PR_MTE_TCF_ASYNC
>
> is one of them and it means "mode preferred by the system" which I
> described as 'auto' for brevity.
>
>> So 'auto' is already covered by the marking presence
>
> I prefer that selection of the mode does not depend on marking. I don't
> think that marking is going to be used a lot and we certainly need to
> support configuration for unmarked binaries as well.
>
> Also, there is no 'auto' in the spec (that is the only place where
> marking is described). I invented that lack of mode marking means 'auto'
> just for completeness.
>
>> (which glibc will use it
>> anyway),
>
> Correction: Glibc *may* use it, but it must also support unmarked binaries.
>
>>>>> ...
>>>>>
>>>>> '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.
>>
>> But, what is difference between let the process use kernel default (transparent
>> to the caller, no tunable being used), or using the support 'sync' or 'async'?
>
> sync means PR_MTE_TCF_SYNC
> async means PR_MTE_TCF_ASYNC
> auto means PR_MTE_TCF_SYNC | PR_MTE_TCF_ASYNC
>
> all of the above is described in the kernel UAPI docs:
>
> https://www.kernel.org/doc/html/next/arm64/memory-tagging-extension.html
>
> and all I want is to support every such configuration: if they exist,
> they are useful for someone, and I don't want to prevent them from using
> it even if I don't know why they need it.
So what do you mean for 'auto' is a way to enable 'asymm', since
'PR_MTE_TCF_SYNC | PR_MTE_TCF_ASYNC' only really makes sense in this scenario
(otherwise just use the kernel default).
I think it would make sense, although the 'auto' name might still cause
confusions. I think 'preferred' makes more sense here.
>
>> For instance, I have a binary with stack marking but no mode.
>
> So, given the way this is implemented in compilers, I think it will be
> very difficult to achieve no mode marking in the binary.
But it still possible and we will need to handle it on glibc, either by
issuing an error or assuming a default.
>
>> I have:
>>
>> 1. No tunable being used - kernel default, which is set by administrator on
>> per-cpu mode.
>>
>> 2. system-wide tunable - 'async' or 'sync' and no override.
>>
>> 3. No system-wide - tunable will be used to select *an specific* mode (sync
>> or async).
>>
>> I still can't see what 'auto' allows different to what other combinations already
>> provide.
>
> Like I said, Glibc should not add extra layer of interpretation of the
> parameters. When you do the prctl syscall, you have 4 combination of the
> PR_MTE_TCF_x flags, and all four need to be supported because they are
> all valid.
>
>> It would make sense to enable MTE on non-marked binaries, to instruct glibc to
>> enable MTE. But my understanding is your proposal does not want to support this
>> scenario.
>
> You've misunderstood. This scenario is actually the most important for
> me. I only intend to support marking because they are mentioned in the
> spec and we need to provide consistent use of them to the users.
>
>>>
>>> ...
>>>
>>> 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.
>>
>> Right, so there will be no way an user can enable memory tagging on non-marked
>> binaries?
>
> I'm not sure where you got this impression from because it is the *opposite*
> of what I was trying to say.
>
> Again:
>
> 1) Marking is present => scope is determined by the marking (tunables
> have no effect on the scope, and perhaps not even on the mode like
> you suggest).
>
> 2) Marking is not present =>
> 2.1) mode is 'none' => no memtag protection (this is default, you
> need to opt-in to use memtag protection)
> 2.2) mode is anything by 'none' => enable 'standard' protection
> (which currently means 'heap')
>
> This is exhaustive, a complete set of variants.
>From 4., but now I see that it should only enables heap tagging. I think
it should work, but a tunable that enables heap tagging on a machine without
MTE must degrade silently.
And I think we need to implement a version tag similar to GLIBC_ABI_DT_RELR
to avoid compatibility issues when running marked binaries with HEAP/STACK
on old glibc (I recall I already raised this very issue in the previous
mte discussion).
More information about the Libc-alpha
mailing list