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 6/9] seccomp: add SECCOMP_USER_NOTIF_FLAG_HOLD
Date: Mon, 19 Oct 2026 17:11:21 -0700	[thread overview]
Message-ID: <20261020001124.974063-7-xiyou.wangcong@gmail.com> (raw)
In-Reply-To: <20261020001124.974063-1-xiyou.wangcong@gmail.com>

From: Cong Wang <cwang@multikernel.io>

A result record is one-way: the task does not wait for the supervisor to
read it. That is enough for accounting, but not when the supervisor must
act on a result before the task builds on it. For a fork, the new task
may already be running by the time the supervisor reads the record, and
where tasks share memory one could rewrite another's arguments without
making a system call; today such a supervisor needs ptrace fork events.

With SECCOMP_USER_NOTIF_FLAG_HOLD added to CONTINUE | RESULT, the result
is reported as an ordinary notification instead, with
SECCOMP_NOTIF_FLAG_RESULT | SECCOMP_NOTIF_FLAG_HELD, and the task waits
at the point where the record would be queued until the supervisor
answers it with CONTINUE. That point is the same for every system call,
the task work on the way back to user space, so HOLD needs nothing of
its own and is supported wherever RESULT is. For a successful fork it
is just before wake_up_new_task(), so the new task has provably not run
when the supervisor learns of it either. For vfork the parent then
wakes the child and waits for it as usual.

The held notification reuses the user notification machinery: it has
the id of the call's notification, ID_VALID works on it, and a dying
listener releases it. It accepts only a plain CONTINUE and no ADDFD,
since the call has completed and there is nothing to decide.

The task sleeps killable, because the call has completed and cannot be
restarted, and freezable, because the supervisor may be frozen before it
answers. A held fork child is in TASK_NEW and cannot be frozen, so a
held fork still holds off the freezer until it is answered. If the task
is killed before the supervisor received the held notification, the
result is queued as a one-way record instead, so the supervisor still
learns of it. A result reported because the task is exiting is never
held.

Signed-off-by: Cong Wang <cwang@multikernel.io>
---
 include/uapi/linux/seccomp.h | 13 ++++++
 kernel/seccomp.c             | 86 +++++++++++++++++++++++++++++++++---
 2 files changed, 92 insertions(+), 7 deletions(-)

