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 <[email protected]> --- 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

