Message ID | 20240627035215.1527279-3-dev.jain@arm.com (mailing list archive) |
---|---|
State | New |
Headers | show |
Series | Add test to distinguish between thread's signal mask and ucontext_t | expand |
On Thu, Jun 27, 2024 at 09:22:15AM +0530, Dev Jain wrote: > This test asserts the relation between blocked signal, delivered signal, > and ucontext. The ucontext is mangled with, by adding a signal mask to > it; on return from the handler, the thread must block the corresponding > signal. Reviewed-by: Mark Brown <broonie@kernel.org>
I see nothing wrong, but perhaps this test can be simplified? Feel free to ignore. Say, On 06/27, Dev Jain wrote: > > +void handler_usr(int signo, siginfo_t *info, void *uc) > +{ > + int ret; > + > + /* > + * Break out of infinite recursion caused by raise(SIGUSR1) invoked > + * from inside the handler > + */ > + ++cnt; > + if (cnt > 1) > + return; > + > + ksft_print_msg("In handler_usr\n"); > + > + /* SEGV blocked during handler execution, delivered on return */ > + if (raise(SIGSEGV)) > + ksft_exit_fail_perror("raise"); > + > + ksft_print_msg("SEGV bypassed successfully\n"); You could simply do sigprocmask(SIG_SETMASK, NULL, &oldset) and check if SIGSEGV is blocked in oldset. SIG_SETMASK has no effect if newset == NULL. Likewise, > + /* > + * Mangle ucontext; this will be copied back into ¤t->blocked > + * on return from the handler. > + */ > + if (sigaddset(&((ucontext_t *)uc)->uc_sigmask, SIGUSR2)) > + ksft_exit_fail_perror("sigaddset"); > +} The caller (main) can do the same rather than raise(SIGUSR2). But again, I won't insist. Oleg.
On 6/30/24 20:48, Oleg Nesterov wrote: > I see nothing wrong, but perhaps this test can be simplified? > Feel free to ignore. > > Say, > > On 06/27, Dev Jain wrote: >> +void handler_usr(int signo, siginfo_t *info, void *uc) >> +{ >> + int ret; >> + >> + /* >> + * Break out of infinite recursion caused by raise(SIGUSR1) invoked >> + * from inside the handler >> + */ >> + ++cnt; >> + if (cnt > 1) >> + return; >> + >> + ksft_print_msg("In handler_usr\n"); >> + >> + /* SEGV blocked during handler execution, delivered on return */ >> + if (raise(SIGSEGV)) >> + ksft_exit_fail_perror("raise"); >> + >> + ksft_print_msg("SEGV bypassed successfully\n"); > You could simply do sigprocmask(SIG_SETMASK, NULL, &oldset) and check if > SIGSEGV is blocked in oldset. SIG_SETMASK has no effect if newset == NULL. > IMHO, isn't raising the signal, and the process not terminating, a stricter test? I have already included your described approach in the last testcase; so, the test includes both ways: raising the signal -> process not terminating, and checking blockage with sigprocmask().
On 7/15/24 05:49, Dev Jain wrote: > > On 6/30/24 20:48, Oleg Nesterov wrote: >> I see nothing wrong, but perhaps this test can be simplified? >> Feel free to ignore. >> >> Say, >> >> On 06/27, Dev Jain wrote: >>> +void handler_usr(int signo, siginfo_t *info, void *uc) >>> +{ >>> + int ret; >>> + >>> + /* >>> + * Break out of infinite recursion caused by raise(SIGUSR1) invoked >>> + * from inside the handler >>> + */ >>> + ++cnt; >>> + if (cnt > 1) >>> + return; >>> + >>> + ksft_print_msg("In handler_usr\n"); This message isn't very useful. Why do you need this message? >>> + >>> + /* SEGV blocked during handler execution, delivered on return */ >>> + if (raise(SIGSEGV)) >>> + ksft_exit_fail_perror("raise");>>> + >>> + ksft_print_msg("SEGV bypassed successfully\n"); >> You could simply do sigprocmask(SIG_SETMASK, NULL, &oldset) and check if >> SIGSEGV is blocked in oldset. SIG_SETMASK has no effect if newset == NULL. >> > > IMHO, isn't raising the signal, and the process not terminating, a stricter test? I have already included your described approach in > the last testcase; so, the test includes both ways: raising the > signal -> process not terminating, and checking blockage with sigprocmask(). thanks, -- Shuah
On 7/22/24 20:28, Shuah Khan wrote: > On 7/15/24 05:49, Dev Jain wrote: >> >> On 6/30/24 20:48, Oleg Nesterov wrote: >>> I see nothing wrong, but perhaps this test can be simplified? >>> Feel free to ignore. >>> >>> Say, >>> >>> On 06/27, Dev Jain wrote: >>>> +void handler_usr(int signo, siginfo_t *info, void *uc) >>>> +{ >>>> + int ret; >>>> + >>>> + /* >>>> + * Break out of infinite recursion caused by raise(SIGUSR1) >>>> invoked >>>> + * from inside the handler >>>> + */ >>>> + ++cnt; >>>> + if (cnt > 1) >>>> + return; >>>> + >>>> + ksft_print_msg("In handler_usr\n"); > > This message isn't very useful. Why do you need this message? There isn't any specific use; I am just showing the progress of the test. If you think this is just waste output.... > >>>> + >>>> + /* SEGV blocked during handler execution, delivered on return */ >>>> + if (raise(SIGSEGV)) >>>> + ksft_exit_fail_perror("raise");>>> + >>>> + ksft_print_msg("SEGV bypassed successfully\n"); >>> You could simply do sigprocmask(SIG_SETMASK, NULL, &oldset) and >>> check if >>> SIGSEGV is blocked in oldset. SIG_SETMASK has no effect if newset == >>> NULL. >>> >> >> IMHO, isn't raising the signal, and the process not terminating, a >> stricter test? I have already included your described approach in >> the last testcase; so, the test includes both ways: raising the >> signal -> process not terminating, and checking blockage with >> sigprocmask(). > > thanks, > -- Shuah >
On 7/22/24 22:30, Dev Jain wrote: > > On 7/22/24 20:28, Shuah Khan wrote: >> On 7/15/24 05:49, Dev Jain wrote: >>> >>> On 6/30/24 20:48, Oleg Nesterov wrote: >>>> I see nothing wrong, but perhaps this test can be simplified? >>>> Feel free to ignore. >>>> >>>> Say, >>>> >>>> On 06/27, Dev Jain wrote: >>>>> +void handler_usr(int signo, siginfo_t *info, void *uc) >>>>> +{ >>>>> + int ret; >>>>> + >>>>> + /* >>>>> + * Break out of infinite recursion caused by raise(SIGUSR1) invoked >>>>> + * from inside the handler >>>>> + */ >>>>> + ++cnt; >>>>> + if (cnt > 1) >>>>> + return; >>>>> + >>>>> + ksft_print_msg("In handler_usr\n"); >> >> This message isn't very useful. Why do you need this message? > > > There isn't any specific use; I am just showing the progress > > of the test. If you think this is just waste output.... Showing progress will not be useful unless it is also giving useful information to the user. It doesn't look like it in this case. You can drop this message. > >> >>>>> + >>>>> + /* SEGV blocked during handler execution, delivered on return */ >>>>> + if (raise(SIGSEGV)) >>>>> + ksft_exit_fail_perror("raise");>>> + >>>>> + ksft_print_msg("SEGV bypassed successfully\n"); >>>> You could simply do sigprocmask(SIG_SETMASK, NULL, &oldset) and check if >>>> SIGSEGV is blocked in oldset. SIG_SETMASK has no effect if newset == NULL. >>>> >>> >>> IMHO, isn't raising the signal, and the process not terminating, a stricter test? I have already included your described approach in >>> the last testcase; so, the test includes both ways: raising the >>> signal -> process not terminating, and checking blockage with sigprocmask(). >> thanks, -- Shuah
diff --git a/tools/testing/selftests/signal/.gitignore b/tools/testing/selftests/signal/.gitignore index 98a7bbc4f325..397fef11c89f 100644 --- a/tools/testing/selftests/signal/.gitignore +++ b/tools/testing/selftests/signal/.gitignore @@ -1,2 +1,3 @@ # SPDX-License-Identifier: GPL-2.0-only +mangle_uc_sigmask sigaltstack diff --git a/tools/testing/selftests/signal/Makefile b/tools/testing/selftests/signal/Makefile index dd6be992fd81..735387a53114 100644 --- a/tools/testing/selftests/signal/Makefile +++ b/tools/testing/selftests/signal/Makefile @@ -1,6 +1,7 @@ # SPDX-License-Identifier: GPL-2.0-only CFLAGS = -Wall -TEST_GEN_PROGS = sigaltstack +TEST_GEN_PROGS = mangle_uc_sigmask +TEST_GEN_PROGS += sigaltstack include ../lib.mk diff --git a/tools/testing/selftests/signal/mangle_uc_sigmask.c b/tools/testing/selftests/signal/mangle_uc_sigmask.c new file mode 100644 index 000000000000..d32e2e05d363 --- /dev/null +++ b/tools/testing/selftests/signal/mangle_uc_sigmask.c @@ -0,0 +1,186 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2024 ARM Ltd. + * + * Author: Dev Jain <dev.jain@arm.com> + * + * Test describing a clear distinction between signal states - delivered and + * blocked, and their relation with ucontext. + * + * A process can request blocking of a signal by masking it into its set of + * blocked signals; such a signal, when sent to the process by the kernel, + * will get blocked by the process and it may later unblock it and take an + * action. At that point, the signal will be delivered. + * + * We test the following functionalities of the kernel: + * + * ucontext_t describes the interrupted context of the thread; this implies + * that, in case of registering a handler and catching the corresponding + * signal, that state is before what was jumping into the handler. + * + * The thread's mask of blocked signals can be permanently changed, i.e, not + * just during the execution of the handler, by mangling with uc_sigmask + * from inside the handler. + * + * Assume that we block the set of signals, S1, by sigaction(), and say, the + * signal for which the handler was installed, is S2. When S2 is sent to the + * program, it will be considered "delivered", since we will act on the + * signal and jump to the handler. Any instances of S1 or S2 raised, while the + * program is executing inside the handler, will be blocked; they will be + * delivered immediately upon termination of the handler. + * + * For standard signals (also see real-time signals in the man page), multiple + * blocked instances of the same signal are not queued; such a signal will + * be delivered just once. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <signal.h> +#include <ucontext.h> + +#include "../kselftest.h" + +void handler_verify_ucontext(int signo, siginfo_t *info, void *uc) +{ + int ret; + + /* Kernel dumps ucontext with USR2 blocked */ + ret = sigismember(&(((ucontext_t *)uc)->uc_sigmask), SIGUSR2); + ksft_test_result(ret == 1, "USR2 blocked in ucontext\n"); + + /* + * USR2 is blocked; can be delivered neither here, nor after + * exit from handler + */ + if (raise(SIGUSR2)) + ksft_exit_fail_perror("raise"); +} + +void handler_segv(int signo, siginfo_t *info, void *uc) +{ + /* + * Three cases possible: + * 1. Program already terminated due to segmentation fault. + * 2. SEGV was blocked even after returning from handler_usr. + * 3. SEGV was delivered on returning from handler_usr. + * The last option must happen. + */ + ksft_test_result_pass("SEGV delivered\n"); +} + +static int cnt; + +void handler_usr(int signo, siginfo_t *info, void *uc) +{ + int ret; + + /* + * Break out of infinite recursion caused by raise(SIGUSR1) invoked + * from inside the handler + */ + ++cnt; + if (cnt > 1) + return; + + ksft_print_msg("In handler_usr\n"); + + /* SEGV blocked during handler execution, delivered on return */ + if (raise(SIGSEGV)) + ksft_exit_fail_perror("raise"); + + ksft_print_msg("SEGV bypassed successfully\n"); + + /* + * Signal responsible for handler invocation is blocked by default; + * delivered on return, leading to recursion + */ + if (raise(SIGUSR1)) + ksft_exit_fail_perror("raise"); + + ksft_test_result(cnt == 1, + "USR1 is blocked, cannot invoke handler right now\n"); + + /* Raise USR1 again; only one instance must be delivered upon exit */ + if (raise(SIGUSR1)) + ksft_exit_fail_perror("raise"); + + /* SEGV has been blocked in sa_mask, but ucontext is empty */ + ret = sigismember(&(((ucontext_t *)uc)->uc_sigmask), SIGSEGV); + ksft_test_result(ret == 0, "SEGV not blocked in ucontext\n"); + + /* USR1 has been blocked, but ucontext is empty */ + ret = sigismember(&(((ucontext_t *)uc)->uc_sigmask), SIGUSR1); + ksft_test_result(ret == 0, "USR1 not blocked in ucontext\n"); + + /* + * Mangle ucontext; this will be copied back into ¤t->blocked + * on return from the handler. + */ + if (sigaddset(&((ucontext_t *)uc)->uc_sigmask, SIGUSR2)) + ksft_exit_fail_perror("sigaddset"); +} + +int main(int argc, char *argv[]) +{ + struct sigaction act, act2; + sigset_t set, oldset; + + ksft_print_header(); + ksft_set_plan(7); + + act.sa_flags = SA_SIGINFO; + act.sa_sigaction = &handler_usr; + + /* Add SEGV to blocked mask */ + if (sigemptyset(&act.sa_mask) || sigaddset(&act.sa_mask, SIGSEGV) + || (sigismember(&act.sa_mask, SIGSEGV) != 1)) + ksft_exit_fail_msg("Cannot add SEGV to blocked mask\n"); + + if (sigaction(SIGUSR1, &act, NULL)) + ksft_exit_fail_perror("Cannot install handler"); + + act2.sa_flags = SA_SIGINFO; + act2.sa_sigaction = &handler_segv; + + if (sigaction(SIGSEGV, &act2, NULL)) + ksft_exit_fail_perror("Cannot install handler"); + + /* Invoke handler */ + if (raise(SIGUSR1)) + ksft_exit_fail_perror("raise"); + + /* USR1 must not be queued */ + ksft_test_result(cnt == 2, "handler invoked only twice\n"); + + /* Mangled ucontext implies USR2 is blocked for current thread */ + if (raise(SIGUSR2)) + ksft_exit_fail_perror("raise"); + + ksft_print_msg("USR2 bypassed successfully\n"); + + act.sa_sigaction = &handler_verify_ucontext; + if (sigaction(SIGUSR1, &act, NULL)) + ksft_exit_fail_perror("Cannot install handler"); + + if (raise(SIGUSR1)) + ksft_exit_fail_perror("raise"); + + /* + * Raising USR2 in handler_verify_ucontext is redundant since it + * is blocked + */ + ksft_print_msg("USR2 still blocked on return from handler\n"); + + /* Confirm USR2 blockage by sigprocmask() too */ + if (sigemptyset(&set)) + ksft_exit_fail_perror("sigemptyset"); + + if (sigprocmask(SIG_BLOCK, &set, &oldset)) + ksft_exit_fail_perror("sigprocmask"); + + ksft_test_result(sigismember(&oldset, SIGUSR2) == 1, + "USR2 present in ¤t->blocked\n"); + + ksft_finished(); +}
This test asserts the relation between blocked signal, delivered signal, and ucontext. The ucontext is mangled with, by adding a signal mask to it; on return from the handler, the thread must block the corresponding signal. In the test description, I have also described signal delivery and blockage, for ease of understanding what the test does. Signed-off-by: Dev Jain <dev.jain@arm.com> --- tools/testing/selftests/signal/.gitignore | 1 + tools/testing/selftests/signal/Makefile | 3 +- .../selftests/signal/mangle_uc_sigmask.c | 186 ++++++++++++++++++ 3 files changed, 189 insertions(+), 1 deletion(-) create mode 100644 tools/testing/selftests/signal/mangle_uc_sigmask.c