[RFC PATCH 03/24] pidfs: add taskless future pidfd inodes

Li Chen me at linux.beauty
Thu Jul 16 14:31:29 UTC 2026


Teach pidfs inodes to represent either a struct pid or a typed future
object. Use the ordinary pidfs file operations for both kinds and
resolve a future object through its callback.

Allocate a stable inode identity without allocating a task or struct
pid. Ordinary pidfd operations return -ESRCH until the future producer
publishes a process, while inode-only GETVERSION remains available.

Assisted-by: Codex:gpt-5.6-sol
Signed-off-by: Li Chen <me at linux.beauty>
---
 fs/pidfs.c            | 254 ++++++++++++++++++++++++++++++++++++++----
 include/linux/pid.h   |  10 ++
 include/linux/pidfs.h |  21 ++++
 3 files changed, 262 insertions(+), 23 deletions(-)

diff --git a/fs/pidfs.c b/fs/pidfs.c
index c55f46c32801d..ebd8cc463811b 100644
--- a/fs/pidfs.c
+++ b/fs/pidfs.c
@@ -25,6 +25,7 @@
 #include <net/net_namespace.h>
 #include <linux/coredump.h>
 #include <linux/rhashtable.h>
+#include <linux/security.h>
 #include <linux/llist.h>
 #include <linux/xattr.h>
 #include <linux/cookie.h>
@@ -35,6 +36,8 @@
 #define PIDFS_PID_DEAD ERR_PTR(-ESRCH)
 
 static struct kmem_cache *pidfs_attr_cachep __ro_after_init;
+static const struct file_operations pidfs_file_operations;
+static struct vfsmount *pidfs_mnt __ro_after_init;
 
 static struct path pidfs_root_path = {};
 
@@ -106,6 +109,64 @@ struct pidfs_attr {
 	};
 };
 
+struct pidfs_future_file {
+	struct pidfs_node node;
+	void *data;
+	const struct pidfs_future_file_ops *ops;
+	struct pidfs_attr *attr;
+	u64 ino;
+};
+
+static struct pidfs_node *pidfs_inode_node(const struct inode *inode)
+{
+	if (!pidfs_mnt || inode->i_sb != pidfs_mnt->mnt_sb ||
+	    inode->i_fop != &pidfs_file_operations)
+		return NULL;
+	return inode->i_private;
+}
+
+static struct pidfs_future_file *
+pidfs_future_file(const struct inode *inode)
+{
+	struct pidfs_node *node = pidfs_inode_node(inode);
+
+	if (!node || node->type != PIDFS_NODE_FUTURE)
+		return NULL;
+	return container_of(node, struct pidfs_future_file, node);
+}
+
+void *pidfs_future_file_data(const struct file *file,
+			     const struct pidfs_future_file_ops *ops)
+{
+	struct pidfs_future_file *future;
+
+	if (file->f_op != &pidfs_file_operations)
+		return NULL;
+	future = pidfs_future_file(file_inode(file));
+	return future && future->ops == ops ? future->data : NULL;
+}
+
+static struct pid *pidfs_inode_pid(const struct inode *inode)
+{
+	struct pidfs_future_file *future;
+	struct pidfs_node *node;
+	struct pid *pid;
+
+	node = pidfs_inode_node(inode);
+	if (!node)
+		return ERR_PTR(-EBADF);
+	if (node->type == PIDFS_NODE_PID)
+		return container_of(node, struct pid, pidfs_node);
+
+	future = pidfs_future_file(inode);
+	if (!future || !future->ops || !future->ops->get_pid)
+		return ERR_PTR(-EBADF);
+	pid = future->ops->get_pid(future->data);
+	if (WARN_ON_ONCE(!pid))
+		return ERR_PTR(-EBADF);
+	return pid;
+}
+
 #if BITS_PER_LONG == 32
 
 DEFINE_SPINLOCK(pidfs_ino_lock);
