]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
pidfd: add pidfs
authorChristian Brauner <brauner@kernel.org>
Mon, 12 Feb 2024 15:32:38 +0000 (16:32 +0100)
committerChristian Brauner <brauner@kernel.org>
Fri, 1 Mar 2024 11:23:37 +0000 (12:23 +0100)
This moves pidfds from the anonymous inode infrastructure to a tiny
pseudo filesystem. This has been on my todo for quite a while as it will
unblock further work that we weren't able to do simply because of the
very justified limitations of anonymous inodes. Moving pidfds to a tiny
pseudo filesystem allows:

* statx() on pidfds becomes useful for the first time.
* pidfds can be compared simply via statx() and then comparing inode
  numbers.
* pidfds have unique inode numbers for the system lifetime.
* struct pid is now stashed in inode->i_private instead of
  file->private_data. This means it is now possible to introduce
  concepts that operate on a process once all file descriptors have been
  closed. A concrete example is kill-on-last-close.
* file->private_data is freed up for per-file options for pidfds.
* Each struct pid will refer to a different inode but the same struct
  pid will refer to the same inode if it's opened multiple times. In
  contrast to now where each struct pid refers to the same inode. Even
  if we were to move to anon_inode_create_getfile() which creates new
  inodes we'd still be associating the same struct pid with multiple
  different inodes.

The tiny pseudo filesystem is not visible anywhere in userspace exactly
like e.g., pipefs and sockfs. There's no lookup, there's no complex
inode operations, nothing. Dentries and inodes are always deleted when
the last pidfd is closed.

We allocate a new inode for each struct pid and we reuse that inode for
all pidfds. We use iget_locked() to find that inode again based on the
inode number which isn't recycled. We allocate a new dentry for each
pidfd that uses the same inode. That is similar to anonymous inodes
which reuse the same inode for thousands of dentries. For pidfds we're
talking way less than that. There usually won't be a lot of concurrent
openers of the same struct pid. They can probably often be counted on
two hands. I know that systemd does use separate pidfd for the same
struct pid for various complex process tracking issues. So I think with
that things actually become way simpler. Especially because we don't
have to care about lookup. Dentries and inodes continue to be always
deleted.

The code is entirely optional and fairly small. If it's not selected we
fallback to anonymous inodes. Heavily inspired by nsfs which uses a
similar stashing mechanism just for namespaces.

Link: https://lore.kernel.org/r/20240213-vfs-pidfd_fs-v1-2-f863f58cfce1@kernel.org
Signed-off-by: Christian Brauner <brauner@kernel.org>
fs/Kconfig
fs/pidfs.c
include/linux/pid.h
include/linux/pidfs.h [new file with mode: 0644]
include/uapi/linux/magic.h
init/main.c
kernel/fork.c
kernel/nsproxy.c
kernel/pid.c

index 89fdbefd1075f8f5a071987bca3b4a50f687a887..f3dbd84a0e40a05fc37312ad092d74229d52be7b 100644 (file)
@@ -174,6 +174,13 @@ source "fs/proc/Kconfig"
 source "fs/kernfs/Kconfig"
 source "fs/sysfs/Kconfig"
 
+config FS_PID
+       bool "Pseudo filesystem for process file descriptors"
+       depends on 64BIT
+       default y
+       help
+         Pidfs implements advanced features for process file descriptors.
+
 config TMPFS
        bool "Tmpfs virtual memory file system support (former shm fs)"
        depends on SHMEM
index eccb291862a06d4aee124bb98d8fb8e9943c09d5..6c3f010074afa2c4cea8469de427e0d5403107b1 100644 (file)
@@ -1,9 +1,11 @@
 // SPDX-License-Identifier: GPL-2.0
+#include <linux/anon_inodes.h>
 #include <linux/file.h>
 #include <linux/fs.h>
 #include <linux/magic.h>
 #include <linux/mount.h>
 #include <linux/pid.h>
