From: Christian Brauner <brauner@kernel.org>
To: Oleg Nesterov <oleg@redhat.com>, Jann Horn <jannh@google.com>
Cc: Linus Torvalds <torvalds@linux-foundation.org>,
Ingo Molnar <mingo@redhat.com>,
Peter Zijlstra <peterz@infradead.org>,
linux-kernel@vger.kernel.org, linux-fsdevel@vger.kernel.org,
Christian Brauner <brauner@kernel.org>
Subject: [PATCH v4 2/4] pidfd: add CLONE_PIDFD_AUTOKILL
Date: Mon, 23 Feb 2026 11:44:59 +0100 [thread overview]
Message-ID: <20260223-work-pidfs-autoreap-v4-2-e393c08c09d1@kernel.org> (raw)
In-Reply-To: <20260223-work-pidfs-autoreap-v4-0-e393c08c09d1@kernel.org>
Add a new clone3() flag CLONE_PIDFD_AUTOKILL that ties a child's
lifetime to the pidfd returned from clone3(). When the last reference to
the struct file created by clone3() is closed the kernel sends SIGKILL
to the child. A pidfd obtained via pidfd_open() for the same process
does not keep the child alive and does not trigger autokill - only the
specific struct file from clone3() has this property.
This is useful for container runtimes, service managers, and sandboxed
subprocess execution - any scenario where the child must die if the
parent crashes or abandons the pidfd.
CLONE_PIDFD_AUTOKILL requires both CLONE_PIDFD (the whole point is tying
lifetime to the pidfd file) and CLONE_AUTOREAP (a killed child with no
one to reap it would become a zombie). CLONE_THREAD is rejected because
autokill targets a process not a thread.
The clone3 pidfd is identified by the PIDFD_AUTOKILL file flag set on
the struct file at clone3() time. The pidfs .release handler checks this
flag and sends SIGKILL via do_send_sig_info(SIGKILL, SEND_SIG_PRIV, ...)
only when it is set. Files from pidfd_open() or open_by_handle_at() are
distinct struct files that do not carry this flag. dup()/fork() share the
same struct file so they extend the child's lifetime until the last
reference drops.
CLONE_PIDFD_AUTOKILL automatically sets no_new_privs on the child
process. This ensures the child cannot escalate privileges beyond the
parent's credential level via setuid/setgid exec. Because the child can
never outprivilege the parent the autokill SIGKILL is always within the
parent's natural authority.
This is a deliberate departure from the pdeath_signal model which is
reset during secureexec and commit_creds() rendering it useless for
container runtimes that need to deprivilege themselves. Setting
no_new_privs on the child avoids the need for any such magical resets:
the kill-on-close contract is absolute.
The no_new_privs restriction only affects the child. The parent retains
its full privileges and can continue to execute setuid binaries.
Signed-off-by: Christian Brauner <brauner@kernel.org>
---
fs/pidfs.c | 38 ++++++++++++++++++++++++++++++++------
include/uapi/linux/pidfd.h | 1 +
include/uapi/linux/sched.h | 1 +
kernel/fork.c | 22 +++++++++++++++++++---
4 files changed, 53 insertions(+), 9 deletions(-)
diff --git a/fs/pidfs.c b/fs/pidfs.c
index 318253344b5c..a8d1bca0395d 100644
--- a/fs/pidfs.c
+++ b/fs/pidfs.c
@@ -8,6 +8,8 @@
#include <linux/mount.h>
#include <linux/pid.h>
#include <linux/pidfs.h>
+#include <linux/sched/signal.h>
+#include <linux/signal.h>
#include <linux/pid_namespace.h>
#include <linux/poll.h>
#include <linux/proc_fs.h>
@@ -637,7 +639,28 @@ static long pidfd_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
return open_namespace(ns_common);
}
+static int pidfs_file_release(struct inode *inode, struct file *file)
+{
+ struct pid *pid = inode->i_private;
+ struct task_struct *task;
+
+ if (!(file->f_flags & PIDFD_AUTOKILL))
+ return 0;
+
+ guard(rcu)();
+ task = pid_task(pid, PIDTYPE_TGID);
+ if (!task)
+ return 0;
+
+ /* Not available for kthreads or user workers for now. */
+ if (WARN_ON_ONCE(task->flags & (PF_KTHREAD | PF_USER_WORKER)))
+ return 0;
+ do_send_sig_info(SIGKILL, SEND_SIG_PRIV, task, PIDTYPE_TGID);
+ return 0;
+}
+
static const struct file_operations pidfs_file_operations = {
+ .release = pidfs_file_release,
.poll = pidfd_poll,
#ifdef CONFIG_PROC_FS
.show_fdinfo = pidfd_show_fdinfo,
@@ -1093,11 +1116,11 @@ struct file *pidfs_alloc_file(struct pid *pid, unsigned int flags)
int ret;
/*
- * Ensure that PIDFD_STALE can be passed as a flag without
- * overloading other uapi pidfd flags.
+ * Ensure that internal pidfd flags don't overlap with each
+ * other or with uapi pidfd flags.
*/
- BUILD_BUG_ON(PIDFD_STALE == PIDFD_THREAD);
- BUILD_BUG_ON(PIDFD_STALE == PIDFD_NONBLOCK);
+ BUILD_BUG_ON(hweight32(PIDFD_THREAD | PIDFD_NONBLOCK |
+ PIDFD_STALE | PIDFD_AUTOKILL) != 4);
ret = path_from_stashed(&pid->stashed, pidfs_mnt, get_pid(pid), &path);
if (ret < 0)
@@ -1108,9 +1131,12 @@ struct file *pidfs_alloc_file(struct pid *pid, unsigned int flags)
flags &= ~PIDFD_STALE;
flags |= O_RDWR;
pidfd_file = dentry_open(&path, flags, current_cred());
- /* Raise PIDFD_THREAD explicitly as do_dentry_open() strips it. */
+ /*
+ * Raise PIDFD_THREAD and PIDFD_AUTOKILL explicitly as
+ * do_dentry_open() strips O_EXCL and O_TRUNC.
+ */
if (!IS_ERR(pidfd_file))
- pidfd_file->f_flags |= (flags & PIDFD_THREAD);
+ pidfd_file->f_flags |= (flags & (PIDFD_THREAD | PIDFD_AUTOKILL));
return pidfd_file;
}
diff --git a/include/uapi/linux/pidfd.h b/include/uapi/linux/pidfd.h
index ea9a6811fc76..9281956a9f32 100644
--- a/include/uapi/linux/pidfd.h
+++ b/include/uapi/linux/pidfd.h
@@ -13,6 +13,7 @@
#ifdef __KERNEL__
#include <linux/sched.h>
#define PIDFD_STALE CLONE_PIDFD
+#define PIDFD_AUTOKILL O_TRUNC
#endif
/* Flags for pidfd_send_signal(). */
diff --git a/include/uapi/linux/sched.h b/include/uapi/linux/sched.h
index 8a22ea640817..b1aea8a86e2f 100644
--- a/include/uapi/linux/sched.h
+++ b/include/uapi/linux/sched.h
@@ -37,6 +37,7 @@
#define CLONE_CLEAR_SIGHAND 0x100000000ULL /* Clear any signal handler and reset to SIG_DFL. */
#define CLONE_INTO_CGROUP 0x200000000ULL /* Clone into a specific cgroup given the right permissions. */
#define CLONE_AUTOREAP 0x400000000ULL /* Auto-reap child on exit. */
+#define CLONE_PIDFD_AUTOKILL 0x800000000ULL /* Kill child when clone pidfd closes. */
/*
* cloning flags intersect with CSIGNAL so can be used with unshare and clone3
diff --git a/kernel/fork.c b/kernel/fork.c
index 0dedf2999f0c..778aed24e01d 100644
--- a/kernel/fork.c
+++ b/kernel/fork.c
@@ -2037,6 +2037,15 @@ __latent_entropy struct task_struct *copy_process(
return ERR_PTR(-EINVAL);
}
+ if (clone_flags & CLONE_PIDFD_AUTOKILL) {
+ if (!(clone_flags & CLONE_PIDFD))
+ return ERR_PTR(-EINVAL);
+ if (!(clone_flags & CLONE_AUTOREAP))
+ return ERR_PTR(-EINVAL);
+ if (clone_flags & CLONE_THREAD)
+ return ERR_PTR(-EINVAL);
+ }
+
/*
* Force any signals received before this point to be delivered
* before the fork happens. Collect up signals sent to multiple
@@ -2259,13 +2268,20 @@ __latent_entropy struct task_struct *copy_process(
* if the fd table isn't shared).
*/
if (clone_flags & CLONE_PIDFD) {
- int flags = (clone_flags & CLONE_THREAD) ? PIDFD_THREAD : 0;
+ unsigned flags = PIDFD_STALE;
+
+ if (clone_flags & CLONE_THREAD)
+ flags |= PIDFD_THREAD;
+ if (clone_flags & CLONE_PIDFD_AUTOKILL) {
+ task_set_no_new_privs(p);
+ flags |= PIDFD_AUTOKILL;
+ }
/*
* Note that no task has been attached to @pid yet indicate
* that via CLONE_PIDFD.
*/
- retval = pidfd_prepare(pid, flags | PIDFD_STALE, &pidfile);
+ retval = pidfd_prepare(pid, flags, &pidfile);
if (retval < 0)
goto bad_fork_free_pid;
pidfd = retval;
@@ -2909,7 +2925,7 @@ static bool clone3_args_valid(struct kernel_clone_args *kargs)
/* Verify that no unknown flags are passed along. */
if (kargs->flags &
~(CLONE_LEGACY_FLAGS | CLONE_CLEAR_SIGHAND | CLONE_INTO_CGROUP |
- CLONE_AUTOREAP))
+ CLONE_AUTOREAP | CLONE_PIDFD_AUTOKILL))
return false;
/*
--
2.47.3
next prev parent reply other threads:[~2026-02-23 10:45 UTC|newest]
Thread overview: 14+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-02-23 10:44 [PATCH v4 0/4] pidfd: add CLONE_AUTOREAP and CLONE_PIDFD_AUTOKILL Christian Brauner
2026-02-23 10:44 ` [PATCH v4 1/4] clone: add CLONE_AUTOREAP Christian Brauner
2026-02-23 10:44 ` Christian Brauner [this message]
2026-02-23 15:47 ` [PATCH v4 2/4] pidfd: add CLONE_PIDFD_AUTOKILL Oleg Nesterov
2026-02-23 15:51 ` Oleg Nesterov
2026-02-23 17:05 ` pidfd && O_RDWR Oleg Nesterov
2026-02-23 18:14 ` David Laight
2026-02-23 19:21 ` Oleg Nesterov
2026-02-23 21:39 ` Christian Brauner
2026-02-24 9:43 ` David Laight
2026-02-24 10:17 ` Oleg Nesterov
2026-02-24 16:47 ` Christian Brauner
2026-02-23 10:45 ` [PATCH v4 3/4] selftests/pidfd: add CLONE_AUTOREAP tests Christian Brauner
2026-02-23 10:45 ` [PATCH v4 4/4] selftests/pidfd: add CLONE_PIDFD_AUTOKILL tests Christian Brauner
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=20260223-work-pidfs-autoreap-v4-2-e393c08c09d1@kernel.org \
--to=brauner@kernel.org \
--cc=jannh@google.com \
--cc=linux-fsdevel@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=mingo@redhat.com \
--cc=oleg@redhat.com \
--cc=peterz@infradead.org \
--cc=torvalds@linux-foundation.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
Powered by JetHome