The existing send_signal tests, including the _remote variants that exercise bpf_send_signal_task() with a caller-supplied target, all gate on if ((bpf_get_current_pid_tgid() >> 32) == pid) so the BPF program only ever runs while current is the test process. The caller-context dimension is therefore untested, which is how bpf_send_signal_task() failing from kernel thread context went unnoticed. Add a program attached to tp_btf/workqueue_execute_start, where current is always a kworker. It cannot filter on current, so it is driven by target_pid alone, and the test asserts the child receives the signal. Setting pid to 0 leaves the pre-existing programs in the skeleton inert. Workqueues run on their own, so nothing has to be triggered from userspace. Without narrowing down the nmi_uaccess_okay() check to task == current, this test fails on x86. Assisted-by: Claude:claude-opus-5 Signed-off-by: Aditya Sharma --- .../selftests/bpf/prog_tests/send_signal.c | 104 ++++++++++++++++++ .../bpf/progs/test_send_signal_kern.c | 26 +++++ 2 files changed, 130 insertions(+) diff --git a/tools/testing/selftests/bpf/prog_tests/send_signal.c b/tools/testing/selftests/bpf/prog_tests/send_signal.c index 7ac4d5a488aa..f9dd23db1ef9 100644 --- a/tools/testing/selftests/bpf/prog_tests/send_signal.c +++ b/tools/testing/selftests/bpf/prog_tests/send_signal.c @@ -261,6 +261,108 @@ static void test_send_signal_nmi(bool signal_thread, bool remote) test_send_signal_common(&attr, signal_thread, remote); } +static void test_send_signal_kworker(void) +{ + struct test_send_signal_kern *skel; + int pipe_c2p[2], pipe_p2c[2]; + struct sigaction sa = {}; + char buf[256]; + int err = -1; + pid_t pid; + + if (!ASSERT_OK(pipe(pipe_c2p), "pipe_c2p")) + return; + + if (!ASSERT_OK(pipe(pipe_p2c), "pipe_p2c")) { + close(pipe_c2p[0]); + close(pipe_c2p[1]); + return; + } + + pid = fork(); + if (!ASSERT_GE(pid, 0, "fork")) { + close(pipe_c2p[0]); + close(pipe_c2p[1]); + close(pipe_p2c[0]); + close(pipe_p2c[1]); + return; + } + + if (pid == 0) { + /* install signal handler and notify parent */ + sa.sa_sigaction = sigusr1_siginfo_handler; + sa.sa_flags = SA_RESTART | SA_SIGINFO; + ASSERT_NEQ(sigaction(SIGUSR1, &sa, NULL), -1, "sigaction"); + + close(pipe_c2p[0]); /* close read */ + close(pipe_p2c[1]); /* close write */ + + /* notify parent signal handler is installed */ + ASSERT_EQ(write(pipe_c2p[1], buf, 1), 1, "pipe_write"); + + /* make sure parent enabled bpf program to send_signal */ + ASSERT_EQ(read(pipe_p2c[0], buf, 1), 1, "pipe_read"); + + /* the signal is sent from workqueue context, so nothing has + * to be triggered from here + */ + while (!sigusr1_received) + sleep(1); + + buf[0] = sigusr1_received; + + ASSERT_EQ(sigusr1_received, 8, "sigusr1_received"); + ASSERT_EQ(write(pipe_c2p[1], buf, 1), 1, "pipe_write"); + + close(pipe_c2p[1]); + close(pipe_p2c[0]); + exit(0); + } + + close(pipe_c2p[1]); /* close write */ + close(pipe_p2c[0]); /* close read */ + + skel = test_send_signal_kern__open_and_load(); + if (!ASSERT_OK_PTR(skel, "skel_open_and_load")) + goto skel_open_load_failure; + + /* wait until child signal handler installed */ + ASSERT_EQ(read(pipe_c2p[0], buf, 1), 1, "pipe_read"); + + /* pid == 0 keeps the other programs of this skeleton inactive */ + skel->bss->pid = 0; + skel->bss->sig = SIGUSR1; + skel->bss->target_pid = pid; + + err = test_send_signal_kern__attach(skel); + if (!ASSERT_OK(err, "skel_attach")) { + err = -1; + goto destroy_skel; + } + + /* notify child that bpf program can send_signal now */ + ASSERT_EQ(write(pipe_p2c[1], buf, 1), 1, "pipe_write"); + + /* wait for result, workqueues run on their own */ + err = read_with_timeout(pipe_c2p[0], buf, 1, 10 * 1000 * 1000); + if (!ASSERT_GT(err, 0, "reading pipe")) + goto destroy_skel; + + ASSERT_EQ(buf[0], 8, "incorrect result"); + +destroy_skel: + test_send_signal_kern__destroy(skel); +skel_open_load_failure: + close(pipe_c2p[0]); + close(pipe_p2c[1]); + /* + * Child is either about to exit cleanly or stuck in case of errors. + * Nudge it to exit. + */ + kill(pid, SIGKILL); + wait(NULL); +} + void test_send_signal(void) { if (test__start_subtest("send_signal_tracepoint")) @@ -289,4 +391,6 @@ void test_send_signal(void) test_send_signal_perf(true, true); if (test__start_subtest("send_signal_nmi_thread_remote")) test_send_signal_nmi(true, true); + if (test__start_subtest("send_signal_kworker")) + test_send_signal_kworker(); } diff --git a/tools/testing/selftests/bpf/progs/test_send_signal_kern.c b/tools/testing/selftests/bpf/progs/test_send_signal_kern.c index 176a355e3062..d1c000c30885 100644 --- a/tools/testing/selftests/bpf/progs/test_send_signal_kern.c +++ b/tools/testing/selftests/bpf/progs/test_send_signal_kern.c @@ -60,6 +60,32 @@ int send_signal_tp_sched(void *ctx) return bpf_send_signal_test(ctx); } +/* Send a signal to a task other than current, from a context where current + * is a kernel thread. No filtering on current is possible here, so this is + * driven entirely by target_pid. + */ +SEC("tp_btf/workqueue_execute_start") +int send_signal_kworker(void *ctx) +{ + struct task_struct *target_task; + int ret; + + if (status != 0 || target_pid == 0) + return 0; + + target_task = bpf_task_from_pid(target_pid); + if (!target_task) + return 0; + + ret = bpf_send_signal_task(target_task, sig, PIDTYPE_TGID, 8); + bpf_task_release(target_task); + + if (ret == 0) + status = 1; + + return 0; +} + SEC("perf_event") int send_signal_perf(void *ctx) { -- 2.34.1