mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Cong Wang <xiyou.wangcong@gmail.com>
To: linux-kernel@vger.kernel.org
Cc: Andy Lutomirski <luto@amacapital.net>,
	Kees Cook <kees@kernel.org>, Will Drewry <wad@chromium.org>,
	Christian Brauner <brauner@kernel.org>
Subject: [RFC PATCH 3/9] seccomp: report the result of a continued system call
Date: Mon, 19 Oct 2026 17:11:18 -0700	[thread overview]
Message-ID: <20261020001124.974063-4-xiyou.wangcong@gmail.com> (raw)
In-Reply-To: <20261020001124.974063-1-xiyou.wangcong@gmail.com>

From: Cong Wang <cwang@multikernel.io>

A supervisor that continues a system call with
SECCOMP_USER_NOTIF_FLAG_CONTINUE never learns how the call turned out.
Add SECCOMP_USER_NOTIF_FLAG_RESULT: given together with CONTINUE, it
asks the kernel to queue a result record for the call on the listener,
carrying the call's return value.

The record is reported from task work, run as the task returns to user
space. That is one place for every system call: nothing has to be added
to the system calls themselves, and it works on every architecture.
The value is read from the task's registers after any ptrace syscall
exit stop, so it is what the task sees. The work is queued with
TWA_RESUME, so that the system call does not see a pending signal;
get_signal() still runs task work before it delivers a signal or
restarts the call.

Every reply that asked for a record gets exactly one. If a signal
interrupts the notification after the reply was sent, the record
reports the error the call fails with. If the task exits before
returning to user space, which happens after seccomp has released its
filters, the record is reported at release with -ESRCH. If the listener
is gone by then, the record is dropped.

SEND allocates the record, so that a failed allocation fails the ioctl,
which can be retried, and the system call never allocates.

The interface does not depend on the system call, but which calls are
supported is decided per call, and so far none is. Unsupported calls
are refused with EINVAL, which is also what older kernels return, so
the flag is easy to probe and can be extended without a new flag. The
next patch adds the fork family.

Signed-off-by: Cong Wang <cwang@multikernel.io>
---
 include/linux/seccomp_types.h |   6 ++
 include/uapi/linux/seccomp.h  |  10 +++
 kernel/seccomp.c              | 133 +++++++++++++++++++++++++++++++---
 3 files changed, 140 insertions(+), 9 deletions(-)

diff --git a/include/linux/seccomp_types.h b/include/linux/seccomp_types.h
index cf0a0355024f..158bac728344 100644
--- a/include/linux/seccomp_types.h
+++ b/include/linux/seccomp_types.h
@@ -7,6 +7,7 @@
 #ifdef CONFIG_SECCOMP
 
 struct seccomp_filter;
+struct seccomp_kresult;
 /**
  * struct seccomp - the state of a seccomp'ed process
  *
@@ -18,11 +19,16 @@ struct seccomp_filter;
  *
  *          @filter must only be accessed from the context of current as there
  *          is no read locking.
+ * @result: a result record requested by a listener for the system call in
+ *          progress, owned by current until it is reported.
+ * @result_work: reports @result when current returns to user space.
  */
 struct seccomp {
 	int mode;
 	atomic_t filter_count;
 	struct seccomp_filter *filter;
+	struct seccomp_kresult *result;
+	struct callback_head result_work;
 };
 
 #else
