[PATCH 1/4] seccomp: wake poll waiters after failed addfd send

From: Andrea Parri

Date: Wed Nov 04 2026 - 05:39:23 EST


An atomic SECCOMP_IOCTL_NOTIF_ADDFD request stops the listener from
reporting EPOLLOUT for its notification while the request is pending. If
installing the file descriptor fails, 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.

The atomic path changes the state from SENT to REPLIED before asking the
target to install the descriptor. seccomp_handle_addfd() restores SENT on
failure, but unlike seccomp_notify_recv(), it does not wake poll waiters.

Wake EPOLLOUT waiters on that failure path. Extend the RLIMIT_NOFILE
test to consume the initial edge and require another after a failed
ADDFD+SEND.

The bug was found and the fix was written with the help of an LLM.

Fixes: 0ae71c7720e3a ("seccomp: Support atomic "addfd + send reply"")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Andrea Parri <parri.andrea@xxxxxxxxx>
---
kernel/seccomp.c | 7 +++++--
tools/testing/selftests/seccomp/seccomp_bpf.c | 15 ++++++++++++++-
2 files changed, 19 insertions(+), 3 deletions(-)

diff --git a/kernel/seccomp.c b/kernel/seccomp.c
index 3eecba5ccdef3..d74e66bcb5211 100644
--- a/kernel/seccomp.c
+++ b/kernel/seccomp.c
@@ -1125,7 +1125,9 @@ static u64 seccomp_next_notify_id(struct seccomp_filter *filter)
return filter->notif->next_id++;
}

-static void seccomp_handle_addfd(struct seccomp_kaddfd *addfd, struct seccomp_knotif *n)
+static void seccomp_handle_addfd(struct seccomp_filter *filter,
+ struct seccomp_kaddfd *addfd,
+ struct seccomp_knotif *n)
{
int fd;

@@ -1144,6 +1146,7 @@ static void seccomp_handle_addfd(struct seccomp_kaddfd *addfd, struct seccomp_kn
/* If we fail reset and return an error to the notifier */
if (fd < 0) {
n->state = SECCOMP_NOTIFY_SENT;
+ wake_up_poll(&filter->wqh, EPOLLOUT | EPOLLWRNORM);
} else {
/* Return the FD we just added */
n->flags = 0;
@@ -1224,7 +1227,7 @@ static int seccomp_do_user_notification(int this_syscall,
struct seccomp_kaddfd, list);
/* Check if we were woken up by a addfd message */
if (addfd)
- seccomp_handle_addfd(addfd, &n);
+ seccomp_handle_addfd(match, addfd, &n);

} while (n.state != SECCOMP_NOTIFY_REPLIED);

diff --git a/tools/testing/selftests/seccomp/seccomp_bpf.c b/tools/testing/selftests/seccomp/seccomp_bpf.c
index 66bb53bad93bc..6dd51efe8dff1 100644
--- a/tools/testing/selftests/seccomp/seccomp_bpf.c
+++ b/tools/testing/selftests/seccomp/seccomp_bpf.c
@@ -22,6 +22,7 @@

#include <errno.h>
#include <linux/filter.h>
+#include <sys/epoll.h>
#include <sys/prctl.h>
#include <sys/ptrace.h>
#include <sys/time.h>
@@ -4391,7 +4392,10 @@ TEST(user_notification_addfd_rlimit)
{
pid_t pid;
long ret;
- int status, listener, memfd;
+ int epollfd, status, listener, memfd;
+ struct epoll_event event = {
+ .events = EPOLLOUT | EPOLLET,
+ };
struct seccomp_notif_addfd addfd = {};
struct seccomp_notif req = {};
struct seccomp_notif_resp resp = {};
@@ -4434,9 +4438,17 @@ TEST(user_notification_addfd_rlimit)
EXPECT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_ADDFD, &addfd), -1);
EXPECT_EQ(errno, EMFILE);

+ 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);
+
addfd.flags = SECCOMP_ADDFD_FLAG_SEND;
EXPECT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_ADDFD, &addfd), -1);
EXPECT_EQ(errno, EMFILE);
+ EXPECT_EQ(epoll_wait(epollfd, &event, 1, 1000), 1);
+ EXPECT_NE(event.events & EPOLLOUT, 0);

addfd.newfd = 100;
addfd.flags = SECCOMP_ADDFD_FLAG_SETFD;
@@ -4454,6 +4466,7 @@ TEST(user_notification_addfd_rlimit)
EXPECT_EQ(true, WIFEXITED(status));
EXPECT_EQ(0, WEXITSTATUS(status));

+ close(epollfd);
close(memfd);
}

--
2.53.0