[PATCH v2 1/1] aarch64: clear ZA state of SME before clone and clone3 syscalls

Richard Sandiford richard.sandiford@arm.com
Thu Jun 12 14:38:34 GMT 2025


Adhemerval Zanella Netto <adhemerval.zanella@linaro.org> writes:
> On 12/06/25 09:53, Richard Sandiford wrote:
>> Adhemerval Zanella Netto <adhemerval.zanella@linaro.org> writes:
>>> On 11/06/25 12:36, Yury Khrustalev wrote:
>>>> Hi everyone,
>>>>
>>>> There are several sub-threads, so I'll reply here to provide some summary.
>>>>
>>>> 1. Changes in the kernel address most common scenarios wrt ZA state of SME.
>>>>
>>>> 2. Since clone() is part of API available to userspace via libc wrapper,
>>>>    it is possible that some corner cases are still not covered, which is why
>>>>    this Glibc patch series is still useful. Ideally, we also want all libc-s
>>>>    to be aligned on this so that userspace programs could expect the same
>>>>    behaviour.
>>>
>>> For thread discussion my understanding was that SME status between v5.19 and
>>> v6.12 is broken due multiple issues (and disabled on stable releases), and
>>> from 6.12 to 6.16 was marked BROKEN.
>>>
>>> And starting from 6.16 there should be no need to no extra care by userland
>>> to clear ZA state of SME before fork, as indicated by kernel commit
>>> cde5c32db55740659fca6d56c09b88800d88fd29:
>>>
>>>     Ideally we'd require that userspace ensures that a task is in the "ZA
>>>     off" state (with PSTATE.ZA==0 and TPIDR2_EL0==NULL) prior to issuing a
>>>     clone syscall, and have the kernel force this state for new threads.
>>>     Unfortunately, contemporary C libraries do not do this, and simply
>>>     forcing this state within the implementation of clone would break
>>>     fork().  
>>>
>>> I do not think it would be worth to handle the old broken kernels (v5.19 to
>>> v6.12) and adding this patch to fix potentially corner cases adds some doubt 
>>> whether the kernel does have SME in an workable condition; nor it is clear 
>>> what exactly are the corner cases is trying to prevent.
>> 
>> I think that's putting the burden of proof in the wrong place.
>> 
>> In principle, the lazy save scheme allows any normal ("private-ZA")
>> function to call __arm_za_disable for any reason.  Calling __arm_za_disable
>> is a conservatively correct thing to do.  The question then is more:
>> can we prove that not calling __arm_za_disable is behaviourally
>> equivalent to calling __arm_za_disable as far as the lazy save
>> scheme is concerned?
>
> It is not conservative if there is no description of what exactly is
> trying to achieve here.  And the question does not really makes sense
> without defining in which usercases libc (or any runtime, because it
> essentially boils down to the kABI and SME interop) should act upon.

But like I say, I think that's putting the burden of proof the wrong
way around.  There doesn't need to be a defined use case where libc
definitely does need to call __arm_za_disable.  It's enough that the
possible uses of clone are hard to enumerate, and that it's difficult
to prove correctness without that enumeration.

> If __arm_za_disable is strictly required, I think it should be proper
> documented in kABI or aarch64 ABI that it should be called in
> clone-like interfaces. 

I think this is too platform-specific to be something that the AArch64
ABI should mention.  And the requirement to call __arm_za_disable isn't
coming from the kernel, or needed to satisfy a contract between the
kernel and userspace.  It's instead needed to satisfy a pure userspace
scheme.

>> From a QoI perspective, functions are discouraged from calling
>> __arm_za_disable if (a) the function could plausibly be used in the
>> middle of an SME operation, (b) it's easy to prove that not calling
>> __arm_za_disable is behaviourally equivalent to calling __arm_za_disable,
>> and (c) the function does not tend to force the kernel to save ZA state.
>> 
>> I think clone fails on at least (a) and (b).  Calling clone in the middle of
>> an SME operation seems significantly outside the normal expected use of SME.
>> And clone is a complex syscall with many options, so it's difficult to
>> prove that not calling __arm_za_disable is behaviourally equivalent to
>> calling __arm_za_disable.
>
> That's why libc provides wrapper over clone() and if users wants
> to mess up with clone flags they can not resort that libc runtime will
> always behave as expected.

I think the fact that it's a general wrapper is also why it's difficult
to prove that not calling __arm_za_disable is correct.

And yeah, I can see that the libc can't guarantee that it will behave
as expected if the program uses clone for something strange.  But at
the same time, there doesn't seem any need to make things harder than
they need to be.

Calling __arm_za_disable is safe if the program is correctly following
the lazy save scheme, and the discussion is moot if the program isn't
correctly following the scheme.  Calling __arm_za_disable ensures that the
new child doesn't start with an incorrect ZA state ("incorrect" according
to the AAPCS64).

>> So IMO, if there's doubt about whether all uses of clone are correct
>> without __arm_za_disable (or glibc's local equivalent), the choice
>> should be to call __arm_za_disable.
>
> So it all boils down on whether if some clone arguments might trigger
> some undefined behavior with SME. The glibc does minimal sanitization
> of clone arguments (just check whether the function and the stack
> are not null), and bionic seems to follow glibc here. 
>
> musl tries to filter out invalid flags that might produce an invalid 
> thread/TLS state, so I wonder if should do the same in this case.

If there's a choice between rejecting things specifically for SME
on the one hand and calling __arm_za_disable on the other, we should
call __arm_za_disable.  Not calling it is effectively an attempted
optimisation.  And given that we seem to be far outside the expected
use case of SME, it doesn't seem like a particularly important optimisation.

Thanks,
Richard


More information about the Libc-alpha mailing list