diff --git a/include/uapi/linux/seccomp.h b/include/uapi/linux/seccomp.h
index 67f7b2f45ab6..799c93c8eed2 100644
--- a/include/uapi/linux/seccomp.h
+++ b/include/uapi/linux/seccomp.h
@@ -136,6 +136,16 @@ struct seccomp_notif {
  */
 #define SECCOMP_USER_NOTIF_FLAG_CONTINUE (1UL << 0)
 
+/*
+ * SECCOMP_USER_NOTIF_FLAG_RESULT, together with
+ * SECCOMP_USER_NOTIF_FLAG_CONTINUE, asks for the outcome of the continued
+ * system call. It is reported once, as a notification with
+ * SECCOMP_NOTIF_FLAG_RESULT set and the same id, by the time the task
+ * returns to user space, and that notification is not answered. A system
+ * call for which results are not supported is refused with EINVAL.
+ */
+#define SECCOMP_USER_NOTIF_FLAG_RESULT (1UL << 1)
+
 struct seccomp_notif_resp {
 	__u64 id;
 	__s64 val;
diff --git a/kernel/seccomp.c b/kernel/seccomp.c
index 5641ac8b2f2e..246205e85d58 100644
--- a/kernel/seccomp.c
+++ b/kernel/seccomp.c
@@ -28,6 +28,7 @@
 #include <linux/slab.h>
 #include <linux/syscalls.h>
 #include <linux/sysctl.h>
+#include <linux/task_work.h>
 
 #include <asm/syscall.h>
 
@@ -98,6 +99,9 @@ struct seccomp_knotif {
 
 	/* outstanding addfd requests */
 	struct list_head addfd;
+
+	/* The result record the reply asked for, handed over with the reply */
+	struct seccomp_kresult *kres;
 };
 
 /**
@@ -137,6 +141,8 @@ struct seccomp_kaddfd {
  * struct seccomp_kresult - a one-way record of a continued system call's
  *			    outcome
  *
+ * @filter: The filter whose listener asked for this record. Only used while
+ *	    the record is pending in the task, which keeps its filters alive.
  * @id: The id of the notification whose reply asked for this record.
  * @task: The task that made the system call.
  * @child: The new task, for a successful call in the fork family.
@@ -147,6 +153,7 @@ struct seccomp_kaddfd {
  * @list: list_head for chaining onto notification->results.
  */
 struct seccomp_kresult {
+	struct seccomp_filter *filter;
 	u64 id;
 	struct pid *task;
 	struct pid *child;
@@ -549,6 +556,36 @@ static inline pid_t seccomp_can_sync_threads(void)
 	return 0;
 }
 
+static void seccomp_kresult_free(struct seccomp_kresult *kres)
+{
+	put_pid(kres->task);
+	put_pid(kres->child);
+	kfree(kres);
+}
+
+static void seccomp_queue_result(struct seccomp_filter *filter,
+				 struct seccomp_kresult *kres)
+{
+	lockdep_assert_held(&filter->notify_lock);
+
+	if (!filter->notif) {
+		seccomp_kresult_free(kres);
+		return;
+	}
+	list_add_tail(&kres->list, &filter->notif->results);
+	atomic_inc(&filter->notif->requests);
+	wake_up_poll(&filter->wqh, EPOLLIN | EPOLLRDNORM);
+}
+
+static void seccomp_report_result(struct seccomp_kresult *kres)
+{
+	struct seccomp_filter *filter = kres->filter;
+
+	mutex_lock(&filter->notify_lock);
+	seccomp_queue_result(filter, kres);
+	mutex_unlock(&filter->notify_lock);
+}
+
 static inline void seccomp_filter_free(struct seccomp_filter *filter)
 {
 	if (filter) {
@@ -602,6 +639,15 @@ void seccomp_filter_release(struct task_struct *tsk)
 	if (WARN_ON((tsk->flags & PF_EXITING) == 0))
 		return;
 
+	/* The filter is gone by the time task work runs; report now. */
+	if (tsk->seccomp.result) {
+		struct seccomp_kresult *kres = tsk->seccomp.result;
+
+		tsk->seccomp.result = NULL;
+		kres->val = -ESRCH;
+		seccomp_report_result(kres);
+	}
+
 	if (READ_ONCE(tsk->seccomp.filter) == NULL)
 		return;
 
@@ -1189,6 +1235,39 @@ static bool should_sleep_killable(struct seccomp_filter *match,
 	return match->wait_killable_recv && n->state >= SECCOMP_NOTIFY_SENT;
 }
 
+/* Report the system call's outcome on the way back to user space. */
+static void seccomp_result_work(struct callback_head *work)
+{
+	struct seccomp_kresult *kres = current->seccomp.result;
+	struct pt_regs *regs = current_pt_regs();
+	long err;
+
+	if (!kres)
+		return;
+	current->seccomp.result = NULL;
+
+	err = syscall_get_error(current, regs);
+	kres->val = err ? err : syscall_get_return_value(current, regs);
+	seccomp_report_result(kres);
+}
+
+static void seccomp_result_pending(struct seccomp_kresult *kres)
+{
+	current->seccomp.result = kres;
+	init_task_work(&current->seccomp.result_work, seccomp_result_work);
+	/*
+	 * TWA_RESUME rather than TWA_SIGNAL: the latter would make the system
+	 * call see a pending signal. The work still runs before a signal is
+	 * delivered or the call restarted, as get_signal() runs task work
+	 * first.
+	 */
+	if (task_work_add(current, &current->seccomp.result_work, TWA_RESUME)) {
+		current->seccomp.result = NULL;
+		kres->val = -ESRCH;
+		seccomp_report_result(kres);
+	}
+}
+
 static int seccomp_do_user_notification(int this_syscall,
 					struct seccomp_filter *match,
 					const struct seccomp_data *sd)
@@ -1276,6 +1355,13 @@ static int seccomp_do_user_notification(int this_syscall,
 out:
 	mutex_unlock(&match->notify_lock);
 
+	/*
+	 * Even if a signal beat the reply, the record is due: it then reports
+	 * the call as failed with the error set below.
+	 */
+	if (n.kres)
+		seccomp_result_pending(n.kres);
+
 	/* Userspace requests to continue the syscall. */
 	if (flags & SECCOMP_USER_NOTIF_FLAG_CONTINUE)
 		return 0;
@@ -1474,13 +1560,6 @@ static long seccomp_set_mode_strict(void)
 }
 
 #ifdef CONFIG_SECCOMP_FILTER
-static void seccomp_kresult_free(struct seccomp_kresult *kres)
-{
-	put_pid(kres->task);
-	put_pid(kres->child);
-	kfree(kres);
-}
-
 static void seccomp_notify_free(struct seccomp_filter *filter)
 {
 	struct seccomp_kresult *kres, *tmp;
@@ -1727,26 +1806,46 @@ static long seccomp_notify_recv(struct seccomp_filter *filter,
 	return ret;
 }
 
+/* Which system calls can report their result. */
+static bool seccomp_result_supported(const struct seccomp_data *sd)
+{
+	return false;
+}
+
 static long seccomp_notify_send(struct seccomp_filter *filter,
 				void __user *buf)
 {
 	struct seccomp_notif_resp resp = {};
+	struct seccomp_kresult *kres = NULL;
 	struct seccomp_knotif *knotif;
 	long ret;
 
 	if (copy_from_user(&resp, buf, sizeof(resp)))
 		return -EFAULT;
 
-	if (resp.flags & ~SECCOMP_USER_NOTIF_FLAG_CONTINUE)
+	if (resp.flags & ~(SECCOMP_USER_NOTIF_FLAG_CONTINUE |
+			   SECCOMP_USER_NOTIF_FLAG_RESULT))
 		return -EINVAL;
 
 	if ((resp.flags & SECCOMP_USER_NOTIF_FLAG_CONTINUE) &&
 	    (resp.error || resp.val))
 		return -EINVAL;
 
+	if (resp.flags & SECCOMP_USER_NOTIF_FLAG_RESULT) {
+		if (!(resp.flags & SECCOMP_USER_NOTIF_FLAG_CONTINUE))
+			return -EINVAL;
+		/*
+		 * Allocate here, where failing is harmless, so that the
+		 * system call never has to.
+		 */
+		kres = kzalloc_obj(*kres, GFP_KERNEL_ACCOUNT);
+		if (!kres)
+			return -ENOMEM;
+	}
+
 	ret = mutex_lock_interruptible(&filter->notify_lock);
 	if (ret < 0)
-		return ret;
+		goto out_free;
 
 	knotif = find_notification(filter, resp.id);
 	if (!knotif) {
@@ -1760,6 +1859,20 @@ static long seccomp_notify_send(struct seccomp_filter *filter,
 		goto out;
 	}
 
+	if (kres) {
+		if (!seccomp_result_supported(knotif->data)) {
+			ret = -EINVAL;
+			goto out;
+		}
+		kres->filter = filter;
+		kres->id = knotif->id;
+		kres->task = get_task_pid(knotif->task, PIDTYPE_PID);
+		kres->nr = knotif->data->nr;
+		kres->arch = knotif->data->arch;
+		knotif->kres = kres;
+		kres = NULL;
+	}
+
 	ret = 0;
 	knotif->state = SECCOMP_NOTIFY_REPLIED;
 	knotif->error = resp.error;
@@ -1771,6 +1884,8 @@ static long seccomp_notify_send(struct seccomp_filter *filter,
 		complete(&knotif->ready);
 out:
 	mutex_unlock(&filter->notify_lock);
+out_free:
+	kfree(kres);
 	return ret;
 }
 
-- 
2.43.0


  parent reply	other threads:[~2026-10-20  0:11 UTC|newest]

Thread overview: 10+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-20  0:11 [RFC PATCH 0/9] seccomp: report the result of continued system calls Cong Wang
2026-10-20  0:11 ` [RFC PATCH 1/9] seccomp: add result records to the notification listener Cong Wang
2026-10-20  0:11 ` [RFC PATCH 2/9] seccomp: add __NR_seccomp_* aliases for the fork family Cong Wang
2026-10-20  0:11 ` Cong Wang [this message]
2026-10-20  0:11 ` [RFC PATCH 4/9] seccomp: report the result of a continued fork Cong Wang
2026-10-20  0:11 ` [RFC PATCH 5/9] seccomp: factor out the user notification queue and wait Cong Wang
2026-10-20  0:11 ` [RFC PATCH 6/9] seccomp: add SECCOMP_USER_NOTIF_FLAG_HOLD Cong Wang
2026-10-20  0:11 ` [RFC PATCH 7/9] selftests/seccomp: test fork result records Cong Wang
2026-10-20  0:11 ` [RFC PATCH 8/9] selftests/seccomp: test held fork results Cong Wang
2026-10-20  0:11 ` [RFC PATCH 9/9] Documentation: seccomp: describe system call result records Cong Wang

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261020001124.974063-4-xiyou.wangcong@gmail.com \
    --to=xiyou.wangcong@gmail.com \
    --cc=brauner@kernel.org \
    --cc=kees@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=luto@amacapital.net \
    --cc=wad@chromium.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
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®