+#include <linux/pidfs.h>
 #include <linux/pid_namespace.h>
 #include <linux/poll.h>
 #include <linux/proc_fs.h>
 
 static int pidfd_release(struct inode *inode, struct file *file)
 {
+#ifndef CONFIG_FS_PID
        struct pid *pid = file->private_data;
 
        file->private_data = NULL;
        put_pid(pid);
+#endif
        return 0;
 }
 
@@ -59,7 +63,7 @@ static int pidfd_release(struct inode *inode, struct file *file)
  */
 static void pidfd_show_fdinfo(struct seq_file *m, struct file *f)
 {
-       struct pid *pid = f->private_data;
+       struct pid *pid = pidfd_pid(f);
        struct pid_namespace *ns;
        pid_t nr = -1;
 
@@ -93,7 +97,7 @@ 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 = file->private_data;
+       struct pid *pid = pidfd_pid(file);
        bool thread = file->f_flags & PIDFD_THREAD;
        struct task_struct *task;
        __poll_t poll_flags = 0;
@@ -113,10 +117,156 @@ static __poll_t pidfd_poll(struct file *file, struct poll_table_struct *pts)
        return poll_flags;
 }
 
-const struct file_operations pidfd_fops = {
+static const struct file_operations pidfs_file_operations = {
        .release        = pidfd_release,
        .poll           = pidfd_poll,
 #ifdef CONFIG_PROC_FS
        .show_fdinfo    = pidfd_show_fdinfo,
 #endif
 };
+
+struct pid *pidfd_pid(const struct file *file)
+{
+       if (file->f_op != &pidfs_file_operations)
+               return ERR_PTR(-EBADF);
+#ifdef CONFIG_FS_PID
+       return file_inode(file)->i_private;
+#else
+       return file->private_data;
+#endif
+}
+
+#ifdef CONFIG_FS_PID
+static struct vfsmount *pidfs_mnt __ro_after_init;
+static struct super_block *pidfs_sb __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
+ * permission concept for pidfds.
+ */
+static int pidfs_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
+                        struct iattr *attr)
+{
+       return -EOPNOTSUPP;
+}
+
+static int pidfs_getattr(struct mnt_idmap *idmap, const struct path *path,
+                        struct kstat *stat, u32 request_mask,
+                        unsigned int query_flags)
+{
+       struct inode *inode = d_inode(path->dentry);
+
+       generic_fillattr(&nop_mnt_idmap, request_mask, inode, stat);
+       return 0;
+}
+
+static const struct inode_operations pidfs_inode_operations = {
+       .getattr = pidfs_getattr,
+       .setattr = pidfs_setattr,
+};
+
+static void pidfs_evict_inode(struct inode *inode)
+{
+       struct pid *pid = inode->i_private;
+
+       clear_inode(inode);
+       put_pid(pid);
+}
+
+static const struct super_operations pidfs_sops = {
+       .drop_inode     = generic_delete_inode,
+       .evict_inode    = pidfs_evict_inode,
+       .statfs         = simple_statfs,
+};
+
+static char *pidfs_dname(struct dentry *dentry, char *buffer, int buflen)
+{
+       return dynamic_dname(buffer, buflen, "pidfd:[%lu]",
+                            d_inode(dentry)->i_ino);
+}
+
+static const struct dentry_operations pidfs_dentry_operations = {
+       .d_delete       = always_delete_dentry,
+       .d_dname        = pidfs_dname,
+};
+
+static int pidfs_init_fs_context(struct fs_context *fc)
+{
+       struct pseudo_fs_context *ctx;
+
+       ctx = init_pseudo(fc, PID_FS_MAGIC);
+       if (!ctx)
+               return -ENOMEM;
+
+       ctx->ops = &pidfs_sops;
+       ctx->dops = &pidfs_dentry_operations;
+       return 0;
+}
+
+static struct file_system_type pidfs_type = {
+       .name                   = "pidfs",
+       .init_fs_context        = pidfs_init_fs_context,
+       .kill_sb                = kill_anon_super,
+};
+
+struct file *pidfs_alloc_file(struct pid *pid, unsigned int flags)
+{
+
+       struct inode *inode;
+       struct file *pidfd_file;
+
+       inode = iget_locked(pidfs_sb, pid->ino);
+       if (!inode)
+               return ERR_PTR(-ENOMEM);
+
+       if (inode->i_state & I_NEW) {
+               /*
+                * Inode numbering for pidfs start at RESERVED_PIDS + 1.
+                * This avoids collisions with the root inode which is 1
+                * for pseudo filesystems.
+                */
+               inode->i_ino = pid->ino;
+               inode->i_mode = S_IFREG | S_IRUGO;
+               inode->i_op = &pidfs_inode_operations;
+               inode->i_fop = &pidfs_file_operations;
+               inode->i_flags |= S_IMMUTABLE;
+               inode->i_private = get_pid(pid);
+               simple_inode_init_ts(inode);
+               unlock_new_inode(inode);
+       }
+
+       pidfd_file = alloc_file_pseudo(inode, pidfs_mnt, "", flags,
+                                      &pidfs_file_operations);
+       if (IS_ERR(pidfd_file))
+               iput(inode);
+
+       return pidfd_file;
+}
+
+void __init pidfs_init(void)
+{
+       pidfs_mnt = kern_mount(&pidfs_type);
+       if (IS_ERR(pidfs_mnt))
+               panic("Failed to mount pidfs pseudo filesystem");
+
+       pidfs_sb = pidfs_mnt->mnt_sb;
+}
+
+#else /* !CONFIG_FS_PID */
+
+struct file *pidfs_alloc_file(struct pid *pid, unsigned int flags)
+{
+       struct file *pidfd_file;
+
+       pidfd_file = anon_inode_getfile("[pidfd]", &pidfs_file_operations, pid,
+                                       flags | O_RDWR);
+       if (IS_ERR(pidfd_file))
+               return pidfd_file;
+
+       get_pid(pid);
+       return pidfd_file;
+}
+
+void __init pidfs_init(void) { }
+#endif
index 8124d57752b9386b9b183e3f0c8064227467f1b4..956481128e8d42438f964f934e50fe1d3857216a 100644 (file)
@@ -55,6 +55,9 @@ struct pid
        refcount_t count;
        unsigned int level;
        spinlock_t lock;
