An atomic SECCOMP_IOCTL_NOTIF_ADDFD request stops the listener from reporting EPOLLOUT for its notification while the request is pending. If a signal interrupts the request before the target dequeues it, the notification becomes writable again, but poll waiters are not woken. A supervisor waiting for EPOLLOUT can then remain asleep, leaving the target blocked in the trapped syscall. This affects edge-triggered epoll if the supervisor consumed the event before the request, and level-triggered epoll, poll() and select() if a concurrent waiter found no EPOLLOUT while the request was pending. seccomp_notify_addfd() restores the state from REPLIED to SENT on this path, but unlike seccomp_notify_recv() and seccomp_handle_addfd(), it does not wake poll waiters. Wake EPOLLOUT waiters when restoring SENT. Extend the interrupted ADDFD+SEND test to consume the initial edge and require another after the interrupted request. The bug was found and the fix was written with the help of an LLM. Fixes: 832b9b176be06 ("seccomp: restore knotif->state when SECCOMP_ADDFD_FLAG_SEND is interrupted") Cc: stable@vger.kernel.org Signed-off-by: Andrea Parri --- kernel/seccomp.c | 4 +++- tools/testing/selftests/seccomp/seccomp_bpf.c | 15 ++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/kernel/seccomp.c b/kernel/seccomp.c index d74e66bcb5211..ee94a071736ef 100644 --- a/kernel/seccomp.c +++ b/kernel/seccomp.c @@ -1824,8 +1824,10 @@ static long seccomp_notify_addfd(struct seccomp_filter *filter, ret = kaddfd.ret; } else { list_del(&kaddfd.list); - if (addfd.flags & SECCOMP_ADDFD_FLAG_SEND) + if (addfd.flags & SECCOMP_ADDFD_FLAG_SEND) { knotif->state = SECCOMP_NOTIFY_SENT; + wake_up_poll(&filter->wqh, EPOLLOUT | EPOLLWRNORM); + } } out_unlock: diff --git a/tools/testing/selftests/seccomp/seccomp_bpf.c b/tools/testing/selftests/seccomp/seccomp_bpf.c index 6dd51efe8dff1..123eddb0c0d64 100644 --- a/tools/testing/selftests/seccomp/seccomp_bpf.c +++ b/tools/testing/selftests/seccomp/seccomp_bpf.c @@ -4485,6 +4485,7 @@ static void empty_handler(int signo) * returning 0 from the trapped syscall without the FD installed. * 2. The supervisor can retry SECCOMP_IOCTL_NOTIF_ADDFD (or * SECCOMP_IOCTL_NOTIF_SEND) instead of failing with -EINPROGRESS. + * 3. Poll waiters see a new EPOLLOUT edge for the notification. * * To deterministically hit the race window where the supervisor sleeps in * wait_for_completion_interruptible(&kaddfd.completion) after waking the @@ -4504,7 +4505,10 @@ TEST(user_notification_addfd_send_interrupted) */ pid_t tracee_pid, sig_pid, parent_pid = getpid(); long ret; - int status, listener, memfd, err; + int epollfd, status, listener, memfd, err; + struct epoll_event event = { + .events = EPOLLOUT | EPOLLET, + }; struct seccomp_notif_addfd addfd = {}; struct seccomp_notif req = {}; struct sigaction sa = {}; @@ -4563,6 +4567,12 @@ TEST(user_notification_addfd_send_interrupted) /* Wait for the tracee to trap in do_user_notif(). */ ASSERT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_RECV, &req), 0); + epollfd = epoll_create1(EPOLL_CLOEXEC); + ASSERT_GE(epollfd, 0); + ASSERT_EQ(epoll_ctl(epollfd, EPOLL_CTL_ADD, listener, &event), 0); + ASSERT_EQ(epoll_wait(epollfd, &event, 1, 1000), 1); + ASSERT_NE(event.events & EPOLLOUT, 0); + /* * Demote the tracee to SCHED_IDLE and promote the supervisor to * SCHED_FIFO(99) on the same CPU. @@ -4630,6 +4640,8 @@ TEST(user_notification_addfd_send_interrupted) EXPECT_EQ(waitpid(sig_pid, &status, 0), sig_pid); EXPECT_EQ(true, WIFEXITED(status)); EXPECT_EQ(0, WEXITSTATUS(status)); + EXPECT_EQ(epoll_wait(epollfd, &event, 1, 1000), 1); + EXPECT_NE(event.events & EPOLLOUT, 0); nanosleep(&delay, NULL); @@ -4645,6 +4657,7 @@ TEST(user_notification_addfd_send_interrupted) EXPECT_EQ(true, WIFEXITED(status)); EXPECT_EQ(0, WEXITSTATUS(status)); + close(epollfd); close(listener); close(memfd); } -- 2.53.0