diff --git a/include/uapi/linux/seccomp.h b/include/uapi/linux/seccomp.h
index 223577fea182..aa3902be9e72 100644
--- a/include/uapi/linux/seccomp.h
+++ b/include/uapi/linux/seccomp.h
@@ -96,6 +96,7 @@ struct seccomp_notif_result {
 /* Valid flags for struct seccomp_notif */
 #define SECCOMP_NOTIF_FLAG_RESULT	(1UL << 0) /* @result is valid, not @data */
 #define SECCOMP_NOTIF_FLAG_THREAD	(1UL << 1) /* the new task is a thread */
+#define SECCOMP_NOTIF_FLAG_HELD		(1UL << 2) /* answer to release the task */
 
 struct seccomp_notif {
 	__u64 id;
@@ -150,6 +151,18 @@ struct seccomp_notif {
  */
 #define SECCOMP_USER_NOTIF_FLAG_RESULT (1UL << 1)
 
+/*
+ * SECCOMP_USER_NOTIF_FLAG_HOLD, together with SECCOMP_USER_NOTIF_FLAG_RESULT,
+ * also keeps the task from returning to user space until the listener has
+ * seen the result. The result is then reported with SECCOMP_NOTIF_FLAG_HELD
+ * set, and the task waits until that notification is answered with
+ * SECCOMP_USER_NOTIF_FLAG_CONTINUE (and nothing else). For the fork family,
+ * a new task does not run until then either. If the task is killed before
+ * the listener received the notification, it is reported without
+ * SECCOMP_NOTIF_FLAG_HELD instead. HOLD is supported wherever RESULT is.
+ */
+#define SECCOMP_USER_NOTIF_FLAG_HOLD (1UL << 2)
+
 struct seccomp_notif_resp {
 	__u64 id;
 	__s64 val;
diff --git a/kernel/seccomp.c b/kernel/seccomp.c
index 793efcf0e626..1f33449ce571 100644
--- a/kernel/seccomp.c
+++ b/kernel/seccomp.c
@@ -102,6 +102,9 @@ struct seccomp_knotif {
 
 	/* The result record the reply asked for, handed over with the reply */
 	struct seccomp_kresult *kres;
+
+	/* The result this notification reports, holding the task until answered */
+	struct seccomp_kresult *held;
 };
 
 /**
@@ -150,6 +153,7 @@ struct seccomp_kaddfd {
  * @arch: The system call convention.
  * @val: The system call's return value.
  * @flags: SECCOMP_NOTIF_FLAG_* flags to report.
+ * @hold: Whether the listener asked to hold the task until it answers.
  * @list: list_head for chaining onto notification->results.
  */
 struct seccomp_kresult {
@@ -161,6 +165,7 @@ struct seccomp_kresult {
 	u32 arch;
 	long val;
 	u32 flags;
+	bool hold;
 	struct list_head list;
 };
 
@@ -1232,6 +1237,9 @@ static void seccomp_handle_addfd(struct seccomp_kaddfd *addfd, struct seccomp_kn
 static bool should_sleep_killable(struct seccomp_filter *match,
 				  struct seccomp_knotif *n)
 {
+	/* A held call has completed and cannot be restarted. */
+	if (n->held)
+		return true;
 	return match->wait_killable_recv && n->state >= SECCOMP_NOTIFY_SENT;
 }
 
@@ -1265,7 +1273,11 @@ static int seccomp_notify_wait(struct seccomp_filter *match,
 		bool wait_killable = should_sleep_killable(match, n);
 
 		mutex_unlock(&match->notify_lock);
-		if (wait_killable)
+		/* The supervisor may be frozen before it can answer. */
+		if (n->held)
+			err = wait_for_completion_state(&n->ready,
+							TASK_KILLABLE | TASK_FREEZABLE);
+		else if (wait_killable)
 			err = wait_for_completion_killable(&n->ready);
 		else
 			err = wait_for_completion_interruptible(&n->ready);
@@ -1320,6 +1332,40 @@ static void seccomp_notify_dequeue(struct seccomp_filter *match,
 		list_del(&n->list);
 }
 
+static void seccomp_hold_result(struct seccomp_filter *filter,
+				struct seccomp_kresult *kres)
+{
+	struct seccomp_knotif n = {};
+
+	n.task = current;
+	n.id = kres->id;
+	n.held = kres;
+	seccomp_notify_queue(filter, &n);
+
+	if (seccomp_notify_wait(filter, &n) &&
+	    n.state == SECCOMP_NOTIFY_INIT) {
+		/* Killed unseen: the listener must still learn the result. */
+		seccomp_queue_result(filter, kres);
+		kres = NULL;
+	}
+
+	seccomp_notify_dequeue(filter, &n);
+	if (kres)
+		seccomp_kresult_free(kres);
+}
+
+static void seccomp_deliver_result(struct seccomp_kresult *kres)
+{
+	struct seccomp_filter *filter = kres->filter;
+
+	mutex_lock(&filter->notify_lock);
+	if (kres->hold && filter->notif)
+		seccomp_hold_result(filter, kres);
+	else
+		seccomp_queue_result(filter, kres);
+	mutex_unlock(&filter->notify_lock);
+}
+
 /* Report the system call's outcome on the way back to user space. */
 static void seccomp_result_work(struct callback_head *work)
 {
@@ -1333,7 +1379,7 @@ static void seccomp_result_work(struct callback_head *work)
 
 	err = syscall_get_error(current, regs);
 	kres->val = err ? err : syscall_get_return_value(current, regs);
-	seccomp_report_result(kres);
+	seccomp_deliver_result(kres);
 }
 
 static void seccomp_result_pending(struct seccomp_kresult *kres)
@@ -1423,7 +1469,7 @@ void seccomp_report_fork(struct task_struct *child, pid_t nr)
 	kres->child = get_task_pid(child, PIDTYPE_PID);
 	if (!thread_group_leader(child))
 		kres->flags |= SECCOMP_NOTIF_FLAG_THREAD;
-	seccomp_report_result(kres);
+	seccomp_deliver_result(kres);
 }
 
 static bool __seccomp_filter(int this_syscall, const bool recheck_after_trace)
@@ -1823,9 +1869,18 @@ static long seccomp_notify_recv(struct seccomp_filter *filter,
 		goto out;
 	}
 
-	unotif.id = knotif->id;
-	unotif.pid = task_pid_vnr(knotif->task);
-	unotif.data = *(knotif->data);
+	if (knotif->held) {
+		ret = seccomp_fill_result(&unotif, knotif->held);
+		if (ret < 0) {
+			atomic_inc(&filter->notif->requests);
+			goto out;
+		}
+		unotif.flags |= SECCOMP_NOTIF_FLAG_HELD;
+	} else {
+		unotif.id = knotif->id;
+		unotif.pid = task_pid_vnr(knotif->task);
+		unotif.data = *(knotif->data);
+	}
 
 	knotif->state = SECCOMP_NOTIFY_SENT;
 	wake_up_poll(&filter->wqh, EPOLLOUT | EPOLLWRNORM);
@@ -1901,7 +1956,12 @@ static long seccomp_notify_send(struct seccomp_filter *filter,
 		return -EFAULT;
 
 	if (resp.flags & ~(SECCOMP_USER_NOTIF_FLAG_CONTINUE |
-			   SECCOMP_USER_NOTIF_FLAG_RESULT))
+			   SECCOMP_USER_NOTIF_FLAG_RESULT |
+			   SECCOMP_USER_NOTIF_FLAG_HOLD))
+		return -EINVAL;
+
+	if ((resp.flags & SECCOMP_USER_NOTIF_FLAG_HOLD) &&
+	    !(resp.flags & SECCOMP_USER_NOTIF_FLAG_RESULT))
 		return -EINVAL;
 
 	if ((resp.flags & SECCOMP_USER_NOTIF_FLAG_CONTINUE) &&
@@ -1936,6 +1996,11 @@ static long seccomp_notify_send(struct seccomp_filter *filter,
 		goto out;
 	}
 
+	if (knotif->held && resp.flags != SECCOMP_USER_NOTIF_FLAG_CONTINUE) {
+		ret = -EINVAL;
+		goto out;
+	}
+
 	if (kres) {
 		if (!seccomp_result_supported(knotif->data)) {
 			ret = -EINVAL;
@@ -1946,6 +2011,7 @@ static long seccomp_notify_send(struct seccomp_filter *filter,
 		kres->task = get_task_pid(knotif->task, PIDTYPE_PID);
 		kres->nr = knotif->data->nr;
 		kres->arch = knotif->data->arch;
+		kres->hold = resp.flags & SECCOMP_USER_NOTIF_FLAG_HOLD;
 		knotif->kres = kres;
 		kres = NULL;
 	}
@@ -2064,6 +2130,12 @@ static long seccomp_notify_addfd(struct seccomp_filter *filter,
 		goto out_unlock;
 	}
 
+	/* A held call has completed; there is nothing to add to. */
+	if (knotif->held) {
+		ret = -EINVAL;
+		goto out_unlock;
+	}
+
 	if (addfd.flags & SECCOMP_ADDFD_FLAG_SEND) {
 		/*
 		 * Disallow queuing an atomic addfd + send reply while there are
-- 
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 ` [RFC PATCH 3/9] seccomp: report the result of a continued system call Cong Wang
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 ` Cong Wang [this message]
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-7-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®