]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
pidfs: rework inode number allocation
authorChristian Brauner <brauner@kernel.org>
Fri, 29 Nov 2024 13:02:23 +0000 (14:02 +0100)
committerChristian Brauner <brauner@kernel.org>
Sat, 14 Dec 2024 11:40:31 +0000 (12:40 +0100)
Recently we received a patchset that aims to enable file handle encoding
and decoding via name_to_handle_at(2) and open_by_handle_at(2).

A crucical step in the patch series is how to go from inode number to
struct pid without leaking information into unprivileged contexts. The
issue is that in order to find a struct pid the pid number in the
initial pid namespace must be encoded into the file handle via
name_to_handle_at(2). This can be used by containers using a separate
pid namespace to learn what the pid number of a given process in the
initial pid namespace is. While this is a weak information leak it could
be used in various exploits and in general is an ugly wart in the design.

To solve this problem a new way is needed to lookup a struct pid based
on the inode number allocated for that struct pid. The other part is to
remove the custom inode number allocation on 32bit systems that is also
an ugly wart that should go away.

So, a new scheme is used that I was discusssing with Tejun some time
back. A cyclic ida is used for the lower 32 bits and a the high 32 bits
are used for the generation number. This gives a 64 bit inode number
that is unique on both 32 bit and 64 bit. The lower 32 bit number is
recycled slowly and can be used to lookup struct pids.

Link: https://lore.kernel.org/r/20241129-work-pidfs-v2-1-61043d66fbce@kernel.org
Reviewed-by: Jeff Layton <jlayton@kernel.org>
Reviewed-by: Amir Goldstein <amir73il@gmail.com>
Reviewed-by: Jan Kara <jack@suse.cz>
Signed-off-by: Christian Brauner <brauner@kernel.org>
fs/pidfs.c
include/linux/pidfs.h
kernel/pid.c

index 618abb1fa1b84cf31282c922374e28d60cd49d00..abfe96be9ffe3c6f4d5fb0f8acaeaa53fe2ad57a 100644 (file)
 #include "internal.h"
 #include "mount.h"
 
