* [PATCH 0/4] seccomp: fix user-notification state transitions
@ 2026-11-04 10:37 Andrea Parri
2026-11-04 10:37 ` [PATCH 1/4] seccomp: wake poll waiters after failed addfd send Andrea Parri
` (4 more replies)
0 siblings, 5 replies; 13+ messages in thread
From: Andrea Parri @ 2026-11-04 10:37 UTC (permalink / raw)
To: Kees Cook
Cc: Andrea Parri, Andy Lutomirski, Will Drewry, Shuah Khan,
Rodrigo Campos, Christian Brauner, Tycho Andersen,
Sargun Dhillon, Hui Peng, Bradley Morgan, Cong Wang,
linux-kernel, linux-kselftest, bpf
An atomic SECCOMP_IOCTL_NOTIF_ADDFD request moves a notification from
SENT to REPLIED, so the listener stops reporting EPOLLOUT for it. Two
paths move it back to SENT without waking poll waiters, so a supervisor
waiting for EPOLLOUT, with edge- or level-triggered epoll, poll() or
select(), can remain asleep, leaving the target blocked in the trapped
syscall.
Patch 1 fixes the failed descriptor install, present since commit
0ae71c7720e3a ("seccomp: Support atomic "addfd + send reply""). Patch 2
fixes the restore on signal interruption, added by
commit 832b9b176be06 ("seccomp: restore knotif->state when
SECCOMP_ADDFD_FLAG_SEND is interrupted"). Patch 3 moves the state change
and the wakeup into one helper, with no functional change.
SECCOMP_IOCTL_NOTIF_RECV marks a notification SENT before copying it to
userspace. Unless SECCOMP_FILTER_FLAG_WAIT_KILLABLE_RECV is also set, a
signal during the copy makes the target return EINTR, even with
SECCOMP_FILTER_FLAG_RESTART_BEFORE_RECV, while the supervisor can receive
a withdrawn ID. With it, the signal is held back as if the notification
had already been received. Patch 4 adds a RECEIVING state and marks the
notification SENT only after the copy.
Patch 4 affects all filters, not only those using RESTART_BEFORE_RECV:
RECV now fails with ENOENT if the target withdraws the notification during
the copy. It clears the buffer first, and
Documentation/userspace-api/seccomp_filter.rst already asks supervisors to
retry on ENOENT.
All four patches concern transitions into SENT; patch 4 takes its
post-copy transition through the helper added by patch 3.
Tested in vng on x86_64 with the full seccomp_bpf suite, using the seccomp
selftest config plus CHECKPOINT_RESTORE: 146 passed, 0 failed, 0 skipped.
Reverting the kernel/seccomp.c changes of the whole series makes
user_notification_addfd_rlimit and user_notification_addfd_send_interrupted
time out on their second epoll_wait() and all four during_receive variants
fail; reverting only those of patch 4 makes just the during_receive
variants fail.
The bugs were found and the patches were written with the help of an LLM.
---
Reviewer notes: 832b9b176be06 and f7952251ecc3, fixed by patches 2 and 4,
are in v7.4-rc1. Patches 3 and 4 only need backporting to 7.4.x:
f7952251ecc3 adds a new flag that is not tagged for stable itself,
unlike 832b9b176be06, and patch 3 is tagged only because patch 4 needs
it.
Patches 1 and 2 do not depend on patches 3 and 4 and can be applied
first.
The series conflicts with Cong Wang's RFC "seccomp: report the result of
continued system calls" [1], which is based on v7.3. After patch 4, a
task killed while its held notification is being received is in
RECEIVING, so the "killed unseen" check in seccomp_hold_result() would
need to become n.state <= SECCOMP_NOTIFY_RECEIVING for the result to
still be queued.
[1] https://lore.kernel.org/all/20261020001124.974063-1-xiyou.wangcong@gmail.com/
Andrea Parri (4):
seccomp: wake poll waiters after failed addfd send
seccomp: wake poll waiters after interrupted addfd send
seccomp: add a helper to move a notification to SENT
seccomp: publish user notifications after copying them to userspace
kernel/seccomp.c | 79 +++++----
tools/testing/selftests/seccomp/config | 1 +
tools/testing/selftests/seccomp/seccomp_bpf.c | 162 +++++++++++++++++-
3 files changed, 204 insertions(+), 38 deletions(-)
base-commit: 6628e7d4c6b25c8d18f1345e3a20d21829aae7e4
--
2.53.0
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH 1/4] seccomp: wake poll waiters after failed addfd send
2026-11-04 10:37 [PATCH 0/4] seccomp: fix user-notification state transitions Andrea Parri
@ 2026-11-04 10:37 ` Andrea Parri
2026-11-04 16:19 ` Tycho Andersen
2026-11-04 10:37 ` [PATCH 2/4] seccomp: wake poll waiters after interrupted " Andrea Parri
` (3 subsequent siblings)
4 siblings, 1 reply; 13+ messages in thread
From: Andrea Parri @ 2026-11-04 10:37 UTC (permalink / raw)
To: Kees Cook
Cc: Andrea Parri, Andy Lutomirski, Will Drewry, Shuah Khan,
Rodrigo Campos, Christian Brauner, Tycho Andersen,
Sargun Dhillon, Hui Peng, Bradley Morgan, Cong Wang,
linux-kernel, linux-kselftest, bpf
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@vger.kernel.org
Signed-off-by: Andrea Parri <parri.andrea@gmail.com>
---
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
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH 2/4] seccomp: wake poll waiters after interrupted addfd send
2026-11-04 10:37 [PATCH 0/4] seccomp: fix user-notification state transitions Andrea Parri
2026-11-04 10:37 ` [PATCH 1/4] seccomp: wake poll waiters after failed addfd send Andrea Parri
@ 2026-11-04 10:37 ` Andrea Parri
2026-11-04 10:37 ` [PATCH 3/4] seccomp: add a helper to move a notification to SENT Andrea Parri
` (2 subsequent siblings)
4 siblings, 0 replies; 13+ messages in thread
From: Andrea Parri @ 2026-11-04 10:37 UTC (permalink / raw)
To: Kees Cook
Cc: Andrea Parri, Andy Lutomirski, Will Drewry, Shuah Khan,
Rodrigo Campos, Christian Brauner, Tycho Andersen,
Sargun Dhillon, Hui Peng, Bradley Morgan, Cong Wang,
linux-kernel, linux-kselftest, bpf
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 <parri.andrea@gmail.com>
---
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
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH 3/4] seccomp: add a helper to move a notification to SENT
2026-11-04 10:37 [PATCH 0/4] seccomp: fix user-notification state transitions Andrea Parri
2026-11-04 10:37 ` [PATCH 1/4] seccomp: wake poll waiters after failed addfd send Andrea Parri
2026-11-04 10:37 ` [PATCH 2/4] seccomp: wake poll waiters after interrupted " Andrea Parri
@ 2026-11-04 10:37 ` Andrea Parri
2026-11-04 10:37 ` [PATCH 4/4] seccomp: publish user notifications after copying them to userspace Andrea Parri
2026-11-06 11:32 ` [PATCH 0/4] seccomp: fix user-notification state transitions Andrea Parri
4 siblings, 0 replies; 13+ messages in thread
From: Andrea Parri @ 2026-11-04 10:37 UTC (permalink / raw)
To: Kees Cook
Cc: Andrea Parri, Andy Lutomirski, Will Drewry, Shuah Khan,
Rodrigo Campos, Christian Brauner, Tycho Andersen,
Sargun Dhillon, Hui Peng, Bradley Morgan, Cong Wang,
linux-kernel, linux-kselftest, bpf
seccomp_notify_recv(), seccomp_handle_addfd() and seccomp_notify_addfd()
each move a user notification to the SENT state. The notification then
accepts a reply, so each site must also wake EPOLLOUT waiters. Both
addfd paths have missed that wakeup before.
Move the state change and the wakeup into seccomp_notify_set_sent() so a
new path cannot set SENT without waking poll waiters. The helper asserts
that notify_lock is held, as all three callers already do.
No functional change intended.
The change was written with the help of an LLM.
Cc: stable@vger.kernel.org # 7.4.x
Signed-off-by: Andrea Parri <parri.andrea@gmail.com>
---
kernel/seccomp.c | 21 +++++++++++++--------
1 file changed, 13 insertions(+), 8 deletions(-)
diff --git a/kernel/seccomp.c b/kernel/seccomp.c
index ee94a071736ef..75a758128db2d 100644
--- a/kernel/seccomp.c
+++ b/kernel/seccomp.c
@@ -1125,6 +1125,15 @@ static u64 seccomp_next_notify_id(struct seccomp_filter *filter)
return filter->notif->next_id++;
}
+/* A notification in SENT state accepts a reply; tell poll waiters. */
+static void seccomp_notify_set_sent(struct seccomp_filter *filter,
+ struct seccomp_knotif *n)
+{
+ lockdep_assert_held(&filter->notify_lock);
+ n->state = SECCOMP_NOTIFY_SENT;
+ wake_up_poll(&filter->wqh, EPOLLOUT | EPOLLWRNORM);
+}
+
static void seccomp_handle_addfd(struct seccomp_filter *filter,
struct seccomp_kaddfd *addfd,
struct seccomp_knotif *n)
@@ -1145,8 +1154,7 @@ static void seccomp_handle_addfd(struct seccomp_filter *filter,
if (addfd->ioctl_flags & SECCOMP_ADDFD_FLAG_SEND) {
/* 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);
+ seccomp_notify_set_sent(filter, n);
} else {
/* Return the FD we just added */
n->flags = 0;
@@ -1600,8 +1608,7 @@ static long seccomp_notify_recv(struct seccomp_filter *filter,
unotif.pid = task_pid_vnr(knotif->task);
unotif.data = *(knotif->data);
- knotif->state = SECCOMP_NOTIFY_SENT;
- wake_up_poll(&filter->wqh, EPOLLOUT | EPOLLWRNORM);
+ seccomp_notify_set_sent(filter, knotif);
ret = 0;
out:
mutex_unlock(&filter->notify_lock);
@@ -1824,10 +1831,8 @@ static long seccomp_notify_addfd(struct seccomp_filter *filter,
ret = kaddfd.ret;
} else {
list_del(&kaddfd.list);
- if (addfd.flags & SECCOMP_ADDFD_FLAG_SEND) {
- knotif->state = SECCOMP_NOTIFY_SENT;
- wake_up_poll(&filter->wqh, EPOLLOUT | EPOLLWRNORM);
- }
+ if (addfd.flags & SECCOMP_ADDFD_FLAG_SEND)
+ seccomp_notify_set_sent(filter, knotif);
}
out_unlock:
--
2.53.0
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH 4/4] seccomp: publish user notifications after copying them to userspace
2026-11-04 10:37 [PATCH 0/4] seccomp: fix user-notification state transitions Andrea Parri
` (2 preceding siblings ...)
2026-11-04 10:37 ` [PATCH 3/4] seccomp: add a helper to move a notification to SENT Andrea Parri
@ 2026-11-04 10:37 ` Andrea Parri
2026-11-06 11:32 ` [PATCH 0/4] seccomp: fix user-notification state transitions Andrea Parri
4 siblings, 0 replies; 13+ messages in thread
From: Andrea Parri @ 2026-11-04 10:37 UTC (permalink / raw)
To: Kees Cook
Cc: Andrea Parri, Andy Lutomirski, Will Drewry, Shuah Khan,
Rodrigo Campos, Christian Brauner, Tycho Andersen,
Sargun Dhillon, Hui Peng, Bradley Morgan, Cong Wang,
linux-kernel, linux-kselftest, bpf
SECCOMP_FILTER_FLAG_RESTART_BEFORE_RECV restarts a syscall when a signal
interrupts its notification before the supervisor receives it. However,
seccomp_notify_recv() marks the notification SENT before copy_to_user().
Unless WAIT_KILLABLE_RECV is also set, a signal in that window makes the
target return EINTR while the supervisor can receive a stale notification
ID; with it, the signal is held back until the reply.
CPU 1 (target) CPU 2 (supervisor)
------------------------------ ------------------------------
mark notification SENT
receive signal
observe SENT copy notification to user
remove notification
return EINTR return success with stale ID
Cure this by reserving the notification in a new RECEIVING state. Move it
to SENT after copy_to_user() succeeds and it remains on the notification
list. The target's interrupted wait treats RECEIVING like INIT, while the
receiver returns ENOENT if the target withdraws the notification during
the copy.
When it returns ENOENT, the receiver clears the copied notification, so
userspace can retry with the same zeroed buffer, and returns EFAULT if
clearing fails.
The copy failure path no longer wakes a killable waiter. The target only
switches to a killable wait once its notification is SENT, which now
happens after the copy, and a SENT notification never returns to INIT.
Filters without RESTART_BEFORE_RECV see the same change. Without
WAIT_KILLABLE_RECV, the receiver fails with ENOENT instead of returning a
withdrawn ID. With it, a signal during the copy is handled normally, as
documented for signals sent before userspace receives the notification.
This should be safe because notify_lock protects every state transition
and the post-copy list lookup. seccomp_notify_recv() works from its
by-value copy across the unlocked copy and looks the notification up again
by ID, so it does not dereference the target's stack object after the
target can remove it.
Add a regression test which uses userfaultfd to stop copy_to_user() in the
race window and checks that the buffer is zeroed again on ENOENT. All four
flag variants fail without the kernel change and pass with it when run with
the following command.
./seccomp_bpf -t during_receive
The test needs CONFIG_USERFAULTFD, so add it to the seccomp selftest
config.
The race was found and the fix was written with the help of an LLM.
Fixes: f7952251ecc3 ("seccomp: allow restarting interrupted unreceived notifications")
Cc: stable@vger.kernel.org
Signed-off-by: Andrea Parri <parri.andrea@gmail.com>
---
kernel/seccomp.c | 59 ++++----
tools/testing/selftests/seccomp/config | 1 +
tools/testing/selftests/seccomp/seccomp_bpf.c | 132 ++++++++++++++++++
3 files changed, 161 insertions(+), 31 deletions(-)
diff --git a/kernel/seccomp.c b/kernel/seccomp.c
index 75a758128db2d..7dfd5eec6d640 100644
--- a/kernel/seccomp.c
+++ b/kernel/seccomp.c
@@ -54,6 +54,7 @@
enum notify_state {
SECCOMP_NOTIFY_INIT,
+ SECCOMP_NOTIFY_RECEIVING,
SECCOMP_NOTIFY_SENT,
SECCOMP_NOTIFY_REPLIED,
};
@@ -74,11 +75,12 @@ struct seccomp_knotif {
/*
* Notification states. When SECCOMP_RET_USER_NOTIF is returned, a
- * struct seccomp_knotif is created and starts out in INIT. Once the
- * handler reads the notification off of an FD, it transitions to SENT.
- * If a signal is received the state transitions back to INIT and
- * another message is sent. When the userspace handler replies, state
- * transitions to REPLIED.
+ * struct seccomp_knotif is created and starts out in INIT. A receiver
+ * reserves it by moving it to RECEIVING. Once the notification has been
+ * copied to userspace, it transitions to SENT; if copying fails, it
+ * returns to INIT. An interrupted wait may withdraw the notification in
+ * any state, including RECEIVING. When the userspace handler replies,
+ * state transitions to REPLIED.
*/
enum notify_state state;
@@ -1225,7 +1227,7 @@ static int seccomp_do_user_notification(int this_syscall,
*/
if (!(!wait_killable && should_sleep_killable(match, &n))) {
if (err == -ERESTARTSYS && match->restart_before_recv &&
- n.state == SECCOMP_NOTIFY_INIT)
+ n.state <= SECCOMP_NOTIFY_RECEIVING)
err = -ERESTARTNOINTR;
goto interrupted;
}
@@ -1600,40 +1602,35 @@ static long seccomp_notify_recv(struct seccomp_filter *filter,
* when we were able to acquire the rw lock.
*/
if (!knotif) {
- ret = -ENOENT;
- goto out;
+ mutex_unlock(&filter->notify_lock);
+ return -ENOENT;
}
unotif.id = knotif->id;
unotif.pid = task_pid_vnr(knotif->task);
unotif.data = *(knotif->data);
-
- seccomp_notify_set_sent(filter, knotif);
- ret = 0;
-out:
+ knotif->state = SECCOMP_NOTIFY_RECEIVING;
mutex_unlock(&filter->notify_lock);
- if (ret == 0 && copy_to_user(buf, &unotif, sizeof(unotif))) {
- ret = -EFAULT;
+ ret = copy_to_user(buf, &unotif, sizeof(unotif)) ? -EFAULT : 0;
- /*
- * Userspace screwed up. To make sure that we keep this
- * notification alive, let's reset it back to INIT. It
- * may have died when we released the lock, so we need to make
- * sure it's still around.
- */
- mutex_lock(&filter->notify_lock);
- knotif = find_notification(filter, unotif.id);
- if (knotif) {
- /* Reset the process to make sure it's not stuck */
- if (should_sleep_killable(filter, knotif))
- complete(&knotif->ready);
- knotif->state = SECCOMP_NOTIFY_INIT;
- atomic_inc(&filter->notif->requests);
- wake_up_poll(&filter->wqh, EPOLLIN | EPOLLRDNORM);
- }
- mutex_unlock(&filter->notify_lock);
+ mutex_lock(&filter->notify_lock);
+ knotif = find_notification(filter, unotif.id);
+ if (!knotif) {
+ if (!ret)
+ ret = -ENOENT;
+ } else if (!ret) {
+ seccomp_notify_set_sent(filter, knotif);
+ } else {
+ knotif->state = SECCOMP_NOTIFY_INIT;
+ atomic_inc(&filter->notif->requests);
+ wake_up_poll(&filter->wqh, EPOLLIN | EPOLLRDNORM);
}
+ mutex_unlock(&filter->notify_lock);
+
+ /* Leave the buffer zeroed so that userspace can retry unchanged. */
+ if (ret == -ENOENT && clear_user(buf, sizeof(unotif)))
+ ret = -EFAULT;
return ret;
}
diff --git a/tools/testing/selftests/seccomp/config b/tools/testing/selftests/seccomp/config
index ad431a5178fbe..c6e14eb473fd9 100644
--- a/tools/testing/selftests/seccomp/config
+++ b/tools/testing/selftests/seccomp/config
@@ -1,4 +1,5 @@
CONFIG_PID_NS=y
CONFIG_SECCOMP=y
CONFIG_SECCOMP_FILTER=y
+CONFIG_USERFAULTFD=y
CONFIG_USER_NS=y
diff --git a/tools/testing/selftests/seccomp/seccomp_bpf.c b/tools/testing/selftests/seccomp/seccomp_bpf.c
index 123eddb0c0d64..1ac5b6960a1ce 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 <linux/userfaultfd.h>
#include <sys/epoll.h>
#include <sys/prctl.h>
#include <sys/ptrace.h>
@@ -5137,6 +5138,137 @@ TEST_F(notification_restart, failed_receive)
notification_result(_metadata, self, -1, EAGAIN);
}
+#ifdef __NR_userfaultfd
+struct notification_receive_args {
+ struct seccomp_notif *req;
+ int listener;
+ int ret;
+ int err;
+};
+
+static void *notification_receive(void *data)
+{
+ struct notification_receive_args *args = data;
+
+ args->ret = ioctl(args->listener, SECCOMP_IOCTL_NOTIF_RECV, args->req);
+ args->err = errno;
+ return NULL;
+}
+
+/*
+ * Write-protect the range through a new userfaultfd and return it, or
+ * return -1 with a skip reason if this kernel cannot report the fault.
+ */
+static int notification_uffd_wp(struct __test_metadata *_metadata,
+ const struct uffdio_range *range,
+ char *reason, size_t reason_len)
+{
+ struct uffdio_writeprotect wp = {};
+ struct uffdio_register reg = {};
+ struct uffdio_api api = {
+ .api = UFFD_API,
+ .features = UFFD_FEATURE_PAGEFAULT_FLAG_WP,
+ };
+ int ret, uffd;
+
+ /* Kernel-originated faults require a privileged userfaultfd. */
+ uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
+ if (uffd < 0) {
+ snprintf(reason, reason_len,
+ "userfaultfd for kernel faults unavailable: %s",
+ strerror(errno));
+ return -1;
+ }
+ ret = ioctl(uffd, UFFDIO_API, &api);
+ if (ret < 0 && errno == EINVAL) {
+ snprintf(reason, reason_len, "userfaultfd API is not available");
+ goto skip;
+ }
+ ASSERT_EQ(ret, 0);
+ if (!(api.features & UFFD_FEATURE_PAGEFAULT_FLAG_WP)) {
+ snprintf(reason, reason_len,
+ "userfaultfd write protection is not available");
+ goto skip;
+ }
+
+ reg.range = *range;
+ reg.mode = UFFDIO_REGISTER_MODE_WP;
+ ASSERT_EQ(ioctl(uffd, UFFDIO_REGISTER, ®), 0);
+ wp.range = *range;
+ wp.mode = UFFDIO_WRITEPROTECT_MODE_WP;
+ ASSERT_EQ(ioctl(uffd, UFFDIO_WRITEPROTECT, &wp), 0);
+ return uffd;
+
+skip:
+ ASSERT_EQ(close(uffd), 0);
+ return -1;
+}
+
+TEST_F(notification_restart, during_receive)
+{
+ struct notification_receive_args args = { .listener = self->listener };
+ struct seccomp_notif req = {};
+ struct seccomp_notif_resp resp = {};
+ struct uffdio_writeprotect wp = {};
+ struct uffdio_range range = {};
+ struct pollfd pfd = { .events = POLLIN };
+ struct uffd_msg msg;
+ char reason[128];
+ pthread_t thread;
+ long page_size;
+ int uffd;
+
+ page_size = sysconf(_SC_PAGESIZE);
+ ASSERT_GT(page_size, 0);
+ args.req = mmap(NULL, page_size, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ ASSERT_NE(args.req, MAP_FAILED);
+ memset(args.req, 0, page_size);
+
+ range.start = (unsigned long)args.req;
+ range.len = page_size;
+ uffd = notification_uffd_wp(_metadata, &range, reason, sizeof(reason));
+ if (uffd < 0) {
+ ASSERT_EQ(munmap(args.req, page_size), 0);
+ SKIP(return, "%s", reason);
+ }
+
+ notification_restart_child(_metadata, self);
+ notification_pending(_metadata, self->listener);
+ ASSERT_EQ(pthread_create(&thread, NULL, notification_receive, &args), 0);
+ pfd.fd = uffd;
+ ASSERT_EQ(poll(&pfd, 1, 5000), 1);
+ ASSERT_EQ(read(uffd, &msg, sizeof(msg)), sizeof(msg));
+ ASSERT_EQ(msg.event, UFFD_EVENT_PAGEFAULT);
+ ASSERT_TRUE(msg.arg.pagefault.flags & UFFD_PAGEFAULT_FLAG_WP);
+
+ /* Withdraw the notification while copy_to_user() is stalled. */
+ notification_signal(_metadata, self);
+ wp.range = range;
+ wp.mode = 0;
+ ASSERT_EQ(ioctl(uffd, UFFDIO_WRITEPROTECT, &wp), 0);
+ ASSERT_EQ(pthread_join(thread, NULL), 0);
+ ASSERT_EQ(args.ret, -1);
+ EXPECT_EQ(args.err, ENOENT);
+ EXPECT_EQ(memcmp(args.req, &req, sizeof(req)), 0);
+ ASSERT_EQ(close(uffd), 0);
+ ASSERT_EQ(munmap(args.req, page_size), 0);
+
+ if (!variant->restart) {
+ notification_result(_metadata, self, -1, EINTR);
+ return;
+ }
+
+ notification_pending(_metadata, self->listener);
+ ASSERT_EQ(ioctl(self->listener, SECCOMP_IOCTL_NOTIF_RECV,
+ &req), 0);
+ resp.id = req.id;
+ resp.flags = SECCOMP_USER_NOTIF_FLAG_CONTINUE;
+ ASSERT_EQ(ioctl(self->listener, SECCOMP_IOCTL_NOTIF_SEND, &resp), 0);
+ notification_result(_metadata, self, getpid(), 0);
+}
+#endif
+
TEST_F(notification_restart, after_receive)
{
struct seccomp_notif req = {};
--
2.53.0
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH 1/4] seccomp: wake poll waiters after failed addfd send
2026-11-04 10:37 ` [PATCH 1/4] seccomp: wake poll waiters after failed addfd send Andrea Parri
@ 2026-11-04 16:19 ` Tycho Andersen
2026-11-05 15:37 ` Andrea Parri
0 siblings, 1 reply; 13+ messages in thread
From: Tycho Andersen @ 2026-11-04 16:19 UTC (permalink / raw)
To: Andrea Parri
Cc: Kees Cook, Andy Lutomirski, Will Drewry, Shuah Khan,
Rodrigo Campos, Christian Brauner, Sargun Dhillon, Hui Peng,
Bradley Morgan, Cong Wang, linux-kernel, linux-kselftest, bpf
On Wed, Nov 04, 2026 at 11:37:31AM +0100, Andrea Parri wrote:
> 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.
Hm, don't we return success or failure from
ioctl(SECCOMP_IOCTL_NOTIF_ADDFD) via kaddfd.ret in
seccomp_notify_addfd()?
I don't really understand why this is necessary unless you're ignoring
the return value from that call.
Tycho
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH 1/4] seccomp: wake poll waiters after failed addfd send
2026-11-04 16:19 ` Tycho Andersen
@ 2026-11-05 15:37 ` Andrea Parri
2026-11-05 16:25 ` Tycho Andersen
0 siblings, 1 reply; 13+ messages in thread
From: Andrea Parri @ 2026-11-05 15:37 UTC (permalink / raw)
To: Tycho Andersen
Cc: Andrea Parri, Kees Cook, Andy Lutomirski, Will Drewry,
Shuah Khan, Christian Brauner, Sargun Dhillon, Hui Peng,
Bradley Morgan, Cong Wang, linux-kernel, linux-kselftest, bpf
On Wed, Nov 04, 2026 at 09:19:10AM -0700, Tycho Andersen wrote:
> Hm, don't we return success or failure from
> ioctl(SECCOMP_IOCTL_NOTIF_ADDFD) via kaddfd.ret in
> seccomp_notify_addfd()?
>
> I don't really understand why this is necessary unless you're ignoring
> the return value from that call.
Right, the issuing thread gets the error and can recover directly. The
edge-triggered example demonstrates the missing wakeup, but does not
establish that a supervisor needs it to recover.
The wakeup matters for other waiters on the listener. A thread or
process waiting for EPOLLOUT while another one issues the request can
see no EPOLLOUT while the request is pending, and nothing wakes it when
the failure moves the notification back to SENT. seccomp_notify_recv()
wakes EPOLLOUT on its transition to SENT, so the two addfd paths are the
odd ones out.
I don't know of a supervisor that waits for EPOLLOUT from a thread other
than the one replying, so the motivation is to report EPOLLOUT
consistently when a notification becomes SENT again, not to fix a
reported hang. I'll revise the changelogs to make that distinction
clear, and drop the stable tags from patches 1 and 2.
Thanks,
Andrea
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH 1/4] seccomp: wake poll waiters after failed addfd send
2026-11-05 15:37 ` Andrea Parri
@ 2026-11-05 16:25 ` Tycho Andersen
2026-11-05 17:35 ` Andrea Parri
0 siblings, 1 reply; 13+ messages in thread
From: Tycho Andersen @ 2026-11-05 16:25 UTC (permalink / raw)
To: Andrea Parri
Cc: Kees Cook, Andy Lutomirski, Will Drewry, Shuah Khan,
Christian Brauner, Sargun Dhillon, Hui Peng, Bradley Morgan,
Cong Wang, linux-kernel, linux-kselftest, bpf
On Thu, Nov 05, 2026 at 04:37:34PM +0100, Andrea Parri wrote:
> On Wed, Nov 04, 2026 at 09:19:10AM -0700, Tycho Andersen wrote:
> > Hm, don't we return success or failure from
> > ioctl(SECCOMP_IOCTL_NOTIF_ADDFD) via kaddfd.ret in
> > seccomp_notify_addfd()?
> >
> > I don't really understand why this is necessary unless you're ignoring
> > the return value from that call.
>
> Right, the issuing thread gets the error and can recover directly. The
> edge-triggered example demonstrates the missing wakeup, but does not
> establish that a supervisor needs it to recover.
...
> I don't know of a supervisor that waits for EPOLLOUT from a thread
> other than the one replying ... not to fix a reported hang.
Seems like a solution in search of a problem to me.
Is there a sane action a supervisor can take on failure here besides
killing the thread that made the syscall? If so, perhaps it's
worthwhile. Or maybe it's worth it "for completeness" and I'm just
being grumpy :)
Tycho
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH 1/4] seccomp: wake poll waiters after failed addfd send
2026-11-05 16:25 ` Tycho Andersen
@ 2026-11-05 17:35 ` Andrea Parri
2026-11-05 17:42 ` Tycho Andersen
0 siblings, 1 reply; 13+ messages in thread
From: Andrea Parri @ 2026-11-05 17:35 UTC (permalink / raw)
To: Tycho Andersen
Cc: Andrea Parri, Kees Cook, Andy Lutomirski, Will Drewry,
Shuah Khan, Christian Brauner, Sargun Dhillon, Hui Peng,
Bradley Morgan, Cong Wang, linux-kernel, linux-kselftest, bpf
On Thu, Nov 05, 2026 at 09:25:17AM -0700, Tycho Andersen wrote:
> Seems like a solution in search of a problem to me.
>
> Is there a sane action a supervisor can take on failure here besides
> killing the thread that made the syscall? If so, perhaps it's
> worthwhile. Or maybe it's worth it "for completeness" and I'm just
> being grumpy :)
There is: seccomp_handle_addfd() puts the notification back in SENT on
failure, so the supervisor can still reply.
For example, if injecting a descriptor for an emulated open() fails with
EMFILE, the supervisor can reply with error = -EMFILE, and the target
sees an ordinary syscall failure. After an interrupted request, it can
simply retry.
But the issuing thread does that from the ioctl result, without an
additional poll wakeup, so patches 1-3 would only add completeness.
I'll drop them and send v2 with just the RECV fix (patch 4 here), unless
Kees would rather keep them.
Thanks,
Andrea
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH 1/4] seccomp: wake poll waiters after failed addfd send
2026-11-05 17:35 ` Andrea Parri
@ 2026-11-05 17:42 ` Tycho Andersen
2026-11-05 17:53 ` Andrea Parri
0 siblings, 1 reply; 13+ messages in thread
From: Tycho Andersen @ 2026-11-05 17:42 UTC (permalink / raw)
To: Andrea Parri
Cc: Kees Cook, Andy Lutomirski, Will Drewry, Shuah Khan,
Christian Brauner, Sargun Dhillon, Hui Peng, Bradley Morgan,
Cong Wang, linux-kernel, linux-kselftest, bpf
On Thu, Nov 05, 2026 at 06:35:28PM +0100, Andrea Parri wrote:
> On Thu, Nov 05, 2026 at 09:25:17AM -0700, Tycho Andersen wrote:
> > Seems like a solution in search of a problem to me.
> >
> > Is there a sane action a supervisor can take on failure here besides
> > killing the thread that made the syscall? If so, perhaps it's
> > worthwhile. Or maybe it's worth it "for completeness" and I'm just
> > being grumpy :)
>
> There is: seccomp_handle_addfd() puts the notification back in SENT on
> failure, so the supervisor can still reply.
But what would it reply with other than EMFILE as you note or SIGKILL,
both of which it would do inline with the ioctl?
Tycho
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH 1/4] seccomp: wake poll waiters after failed addfd send
2026-11-05 17:42 ` Tycho Andersen
@ 2026-11-05 17:53 ` Andrea Parri
0 siblings, 0 replies; 13+ messages in thread
From: Andrea Parri @ 2026-11-05 17:53 UTC (permalink / raw)
To: Tycho Andersen
Cc: Andrea Parri, Kees Cook, Andy Lutomirski, Will Drewry,
Shuah Khan, Christian Brauner, Sargun Dhillon, Hui Peng,
Bradley Morgan, Cong Wang, linux-kernel, linux-kselftest, bpf
On Thu, Nov 05, 2026 at 10:42:07AM -0700, Tycho Andersen wrote:
> But what would it reply with other than EMFILE as you note or SIGKILL,
> both of which it would do inline with the ioctl?
Right, those actions can be handled inline with the ioctl. That is why
I'm dropping the wakeup changes and helper from v2.
Thanks,
Andrea
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH 0/4] seccomp: fix user-notification state transitions
2026-11-04 10:37 [PATCH 0/4] seccomp: fix user-notification state transitions Andrea Parri
` (3 preceding siblings ...)
2026-11-04 10:37 ` [PATCH 4/4] seccomp: publish user notifications after copying them to userspace Andrea Parri
@ 2026-11-06 11:32 ` Andrea Parri
2026-11-06 11:36 ` Bradley Morgan
4 siblings, 1 reply; 13+ messages in thread
From: Andrea Parri @ 2026-11-06 11:32 UTC (permalink / raw)
To: Kees Cook
Cc: Tycho Andersen, Cong Wang, Andy Lutomirski, Will Drewry,
Shuah Khan, Christian Brauner, Sargun Dhillon, Hui Peng,
Bradley Morgan, linux-kernel, linux-kselftest, bpf
I've reconsidered my earlier plan to send patch 4 on its own, and I'm
dropping this series. Patches 1-3 go as discussed with Tycho. For
patch 4, it looks possible for a signal during a RECV whose copy then
fails to make the target return EINTR even with RESTART_BEFORE_RECV
alone. I don't plan to pursue this further. If anyone wants to pick it
up, the fix could be limited to RESTART_BEFORE_RECV filters, leaving the
others unchanged.
Thanks,
Andrea
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH 0/4] seccomp: fix user-notification state transitions
2026-11-06 11:32 ` [PATCH 0/4] seccomp: fix user-notification state transitions Andrea Parri
@ 2026-11-06 11:36 ` Bradley Morgan
0 siblings, 0 replies; 13+ messages in thread
From: Bradley Morgan @ 2026-11-06 11:36 UTC (permalink / raw)
To: Andrea Parri, Kees Cook
Cc: Tycho Andersen, Cong Wang, Andy Lutomirski, Will Drewry,
Shuah Khan, Christian Brauner, Sargun Dhillon, Hui Peng,
linux-kernel, linux-kselftest, bpf
On 6 November 2026 11:32:49 GMT, Andrea Parri <parri.andrea@gmail.com>
wrote:
>I've reconsidered my earlier plan to send patch 4 on its own, and I'm
>dropping this series. Patches 1-3 go as discussed with Tycho. For
>patch 4, it looks possible for a signal during a RECV whose copy then
>fails to make the target return EINTR even with RESTART_BEFORE_RECV
>alone. I don't plan to pursue this further. If anyone wants to pick it
>up, the fix could be limited to RESTART_BEFORE_RECV filters, leaving the
>others unchanged.
Your dropping the entire series?
>
>Thanks,
> Andrea
>
--- Thanks!
^ permalink raw reply [flat|nested] 13+ messages in thread
end of thread, other threads:[~2026-11-06 11:36 UTC | newest]
Thread overview: 13+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-04 10:37 [PATCH 0/4] seccomp: fix user-notification state transitions Andrea Parri
2026-11-04 10:37 ` [PATCH 1/4] seccomp: wake poll waiters after failed addfd send Andrea Parri
2026-11-04 16:19 ` Tycho Andersen
2026-11-05 15:37 ` Andrea Parri
2026-11-05 16:25 ` Tycho Andersen
2026-11-05 17:35 ` Andrea Parri
2026-11-05 17:42 ` Tycho Andersen
2026-11-05 17:53 ` Andrea Parri
2026-11-04 10:37 ` [PATCH 2/4] seccomp: wake poll waiters after interrupted " Andrea Parri
2026-11-04 10:37 ` [PATCH 3/4] seccomp: add a helper to move a notification to SENT Andrea Parri
2026-11-04 10:37 ` [PATCH 4/4] seccomp: publish user notifications after copying them to userspace Andrea Parri
2026-11-06 11:32 ` [PATCH 0/4] seccomp: fix user-notification state transitions Andrea Parri
2026-11-06 11:36 ` Bradley Morgan
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®