+#ifdef CONFIG_FS_PID
+       unsigned long ino;
+#endif
        /* lists of tasks that use this pid */
        struct hlist_head tasks[PIDTYPE_MAX];
        struct hlist_head inodes;
@@ -66,8 +69,6 @@ struct pid
 
 extern struct pid init_struct_pid;
 
-extern const struct file_operations pidfd_fops;
-
 struct file;
 
 struct pid *pidfd_pid(const struct file *file);
diff --git a/include/linux/pidfs.h b/include/linux/pidfs.h
new file mode 100644 (file)
index 0000000..75bdf98
--- /dev/null
@@ -0,0 +1,8 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _LINUX_PID_FS_H
+#define _LINUX_PID_FS_H
+
+struct file *pidfs_alloc_file(struct pid *pid, unsigned int flags);
+void __init pidfs_init(void);
+
+#endif /* _LINUX_PID_FS_H */
index 6325d1d0e90f5dcdc7bdc91d612f8fc4c7b40135..1b40a968ba91fc9d2d0a8339261584a0d89ff9a9 100644 (file)
 #define DMA_BUF_MAGIC          0x444d4142      /* "DMAB" */
 #define DEVMEM_MAGIC           0x454d444d      /* "DMEM" */
 #define SECRETMEM_MAGIC                0x5345434d      /* "SECM" */
+#define PID_FS_MAGIC           0x50494446      /* "PIDF" */
 
 #endif /* __LINUX_MAGIC_H__ */
index e24b0780fdff7a807bd027ab26e61fc303c624ef..2fbf6a3114d57a8458bfcd0fd3d3b43163714010 100644 (file)
@@ -99,6 +99,7 @@
 #include <linux/init_syscalls.h>
 #include <linux/stackdepot.h>
 #include <linux/randomize_kstack.h>
+#include <linux/pidfs.h>
 #include <net/net_namespace.h>
 
 #include <asm/io.h>