+static DEFINE_IDR(pidfs_ino_idr);
+
+static u32 pidfs_ino_upper_32_bits = 0;
+
+#if BITS_PER_LONG == 32
+/*
+ * On 32 bit systems the lower 32 bits are the inode number and
+ * the higher 32 bits are the generation number. The starting
+ * value for the inode number and the generation number is one.
+ */
+static u32 pidfs_ino_lower_32_bits = 1;
+
+static inline unsigned long pidfs_ino(u64 ino)
+{
+       return lower_32_bits(ino);
+}
+
+/* On 32 bit the generation number are the upper 32 bits. */
+static inline u32 pidfs_gen(u64 ino)
+{
+       return upper_32_bits(ino);
+}
+
+#else
+
+static u32 pidfs_ino_lower_32_bits = 0;
+
+/* On 64 bit simply return ino. */
+static inline unsigned long pidfs_ino(u64 ino)
+{
+       return ino;
+}
+
+/* On 64 bit the generation number is 1. */
+static inline u32 pidfs_gen(u64 ino)
+{
+       return 1;
+}
+#endif
+
+/*
+ * Construct an inode number for struct pid in a way that we can use the
+ * lower 32bit to lookup struct pid independent of any pid numbers that
+ * could be leaked into userspace (e.g., via file handle encoding).
+ */
+int pidfs_add_pid(struct pid *pid)
+{
+       u32 upper;
+       int lower;
+
+        /*
+        * Inode numbering for pidfs start at 2. This avoids collisions
+        * with the root inode which is 1 for pseudo filesystems.
+         */
+       lower = idr_alloc_cyclic(&pidfs_ino_idr, pid, 2, 0, GFP_ATOMIC);
+       if (lower >= 0 && lower < pidfs_ino_lower_32_bits)
+               pidfs_ino_upper_32_bits++;
+       upper = pidfs_ino_upper_32_bits;
+       pidfs_ino_lower_32_bits = lower;
+       if (lower < 0)
+               return lower;
+
+       pid->ino = ((u64)upper << 32) | lower;
+       pid->stashed = NULL;
+       return 0;
+}
+
+/* The idr number to remove is the lower 32 bits of the inode. */
+void pidfs_remove_pid(struct pid *pid)
+{
+       idr_remove(&pidfs_ino_idr, lower_32_bits(pid->ino));
+}
+
 #ifdef CONFIG_PROC_FS
 /**
  * pidfd_show_fdinfo - print information about a pidfd
@@ -346,7 +419,7 @@ static inline void pidfs_free_inum(unsigned long ino)
 #else
 static inline int pidfs_inum(struct pid *pid, unsigned long *ino)
 {
-       *ino = pid->ino;
+       *ino = pidfs_ino(pid->ino);
        return 0;
 }
 #define pidfs_free_inum(ino) ((void)(ino))
@@ -429,11 +502,14 @@ static const struct dentry_operations pidfs_dentry_operations = {
 
 static int pidfs_init_inode(struct inode *inode, void *data)
 {
+       const struct pid *pid = data;
+
        inode->i_private = data;
        inode->i_flags |= S_PRIVATE;
        inode->i_mode |= S_IRWXU;
        inode->i_op = &pidfs_inode_operations;
        inode->i_fop = &pidfs_file_operations;
+       inode->i_generation = pidfs_gen(pid->ino);
        /*
         * Inode numbering for pidfs start at RESERVED_PIDS + 1. This
         * avoids collisions with the root inode which is 1 for pseudo
index 75bdf9807802a5d1a9699c99aa42648c2bd34170..2958652bb108b8a2e02128e17317be4545b40a01 100644 (file)
@@ -4,5 +4,7 @@
 
 struct file *pidfs_alloc_file(struct pid *pid, unsigned int flags);
 void __init pidfs_init(void);
+int pidfs_add_pid(struct pid *pid);
+void pidfs_remove_pid(struct pid *pid);
 
 #endif /* _LINUX_PID_FS_H */
index 115448e89c3e9e664d0d51c8d853e8167ba0540c..58567d6904b25817c4d4f66942b0a941b4cd0682 100644 (file)
@@ -64,11 +64,6 @@ int pid_max = PID_MAX_DEFAULT;
 
 int pid_max_min = RESERVED_PIDS + 1;
 int pid_max_max = PID_MAX_LIMIT;
-/*
- * Pseudo filesystems start inode numbering after one. We use Reserved
- * PIDs as a natural offset.
- */
-static u64 pidfs_ino = RESERVED_PIDS;
 
 /*
  * PID-map pages start out as NULL, they get allocated upon
@@ -158,6 +153,7 @@ void free_pid(struct pid *pid)
 
                idr_remove(&ns->idr, upid->nr);
        }
+       pidfs_remove_pid(pid);
        spin_unlock_irqrestore(&pidmap_lock, flags);
 
        call_rcu(&pid->rcu, delayed_put_pid);
@@ -273,22 +269,26 @@ struct pid *alloc_pid(struct pid_namespace *ns, pid_t *set_tid,
        INIT_HLIST_HEAD(&pid->inodes);
 
        upid = pid->numbers + ns->level;
+       idr_preload(GFP_KERNEL);
        spin_lock_irq(&pidmap_lock);
        if (!(ns->pid_allocated & PIDNS_ADDING))
                goto out_unlock;
-       pid->stashed = NULL;
-       pid->ino = ++pidfs_ino;
+       retval = pidfs_add_pid(pid);
+       if (retval)
+               goto out_unlock;
        for ( ; upid >= pid->numbers; --upid) {
                /* Make the PID visible to find_pid_ns. */
                idr_replace(&upid->ns->idr, pid, upid->nr);
                upid->ns->pid_allocated++;
        }
        spin_unlock_irq(&pidmap_lock);
+       idr_preload_end();
 
        return pid;
 
 out_unlock:
        spin_unlock_irq(&pidmap_lock);
+       idr_preload_end();
        put_pid_ns(ns);
 
 out_free: