From 8f4632deb3545b2949cec5454afc3cb21a0024ea Mon Sep 17 00:00:00 2001 From: Mathieu Desnoyers Date: Mon, 6 Jul 2020 10:21:35 +0200 Subject: [PATCH] Linux: rseq registration tests These tests validate that rseq is registered from various execution contexts (main thread, destructor, other threads, other threads created from destructor, forked process (without exec), pthread_atfork handlers, pthread setspecific destructors, signal handlers, atexit handlers). tst-rseq.c only links against libc.so, testing registration of rseq in a non-multithreaded environment. tst-rseq-nptl.c also links against libpthread.so, testing registration of rseq in a multithreaded environment. See the Linux kernel selftests for extensive rseq stress-tests. --- sysdeps/unix/sysv/linux/Makefile | 10 +- sysdeps/unix/sysv/linux/tst-rseq-nptl.c | 256 ++++++++++++++++++++++++ sysdeps/unix/sysv/linux/tst-rseq.c | 64 ++++++ sysdeps/unix/sysv/linux/tst-rseq.h | 59 ++++++ 4 files changed, 388 insertions(+), 1 deletion(-) create mode 100644 sysdeps/unix/sysv/linux/tst-rseq-nptl.c create mode 100644 sysdeps/unix/sysv/linux/tst-rseq.c create mode 100644 sysdeps/unix/sysv/linux/tst-rseq.h diff --git a/sysdeps/unix/sysv/linux/Makefile b/sysdeps/unix/sysv/linux/Makefile index e855db2cb9..2ee83e88d7 100644 --- a/sysdeps/unix/sysv/linux/Makefile +++ b/sysdeps/unix/sysv/linux/Makefile @@ -100,7 +100,11 @@ tests += tst-clone tst-clone2 tst-clone3 tst-fanotify tst-personality \ test-errno-linux tst-memfd_create tst-mlock2 tst-pkey \ tst-rlimit-infinity tst-ofdlocks tst-gettid tst-gettid-kill \ tst-tgkill -tests-internal += tst-ofdlocks-compat tst-sigcontext-get_pc + +# tst-rseq is an internal test because it requires a definition of __NR_rseq +# from the internal system call list. +tests-internal += tst-ofdlocks-compat tst-sigcontext-get_pc \ + tst-rseq CFLAGS-tst-sigcontext-get_pc.c = -fasynchronous-unwind-tables @@ -301,4 +305,8 @@ endif ifeq ($(subdir),nptl) tests += tst-align-clone tst-getpid1 + +# tst-rseq-nptl is an internal test because it requires a definition of +# __NR_rseq from the internal system call list. +tests-internal += tst-rseq-nptl endif diff --git a/sysdeps/unix/sysv/linux/tst-rseq-nptl.c b/sysdeps/unix/sysv/linux/tst-rseq-nptl.c new file mode 100644 index 0000000000..5e788dcfa9 --- /dev/null +++ b/sysdeps/unix/sysv/linux/tst-rseq-nptl.c @@ -0,0 +1,256 @@ +/* Restartable Sequences NPTL test. + Copyright (C) 2020 Free Software Foundation, Inc. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +/* These tests validate that rseq is registered from various execution + contexts (main thread, destructor, other threads, other threads created + from destructor, forked process (without exec), pthread_atfork handlers, + pthread setspecific destructors, signal handlers, atexit handlers). + + See the Linux kernel selftests for extensive rseq stress-tests. */ + +#include +#include +#include +#include +#include + +#ifdef RSEQ_SIG +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include "tst-rseq.h" + +static pthread_key_t rseq_test_key; + +static void +atfork_prepare (void) +{ + if (!rseq_thread_registered ()) + { + printf ("error: rseq not registered in pthread atfork prepare\n"); + support_record_failure (); + } +} + +static void +atfork_parent (void) +{ + if (!rseq_thread_registered ()) + { + printf ("error: rseq not registered in pthread atfork parent\n"); + support_record_failure (); + } +} + +static void +atfork_child (void) +{ + if (!rseq_thread_registered ()) + { + printf ("error: rseq not registered in pthread atfork child\n"); + support_record_failure (); + } +} + +static void +rseq_key_destructor (void *arg) +{ + /* Cannot use deferred failure reporting after main returns. */ + if (!rseq_thread_registered ()) + FAIL_EXIT1 ("rseq not registered in pthread key destructor"); +} + +static void +atexit_handler (void) +{ + /* Cannot use deferred failure reporting after main returns. */ + if (!rseq_thread_registered ()) + FAIL_EXIT1 ("rseq not registered in atexit handler"); +} + +static void +do_rseq_main_test (void) +{ + TEST_COMPARE (atexit (atexit_handler), 0); + rseq_test_key = xpthread_key_create (rseq_key_destructor); + TEST_COMPARE (pthread_atfork (atfork_prepare, atfork_parent, atfork_child), 0); + xraise (SIGUSR1); + TEST_COMPARE (pthread_setspecific (rseq_test_key, (void *) 1l), 0); + TEST_VERIFY_EXIT (rseq_thread_registered ()); +} + +static void +cancel_routine (void *arg) +{ + if (!rseq_thread_registered ()) + { + printf ("error: rseq not registered in cancel routine\n"); + support_record_failure (); + } +} + +static pthread_barrier_t cancel_thread_barrier; +static pthread_cond_t cancel_thread_cond = PTHREAD_COND_INITIALIZER; +static pthread_mutex_t cancel_thread_mutex = PTHREAD_MUTEX_INITIALIZER; + +static void +test_cancel_thread (void) +{ + pthread_cleanup_push (cancel_routine, NULL); + (void) xpthread_barrier_wait (&cancel_thread_barrier); + /* Wait forever until cancellation. */ + xpthread_cond_wait (&cancel_thread_cond, &cancel_thread_mutex); + pthread_cleanup_pop (0); +} + +static void * +thread_function (void * arg) +{ + int i = (int) (intptr_t) arg; + + xraise (SIGUSR1); + if (i == 0) + test_cancel_thread (); + TEST_COMPARE (pthread_setspecific (rseq_test_key, (void *) 1l), 0); + return rseq_thread_registered () ? NULL : (void *) 1l; +} + +static void +sighandler (int sig) +{ + if (!rseq_thread_registered ()) + { + printf ("error: rseq not registered in signal handler\n"); + support_record_failure (); + } +} + +static void +setup_signals (void) +{ + struct sigaction sa; + + sigemptyset (&sa.sa_mask); + sigaddset (&sa.sa_mask, SIGUSR1); + sa.sa_flags = 0; + sa.sa_handler = sighandler; + xsigaction (SIGUSR1, &sa, NULL); +} + +static int +do_rseq_threads_test (int nr_threads) +{ + pthread_t th[nr_threads]; + int i; + int result = 0; + + xpthread_barrier_init (&cancel_thread_barrier, NULL, 2); + + for (i = 0; i < nr_threads; ++i) + th[i] = xpthread_create (NULL, thread_function, + (void *) (intptr_t) i); + + (void) xpthread_barrier_wait (&cancel_thread_barrier); + + xpthread_cancel (th[0]); + + for (i = 0; i < nr_threads; ++i) + { + void *v; + + v = xpthread_join (th[i]); + if (i != 0 && v != NULL) + { + printf ("error: join %d successful, but child failed\n", i); + result = 1; + } + else if (i == 0 && v == NULL) + { + printf ("error: join %d successful, child did not fail as expected\n", i); + result = 1; + } + } + + xpthread_barrier_destroy (&cancel_thread_barrier); + + return result; +} + +static void +subprocess_callback (void *closure) +{ + do_rseq_main_test (); +} + +static void +do_rseq_fork_test (void) +{ + support_isolate_in_subprocess (subprocess_callback, NULL); +} + +static int +do_rseq_test (void) +{ + int t[] = { 1, 2, 6, 5, 4, 3, 50 }; + int i, result = 0; + + if (!rseq_available ()) + FAIL_UNSUPPORTED ("kernel does not support rseq, skipping test"); + setup_signals (); + xraise (SIGUSR1); + do_rseq_main_test (); + for (i = 0; i < array_length (t); i++) + if (do_rseq_threads_test (t[i])) + result = 1; + do_rseq_fork_test (); + return result; +} + +static void __attribute__ ((destructor)) +do_rseq_destructor_test (void) +{ + /* Cannot use deferred failure reporting after main returns. */ + if (do_rseq_test ()) + FAIL_EXIT1 ("rseq not registered within destructor"); + xpthread_key_delete (rseq_test_key); +} + +#else /* RSEQ_SIG */ +static int +do_rseq_test (void) +{ + FAIL_UNSUPPORTED ("glibc does not define RSEQ_SIG, skipping test"); + return 0; +} +#endif /* RSEQ_SIG */ + +static int +do_test (void) +{ + return do_rseq_test (); +} + +#include diff --git a/sysdeps/unix/sysv/linux/tst-rseq.c b/sysdeps/unix/sysv/linux/tst-rseq.c new file mode 100644 index 0000000000..aa902fb26a --- /dev/null +++ b/sysdeps/unix/sysv/linux/tst-rseq.c @@ -0,0 +1,64 @@ +/* Restartable Sequences single-threaded tests. + Copyright (C) 2020 Free Software Foundation, Inc. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +/* These tests validate that rseq is registered from main in an executable + not linked against libpthread. */ + +#include +#include +#include +#include + +#ifdef RSEQ_SIG +# include +# include +# include +# include +# include +# include "tst-rseq.h" + +static void +do_rseq_main_test (void) +{ + TEST_VERIFY_EXIT (rseq_thread_registered ()); +} + +static void +do_rseq_test (void) +{ + if (!rseq_available ()) + { + FAIL_UNSUPPORTED ("kernel does not support rseq, skipping test"); + } + do_rseq_main_test (); +} +#else /* RSEQ_SIG */ +static void +do_rseq_test (void) +{ + FAIL_UNSUPPORTED ("glibc does not define RSEQ_SIG, skipping test"); +} +#endif /* RSEQ_SIG */ + +static int +do_test (void) +{ + do_rseq_test (); + return 0; +} + +#include diff --git a/sysdeps/unix/sysv/linux/tst-rseq.h b/sysdeps/unix/sysv/linux/tst-rseq.h new file mode 100644 index 0000000000..c2cb211f56 --- /dev/null +++ b/sysdeps/unix/sysv/linux/tst-rseq.h @@ -0,0 +1,59 @@ +/* Restartable Sequences tests header. + Copyright (C) 2020 Free Software Foundation, Inc. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#include +#include +#include +#include +#include +#include +#include + +static inline bool +rseq_thread_registered (void) +{ + int32_t v; + + __atomic_load (&__rseq_abi.cpu_id, &v, __ATOMIC_RELAXED); + return v >= 0; +} + +static inline int +sys_rseq (struct rseq *rseq_abi, uint32_t rseq_len, int flags, uint32_t sig) +{ + return syscall (__NR_rseq, rseq_abi, rseq_len, flags, sig); +} + +static inline bool +rseq_available (void) +{ + int rc; + + rc = sys_rseq (NULL, 0, 0, 0); + if (rc != -1) + FAIL_EXIT1 ("Unexpected rseq return value %d", rc); + switch (errno) + { + case ENOSYS: + return false; + case EINVAL: + /* rseq is implemented, but detected an invalid rseq_len parameter. */ + return true; + default: + FAIL_EXIT1 ("Unexpected rseq error %s", strerror (errno)); + } +} -- 2.43.5