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 4/9] seccomp: report the result of a continued fork
Date: Mon, 19 Oct 2026 17:11:19 -0700	[thread overview]
Message-ID: <20261020001124.974063-5-xiyou.wangcong@gmail.com> (raw)
In-Reply-To: <20261020001124.974063-1-xiyou.wangcong@gmail.com>

From: Cong Wang <cwang@multikernel.io>

Support SECCOMP_USER_NOTIF_FLAG_RESULT for fork, vfork, clone and
clone3. A successful fork is reported with the new task's pid in the
listener's pid namespace and its pidfs inode number, and with
SECCOMP_NOTIF_FLAG_THREAD if the new task is a thread.

With this a supervisor learns about every task created below it, even
one that is killed before it makes a single notified system call, and
it learns about forks that the kernel rejected after CONTINUE. Once it
knows the new task it can open a pidfd for it by inode number, without
privileges and without racing pid reuse, and the pidfd reports the
task's exit however it happens. Today the only complete alternative for
an unprivileged supervisor is ptrace fork events.

Reporting on the way back to user space is too late for a successful
fork: by then the child may be running, may have triggered
notifications, or may be gone with its pid reused. So kernel_clone()
reports the child just before wake_up_new_task(), holding a reference
to its struct pid. Records are delivered before notifications, so the
supervisor always hears of a new task before it can hear from it. The
forking task does not wait for the supervisor. A failed fork, wherever
it fails, is reported on exit like any other system call, so no other
fork path needs a hook. The new task does not inherit the pending
record.

The fork family is recognised through the __NR_seccomp_* aliases:
natively on every arch that defines SECCOMP_ARCH_NATIVE, and for compat
tasks where the arch provides the compat aliases, so far ia32. x32 is
refused, since its numbers carry __X32_SYSCALL_BIT and never match.

Signed-off-by: Cong Wang <cwang@multikernel.io>
---
 include/linux/seccomp.h      |  4 +++
 include/uapi/linux/seccomp.h |  4 +++
 kernel/fork.c                |  3 +++
 kernel/seccomp.c             | 48 +++++++++++++++++++++++++++++++++++-
 4 files changed, 58 insertions(+), 1 deletion(-)

diff --git a/include/linux/seccomp.h b/include/linux/seccomp.h
index fcb3eb9825e5..91d6edaf5eb7 100644
--- a/include/linux/seccomp.h
+++ b/include/linux/seccomp.h
@@ -75,6 +75,7 @@ static inline int seccomp_mode(struct seccomp *s)
 #ifdef CONFIG_SECCOMP_FILTER
 extern void seccomp_filter_release(struct task_struct *tsk);
 extern void get_seccomp_filter(struct task_struct *tsk);