@@ -166,6 +227,7 @@ static u64 pidfs_alloc_ino(void)
 
 void pidfs_prepare_pid(struct pid *pid)
 {
+	pid->pidfs_node.type = PIDFS_NODE_PID;
 	pid->stashed = NULL;
 	pid->attr = NULL;
 	pid->ino = 0;
@@ -275,7 +337,7 @@ static void pidfd_show_fdinfo(struct seq_file *m, struct file *f)
 	struct pid_namespace *ns;
 	pid_t nr = -1;
 
-	if (likely(pid_has_task(pid, PIDTYPE_PID))) {
+	if (!IS_ERR(pid) && likely(pid_has_task(pid, PIDTYPE_PID))) {
 		ns = proc_pid_ns(file_inode(m->file)->i_sb);
 		nr = pid_nr_ns(pid, ns);
 	}
@@ -305,11 +367,15 @@ static void pidfd_show_fdinfo(struct seq_file *m, struct file *f)
  */
 static __poll_t pidfd_poll(struct file *file, struct poll_table_struct *pts)
 {
-	struct pid *pid = pidfd_pid(file);
 	struct task_struct *task;
 	__poll_t poll_flags = 0;
+	struct pid *pid = pidfd_pid(file);
+
+	if (IS_ERR(pid))
+		return PTR_ERR(pid) == -ESRCH ? 0 : EPOLLHUP;
 
 	poll_wait(file, &pid->wait_pidfd, pts);
+
 	/*
 	 * Don't wake waiters if the thread-group leader exited
 	 * prematurely. They either get notified when the last subthread
@@ -376,6 +442,8 @@ static long pidfd_info(struct file *file, unsigned int cmd, unsigned long arg)
 
 	BUILD_BUG_ON(sizeof(struct pidfd_info) != PIDFD_INFO_SIZE_VER3);
 
+	if (IS_ERR(pid))
+		return PTR_ERR(pid);
 	if (!uinfo)
 		return -EINVAL;
 	if (usize < PIDFD_INFO_SIZE_VER0)
@@ -532,6 +600,7 @@ static long pidfd_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
 	struct task_struct *task __free(put_task) = NULL;
 	struct nsproxy *nsp __free(put_nsproxy) = NULL;
 	struct ns_common *ns_common = NULL;
+	struct pid *pid;
 
 	if (!pidfs_ioctl_valid(cmd))
 		return -ENOIOCTLCMD;
@@ -544,11 +613,15 @@ static long pidfd_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
 		return put_user(file_inode(file)->i_generation, argp);
 	}
 
+	pid = pidfd_pid(file);
+	if (IS_ERR(pid))
+		return PTR_ERR(pid);
+
 	/* Extensible IOCTL that does not open namespace FDs, take a shortcut */
 	if (_IOC_NR(cmd) == _IOC_NR(PIDFD_GET_INFO))
 		return pidfd_info(file, cmd, arg);
 
-	task = get_pid_task(pidfd_pid(file), PIDTYPE_PID);
+	task = get_pid_task(pid, PIDTYPE_PID);
 	if (!task)
 		return -ESRCH;
 
@@ -673,11 +746,14 @@ static long pidfd_compat_ioctl(struct file *file, unsigned int cmd,
 
 static int pidfs_file_release(struct inode *inode, struct file *file)
 {
-	struct pid *pid = inode->i_private;
+	struct pid *pid;
 	struct task_struct *task;
 
 	if (!(file->f_flags & PIDFD_AUTOKILL))
 		return 0;
+	pid = pidfd_pid(file);
+	if (IS_ERR(pid))
+		return 0;
 
 	guard(rcu)();
 	task = pid_task(pid, PIDTYPE_TGID);
@@ -705,9 +781,9 @@ static const struct file_operations pidfs_file_operations = {
 
 struct pid *pidfd_pid(const struct file *file)
 {
-	if (file->f_op != &pidfs_file_operations)
+	if (unlikely(file->f_op != &pidfs_file_operations))
 		return ERR_PTR(-EBADF);
-	return file_inode(file)->i_private;
+	return pidfs_inode_pid(file_inode(file));
 }
 
 /*
@@ -797,8 +873,6 @@ void pidfs_coredump(const struct coredump_params *cprm)
 }
 #endif
 
-static struct vfsmount *pidfs_mnt __ro_after_init;
-
 /*
  * The vfs falls back to simple_setattr() if i_op->setattr() isn't
  * implemented. Let's reject it completely until we have a clean
@@ -820,7 +894,10 @@ static int pidfs_getattr(struct mnt_idmap *idmap, const struct path *path,
 static ssize_t pidfs_listxattr(struct dentry *dentry, char *buf, size_t size)
 {
 	struct inode *inode = d_inode(dentry);
-	struct pid *pid = inode->i_private;
+	struct pid *pid = pidfs_inode_pid(inode);
+
+	if (IS_ERR(pid))
+		return PTR_ERR(pid);
 
 	return simple_xattr_list(inode, &pid->attr->xattrs, buf, size);
 }
@@ -833,10 +910,25 @@ static const struct inode_operations pidfs_inode_operations = {
 
 static void pidfs_evict_inode(struct inode *inode)
 {
-	struct pid *pid = inode->i_private;
+	struct pidfs_future_file *future;
+	struct pidfs_node *node = pidfs_inode_node(inode);
 
 	clear_inode(inode);
-	put_pid(pid);
+	if (!node)
+		return;
+	if (node->type == PIDFS_NODE_PID) {
+		put_pid(container_of(node, struct pid, pidfs_node));
+		return;
+	}
+	if (WARN_ON_ONCE(node->type != PIDFS_NODE_FUTURE))
+		return;
+
+	future = container_of(node, struct pidfs_future_file, node);
+	if (future->ops->release)
+		future->ops->release(future->data);
+	if (future->attr)
+		kmem_cache_free(pidfs_attr_cachep, future->attr);
+	kfree(future);
 }
 
 static const struct super_operations pidfs_sops = {
@@ -854,15 +946,24 @@ static char *pidfs_dname(struct dentry *dentry, char *buffer, int buflen)
 	return dynamic_dname(buffer, buflen, "anon_inode:[pidfd]");
 }
 
+static void pidfs_dentry_prune(struct dentry *dentry)
+{
+	if (dentry->d_fsdata)
+		stashed_dentry_prune(dentry);
+}
+
 const struct dentry_operations pidfs_dentry_operations = {
 	.d_dname	= pidfs_dname,
-	.d_prune	= stashed_dentry_prune,
+	.d_prune	= pidfs_dentry_prune,
 };
 
 static int pidfs_encode_fh(struct inode *inode, u32 *fh, int *max_len,
 			   struct inode *parent)
 {
-	const struct pid *pid = inode->i_private;
+	const struct pid *pid = pidfs_inode_pid(inode);
+
+	if (IS_ERR(pid))
+		return FILEID_INVALID;
 
 	if (*max_len < 2) {
 		*max_len = 2;
@@ -974,22 +1075,36 @@ static const struct export_operations pidfs_export_operations = {
 	.permission	= pidfs_export_permission,
 };
 
-static int pidfs_init_inode(struct inode *inode, void *data)
+static void pidfs_init_common_inode(struct inode *inode,
+				    struct pidfs_node *node, u64 ino)
 {
-	const struct pid *pid = data;
-
-	inode->i_private = data;
+	inode->i_private = node;
 	inode->i_flags |= S_PRIVATE | S_ANON_INODE;
 	/* We allow to set xattrs. */
 	inode->i_flags &= ~S_IMMUTABLE;
-	inode->i_mode |= S_IRWXU;
+	inode->i_mode = S_IFREG | 0700;
+	inode->i_uid = GLOBAL_ROOT_UID;
+	inode->i_gid = GLOBAL_ROOT_GID;
 	inode->i_op = &pidfs_inode_operations;
 	inode->i_fop = &pidfs_file_operations;
-	inode->i_ino = pidfs_ino(pid->ino);
-	inode->i_generation = pidfs_gen(pid->ino);
+	inode->i_ino = pidfs_ino(ino);
+	inode->i_generation = pidfs_gen(ino);
+}
+
+static int pidfs_init_inode(struct inode *inode, void *data)
+{
+	struct pid *pid = data;
+
+	pidfs_init_common_inode(inode, &pid->pidfs_node, pid->ino);
 	return 0;
 }
 
+static bool pidfs_inode_data_matches(const struct inode *inode,
+				     const void *data)
+{
+	return pidfs_inode_pid(inode) == data;
+}
+
 static void pidfs_put_data(void *data)
 {
 	struct pid *pid = data;
@@ -1044,9 +1159,11 @@ int pidfs_register_pid_gfp(struct pid *pid, gfp_t gfp)
 static struct dentry *pidfs_stash_dentry(struct dentry **stashed,
 					 struct dentry *dentry)
 {
+	struct pid *pid = pidfs_inode_pid(d_inode(dentry));
 	int ret;
-	struct pid *pid = d_inode(dentry)->i_private;
 
+	if (WARN_ON_ONCE(IS_ERR(pid)))
+		return ERR_CAST(pid);
 	VFS_WARN_ON_ONCE(stashed != &pid->stashed);
 
 	ret = pidfs_register_pid(pid);
@@ -1060,15 +1177,19 @@ static const struct stashed_operations pidfs_stashed_ops = {
 	.stash_dentry	= pidfs_stash_dentry,
 	.init_inode	= pidfs_init_inode,
 	.put_data	= pidfs_put_data,
+	.data_matches	= pidfs_inode_data_matches,
 };
 
 static int pidfs_xattr_get(const struct xattr_handler *handler,
 			   struct dentry *unused, struct inode *inode,
 			   const char *suffix, void *value, size_t size)
 {
-	struct pid *pid = inode->i_private;
+	struct pid *pid = pidfs_inode_pid(inode);
 	const char *name = xattr_full_name(handler, suffix);
 
+	if (IS_ERR(pid))
+		return PTR_ERR(pid);
+
 	return simple_xattr_get(&pidfs_xa_cache, &pid->attr->xattrs, name, value, size);
 }
 
@@ -1077,10 +1198,13 @@ static int pidfs_xattr_set(const struct xattr_handler *handler,
 			   struct inode *inode, const char *suffix,
 			   const void *value, size_t size, int flags)
 {
-	struct pid *pid = inode->i_private;
+	struct pid *pid = pidfs_inode_pid(inode);
 	const char *name = xattr_full_name(handler, suffix);
 	struct simple_xattr *old_xattr;
 
+	if (IS_ERR(pid))
+		return PTR_ERR(pid);
+
 	/* Ensure we're the only one to set @attr->xattrs. */
 	WARN_ON_ONCE(!inode_is_locked(inode));
 
@@ -1158,6 +1282,90 @@ struct file *pidfs_alloc_file(struct pid *pid, unsigned int flags)
 	return pidfd_file;
 }
 
+/**
+ * pidfs_alloc_future_file - allocate a taskless pidfs file
+ * @name: anonymous file name used by LSM initialization
+ * @data: producer data retained for the inode lifetime
+ * @ops: callbacks that resolve and release @data
+ * @flags: file status flags
+ *
+ * Ownership of @data transfers to the returned file on success. One serialized
+ * producer owns all later association and publication transitions. It may
+ * associate one preallocated pid with
+ * pidfs_future_file_set_pid(), publish its pidfs metadata with
+ * pidfs_future_file_publish_pid(), make @ops->get_pid() resolve that pid, and
+ * finally wake waiters with pidfs_future_file_notify().
+ *
+ * A task associated with the future pid must not become runnable before
+ * publication metadata and exit-wakeup forwarding are installed.
+ *
+ * Return: A new pidfs file on success or an error pointer on failure.
+ */
+struct file *pidfs_alloc_future_file(const char *name, void *data,
+				     const struct pidfs_future_file_ops *ops,
+				     unsigned int flags)
+{
+	struct pidfs_future_file *future;
+	struct inode *inode;
+	struct file *file;
+	u64 ino;
+	int ret;
+
+	if (!ops || !ops->get_pid)
+		return ERR_PTR(-EINVAL);
+
+	future = kzalloc_obj(*future, GFP_KERNEL_ACCOUNT);
+	if (!future)
+		return ERR_PTR(-ENOMEM);
+	future->attr = kmem_cache_zalloc(pidfs_attr_cachep, GFP_KERNEL_ACCOUNT);
+	if (!future->attr) {
+		kfree(future);
+		return ERR_PTR(-ENOMEM);
+	}
+	INIT_LIST_HEAD_RCU(&future->attr->xattrs);
+
+	inode = new_inode_pseudo(pidfs_mnt->mnt_sb);
+	if (!inode) {
+		kmem_cache_free(pidfs_attr_cachep, future->attr);
+		kfree(future);
+		return ERR_PTR(-ENOMEM);
+	}
+	/* Preserve the anonymous-inode creation check for future pidfds. */
+	ret = security_inode_init_security_anon(inode, &QSTR(name), NULL);
+	if (ret) {
+		iput(inode);
+		kmem_cache_free(pidfs_attr_cachep, future->attr);
+		kfree(future);
+		return ERR_PTR(ret);
+	}
+	ino = pidfs_alloc_ino();
+	simple_inode_init_ts(inode);
+	pidfs_init_common_inode(inode, NULL, ino);
+
+	file = alloc_file_pseudo(inode, pidfs_mnt, name, flags,
+				 &pidfs_file_operations);
+	if (IS_ERR(file)) {
+		iput(inode);
+		kmem_cache_free(pidfs_attr_cachep, future->attr);
+		kfree(future);
+		return file;
+	}
+
+	future->node.type = PIDFS_NODE_FUTURE;
+	future->data = data;
+	future->ops = ops;
+	future->ino = ino;
+	inode->i_private = &future->node;
+	return file;
+}
+
+u64 pidfs_future_file_ino(const struct file *file)
+{
+	struct pidfs_future_file *future = pidfs_future_file(file_inode(file));
+
+	return future ? future->ino : 0;
+}
+
 void __init pidfs_init(void)
 {
 	if (rhashtable_init(&pidfs_ino_ht, &pidfs_ino_ht_params))
diff --git a/include/linux/pid.h b/include/linux/pid.h
index ddaef0bbc8ba3..a29ffe2a5fa8e 100644
--- a/include/linux/pid.h
+++ b/include/linux/pid.h
@@ -50,6 +50,15 @@
 
 struct pidfs_attr;
 
+enum pidfs_node_type {
+	PIDFS_NODE_PID,
+	PIDFS_NODE_FUTURE,
+};
+
+struct pidfs_node {
+	enum pidfs_node_type type;
+};
+
 struct upid {
 	int nr;
 	struct pid_namespace *ns;
@@ -59,6 +68,7 @@ struct pid {
 	refcount_t count;
 	unsigned int level;
 	spinlock_t lock;
+	struct pidfs_node pidfs_node;
 	struct {
 		u64 ino;
 		struct rhash_head pidfs_hash;
diff --git a/include/linux/pidfs.h b/include/linux/pidfs.h
index 0abf7da9ab236..6b7fbc54ab388 100644
--- a/include/linux/pidfs.h
+++ b/include/linux/pidfs.h
@@ -5,8 +5,29 @@
 #include <linux/gfp_types.h>
 
 struct coredump_params;
+struct file;
+struct pid;
+
+/**
+ * struct pidfs_future_file_ops - callbacks for a taskless pidfs file
+ * @get_pid: Return the published, borrowed, non-NULL process identity, or an
+ *	error pointer while the producer is still taskless. The producer must
+ *	keep the returned pid alive for the future inode lifetime.
+ * @release: Optionally release producer-owned data when the pidfs inode is
+ *	evicted. If provided, this is called at most once.
+ */
+struct pidfs_future_file_ops {
+	struct pid *(*get_pid)(void *data);
+	void (*release)(void *data);
+};
 
 struct file *pidfs_alloc_file(struct pid *pid, unsigned int flags);
+struct file *pidfs_alloc_future_file(const char *name, void *data,
+				     const struct pidfs_future_file_ops *ops,
+				     unsigned int flags);
+void *pidfs_future_file_data(const struct file *file,
+			     const struct pidfs_future_file_ops *ops);
+u64 pidfs_future_file_ino(const struct file *file);
 void __init pidfs_init(void);
 void pidfs_prepare_pid(struct pid *pid);
 int pidfs_add_pid(struct pid *pid);
-- 
2.52.0




More information about the Linux-security-module-archive mailing list