@@ -1059,6 +1060,7 @@ void start_kernel(void)
        seq_file_init();
        proc_root_init();
        nsfs_init();
+       pidfs_init();
        cpuset_init();
        cgroup_init();
        taskstats_init_early();
index 662a61f340ce86bb97e0f2669ccaa268cf623a83..2f839c290dcf2967ede43ed6d1fe039daa015bc2 100644 (file)
 #include <linux/iommu.h>
 #include <linux/rseq.h>
 #include <uapi/linux/pidfd.h>
+#include <linux/pidfs.h>
 
 #include <asm/pgalloc.h>
 #include <linux/uaccess.h>
@@ -1985,14 +1986,6 @@ static inline void rcu_copy_process(struct task_struct *p)
 #endif /* #ifdef CONFIG_TASKS_TRACE_RCU */
 }
 
-struct pid *pidfd_pid(const struct file *file)
-{
-       if (file->f_op == &pidfd_fops)
-               return file->private_data;
-
-       return ERR_PTR(-EBADF);
-}
-
 /**
  * __pidfd_prepare - allocate a new pidfd_file and reserve a pidfd
  * @pid:   the struct pid for which to create a pidfd
@@ -2030,13 +2023,11 @@ static int __pidfd_prepare(struct pid *pid, unsigned int flags, struct file **re
        if (pidfd < 0)
                return pidfd;
 
-       pidfd_file = anon_inode_getfile("[pidfd]", &pidfd_fops, pid,
-                                       flags | O_RDWR);
+       pidfd_file = pidfs_alloc_file(pid, flags | O_RDWR);
        if (IS_ERR(pidfd_file)) {
                put_unused_fd(pidfd);
                return PTR_ERR(pidfd_file);
        }
-       get_pid(pid); /* held by pidfd_file now */
        /*
         * anon_inode_getfile() ignores everything outside of the
         * O_ACCMODE | O_NONBLOCK mask, set PIDFD_THREAD manually.
index 15781acaac1ceec97fa2ae649d284c025693186e..6ec3deec68c200eb656d2686e20a31165922f3b3 100644 (file)
@@ -573,7 +573,7 @@ SYSCALL_DEFINE2(setns, int, fd, int, flags)
        if (proc_ns_file(f.file))
                err = validate_ns(&nsset, ns);
        else
-               err = validate_nsset(&nsset, f.file->private_data);
+               err = validate_nsset(&nsset, pidfd_pid(f.file));
        if (!err) {
                commit_nsset(&nsset);
                perf_event_namespaces(current);
index c1d940fbd3140ae7fd8250f7d9a1172dfa5ab61c..581cc34341fd709bf40d16f610e9e13ac763f1a8 100644 (file)
@@ -42,6 +42,7 @@
 #include <linux/sched/signal.h>
 #include <linux/sched/task.h>
 #include <linux/idr.h>
+#include <linux/pidfs.h>
 #include <net/sock.h>
 #include <uapi/linux/pidfd.h>
 
@@ -65,6 +66,13 @@ int pid_max = PID_MAX_DEFAULT;
 
 int pid_max_min = RESERVED_PIDS + 1;
 int pid_max_max = PID_MAX_LIMIT;
+#ifdef CONFIG_FS_PID
+/*
+ * Pseudo filesystems start inode numbering after one. We use Reserved
+ * PIDs as a natural offset.
+ */
+static u64 pidfs_ino = RESERVED_PIDS;
+#endif
 
 /*
  * PID-map pages start out as NULL, they get allocated upon
@@ -272,6 +280,9 @@ struct pid *alloc_pid(struct pid_namespace *ns, pid_t *set_tid,
        spin_lock_irq(&pidmap_lock);
        if (!(ns->pid_allocated & PIDNS_ADDING))
                goto out_unlock;
+#ifdef CONFIG_FS_PID
+       pid->ino = ++pidfs_ino;
+#endif
        for ( ; upid >= pid->numbers; --upid) {
                /* Make the PID visible to find_pid_ns. */
                idr_replace(&upid->ns->idr, pid, upid->nr);