+extern void seccomp_report_fork(struct task_struct *child, pid_t nr);
 #else  /* CONFIG_SECCOMP_FILTER */
 static inline void seccomp_filter_release(struct task_struct *tsk)
 {
@@ -84,6 +85,9 @@ static inline void get_seccomp_filter(struct task_struct *tsk)
 {
 	return;
 }
+static inline void seccomp_report_fork(struct task_struct *child, pid_t nr)
+{
+}
 #endif /* CONFIG_SECCOMP_FILTER */
 
 #if defined(CONFIG_SECCOMP_FILTER) && defined(CONFIG_CHECKPOINT_RESTORE)
diff --git a/include/uapi/linux/seccomp.h b/include/uapi/linux/seccomp.h
index 799c93c8eed2..223577fea182 100644
--- a/include/uapi/linux/seccomp.h
+++ b/include/uapi/linux/seccomp.h
@@ -143,6 +143,10 @@ struct seccomp_notif {
  * 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.
+ *
+ * So far fork, vfork, clone and clone3 are supported. For these a new task
+ * is reported before it runs, so the record reaches the listener before
+ * any notification the new task can trigger.
  */
 #define SECCOMP_USER_NOTIF_FLAG_RESULT (1UL << 1)
 
diff --git a/kernel/fork.c b/kernel/fork.c
index 33760ece3bb3..637eec6a6cb3 100644
--- a/kernel/fork.c
+++ b/kernel/fork.c
@@ -1815,6 +1815,8 @@ static void copy_seccomp(struct task_struct *p)
 	/* Ref-count the new filter user, and assign it. */
 	get_seccomp_filter(current);
 	p->seccomp = current->seccomp;
+	/* The result of this fork is reported by the parent only. */
+	p->seccomp.result = NULL;
 
 	/*
 	 * Explicitly enable no_new_privs here in case it got set
@@ -2809,6 +2811,7 @@ pid_t kernel_clone(struct kernel_clone_args *args)
 		task_unlock(p);
 	}
 
+	seccomp_report_fork(p, nr);
 	wake_up_new_task(p);
 
 	/* forking complete and child started to run, tell ptracer */
diff --git a/kernel/seccomp.c b/kernel/seccomp.c
index 246205e85d58..336a7c28e1d6 100644
--- a/kernel/seccomp.c
+++ b/kernel/seccomp.c
@@ -1371,6 +1371,30 @@ static int seccomp_do_user_notification(int this_syscall,
 	return -1;
 }
 
+/**
+ * seccomp_report_fork - report a new task to the listener that asked for it
+ * @child: the new task, not woken up yet
+ * @nr: its pid, the value the fork returns
+ *
+ * Exit is too late for a successful fork: the child would already run, and
+ * might be gone with its pid reused. Report it before it is woken instead.
+ * A failed fork is reported on exit, like any other system call.
+ */
+void seccomp_report_fork(struct task_struct *child, pid_t nr)
+{
+	struct seccomp_kresult *kres = current->seccomp.result;
+
+	if (likely(!kres))
+		return;
+	current->seccomp.result = NULL;
+
+	kres->val = nr;
+	kres->child = get_task_pid(child, PIDTYPE_PID);
+	if (!thread_group_leader(child))
+		kres->flags |= SECCOMP_NOTIF_FLAG_THREAD;
+	seccomp_report_result(kres);
+}
+
 static bool __seccomp_filter(int this_syscall, const bool recheck_after_trace)
 {
 	u32 filter_ret, action;
@@ -1806,10 +1830,32 @@ static long seccomp_notify_recv(struct seccomp_filter *filter,
 	return ret;
 }
 
+static bool seccomp_is_fork_syscall(const struct seccomp_data *sd)
+{
+	/* The aliases use -1 for a syscall the arch lacks. */
+	if (sd->nr < 0)
+		return false;
+#if defined(SECCOMP_ARCH_COMPAT) && defined(__NR_seccomp_clone_32)
+	if (sd->arch == SECCOMP_ARCH_COMPAT)
+		return sd->nr == __NR_seccomp_clone_32 ||
+		       sd->nr == __NR_seccomp_clone3_32 ||
+		       sd->nr == __NR_seccomp_fork_32 ||
+		       sd->nr == __NR_seccomp_vfork_32;
+#endif
+#if defined(SECCOMP_ARCH_NATIVE) && defined(__NR_seccomp_clone)
+	if (sd->arch == SECCOMP_ARCH_NATIVE)
+		return sd->nr == __NR_seccomp_clone ||
+		       sd->nr == __NR_seccomp_clone3 ||
+		       sd->nr == __NR_seccomp_fork ||
+		       sd->nr == __NR_seccomp_vfork;
+#endif
+	return false;
+}
+
 /* Which system calls can report their result. */
 static bool seccomp_result_supported(const struct seccomp_data *sd)
 {
-	return false;
+	return seccomp_is_fork_syscall(sd);
 }
 
 static long seccomp_notify_send(struct seccomp_filter *filter,
-- 
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 ` Cong Wang [this message]
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-5-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®