[PATCH v2 3/3] aarch64: GCS: add clone3 tests for shadow stack
Adhemerval Zanella Netto
adhemerval.zanella@linaro.org
Tue Sep 9 14:31:53 GMT 2025
On 08/09/25 12:33, Yury Khrustalev wrote:
> * Adhemerval
>
> On Tue, Aug 26, 2025 at 04:46:01PM -0300, Adhemerval Zanella Netto wrote:
>>
>> On 07/07/25 09:47, Yury Khrustalev wrote:
>>> If GCS is available check that new thread is created with a
>>> shadow stack allocated by Glibc.
>>>
>>> Check that shadow stack is de-allocated if a new thread hasn't
>>> started or has been cancelled.
>>>
>>> ...
>>>
>>> diff --git a/sysdeps/unix/sysv/linux/aarch64/tst-gcs-clone3-cancel.c b/sysdeps/unix/sysv/linux/aarch64/tst-gcs-clone3-cancel.c
>>> new file mode 100644
>>> index 0000000000..b2a0a66abb
>>> --- /dev/null
>>> +++ b/sysdeps/unix/sysv/linux/aarch64/tst-gcs-clone3-cancel.c
>>>
>>> ...
>>>
>>> +static void
>>> +test_cancel (void)
>>> +{
>>> + pthread_t th;
>>> + if (pthread_create (&th, NULL, forever, NULL) != 0)
>>> + {
>>> + perror ("pthread_create");
>>> + TEST_VERIFY (false);
>>> + return;
>>> + }
>>
>> Maybe use xpthread_create.
>
> Will fix in the next version of the patch (along with xpthread_cancel
> and xpthread_join pointed elsewhere).
>
>>> + printf ("thread created\n");
>>> + struct pthread *pd = (struct pthread *)th;
>>> + void *token = pd->shadow_stack_token;
>>
>> I am not sure if accessing an internal member field is the best way
>> here. I think maybe it would be better to work by parsing /proc/self/smaps
>> and check for only one mapping with 'ss' in VmFlags (the main thread).
>
> Parsing /proc/self/smaps requires much more code, I'll add it in the
> next version of the patch. After the thread started we'll have 2
> mappings with with the ss flag: parent and child. After cancellation we
> expect 1 mapping (the parent one).
It depends if the kernel supports MADV_GUARD_INSTALL and if the pthread
requires a guard page. I think to simplify you can disable the guard page
to always expect one mapping.
>
> If generic enough, we probably should put it somewhere in the `support`
> folder?
Yes, the support is the place to such routines.
>
>>>
>>> ...
>>>
>>> +#include <support/test-driver.c>
>>> diff --git a/sysdeps/unix/sysv/linux/aarch64/tst-gcs-clone3-helper.h b/sysdeps/unix/sysv/linux/aarch64/tst-gcs-clone3-helper.h
>>> new file mode 100644
>>> index 0000000000..4ca36aa5b3
>>> --- /dev/null
>>> +++ b/sysdeps/unix/sysv/linux/aarch64/tst-gcs-clone3-helper.h
>>>
>>> ...
>>>
>>> +#ifndef TST_GCS_CLONE3_HELPER_H
>>> +#define TST_GCS_CLONE3_HELPER_H
>>> +
>>> +#include <setjmp.h>
>>> +
>>> +jmp_buf jmp;
>>> +
>>> +static void
>>> +handler (int signum)
>>> +{
>>> + TEST_VERIFY (signum == SIGSEGV);
>>> + write (STDOUT_FILENO, "in signal handler\n", 18);
>>
>> I am seeing:
>>
>> ../sysdeps/unix/sysv/linux/aarch64/tst-gcs-clone3.c: In function ‘handler’:
>> ../sysdeps/unix/sysv/linux/aarch64/tst-gcs-clone3.c:74:3: error: ignoring return value of ‘write’ declared with attribute ‘warn_unused_result’ [-Werror=unused-result]
>> 74 | write (STDOUT_FILENO, "in signal handler\n", 18);
>>
>> The TEST_VERIFY will eventually call printf, so I suggest to use something
>> similar I did on nptl/tst-guard1.c to check if the page is writable.
>
> Thanks for the configure flags you provided in another thread. I'll fix
> this by checking the result return from the write() function.
>
>>>
>>> ...
>>>
>>> +#endif // TST_GCS_CLONE3_HELPER_H
>>> diff --git a/sysdeps/unix/sysv/linux/aarch64/tst-gcs-clone3-nomem.c b/sysdeps/unix/sysv/linux/aarch64/tst-gcs-clone3-nomem.c
>>> new file mode 100644
>>> index 0000000000..07d381ed29
>>> --- /dev/null
>>> +++ b/sysdeps/unix/sysv/linux/aarch64/tst-gcs-clone3-nomem.c
>>>
>>> ...
>>>
>>> + if (pthread_create (&th, NULL, fun, NULL) != 0)
>>> + {
>>> + perror ("expected: pthread_create");
>>> + setrlimit (RLIMIT_AS, &prev);
>>> + struct pthread *pd = (struct pthread *)th;
>>> + void *token = pd->shadow_stack_token;
>>> + pthread_join (th, NULL);
>>
>> It is UB to call pthread_join on an invalid handler, and I do not think
>> we should add a test that rely on an specific implementation detail
>> (where pthread_create might return a ligering value in the handler).
>
> You are correct, (x)pthread_join() should be outside of this if branch
> (for the case when thread has in fact started). Will fix in the new
> version of the patch.
>
>>>
>>> ...
>>>
>>> --- /dev/null
>>> +++ b/sysdeps/unix/sysv/linux/aarch64/tst-gcs-clone3.c
>>>
>>> ...
>>>
>>> + if (base == 0)
>>> + {
>>> + puts ("shadow stack was not allocated by Glibc");
>>> + exit (EXIT_UNSUPPORTED);
>>> + }
>>> + printf ("shadow stack top: %016lx\n", top);
>>> + printf ("shadow stack token: %016lx\n", token);
>>> +
>>> + /* This macro guard is for the sake of compilers that don't
>>> + yet have the __builtin_aarch64_gcspr() builtin so that
>>> + the test could compiler regardless. */
>>> +#if __ARM_FEATURE_GCS_DEFAULT
>>
>> This fails for compilers that do not define __ARM_FEATURE_GCS_DEFAULT. I think
>> you meant 'ifdef' here.
>
> Correct. Will fix this too.
>
> Thanks,
> Yury
>
More information about the Libc-alpha
mailing list