]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
kernfs: make directory seek namespace-aware
authorChristian Brauner <brauner@kernel.org>
Thu, 2 Apr 2026 07:12:11 +0000 (09:12 +0200)
committerChristian Brauner <brauner@kernel.org>
Thu, 9 Apr 2026 12:36:52 +0000 (14:36 +0200)
The rbtree backing kernfs directories is ordered by (hash, ns_id, name)
but kernfs_dir_pos() only searches by hash when seeking to a position
during readdir. When two nodes from different namespaces share the same
hash value, the binary search can land on a node in the wrong namespace.
The subsequent skip-forward loop walks rb_next() and may overshoot the
correct node, silently dropping an entry from the readdir results.

With the recent switch from raw namespace pointers to public namespace
ids as hash seeds, computing hash collisions became an offline operation.
An unprivileged user could unshare into a new network namespace, create
a single interface whose name-hash collides with a target entry in
init_net, and cause a victim's seekdir/readdir on /sys/class/net to miss
that entry.

Fix this by extending the rbtree search in kernfs_dir_pos() to also
compare namespace ids when hashes match. Since the rbtree is already
ordered by (hash, ns_id, name), this makes the seek land directly in the
correct namespace's range, eliminating the wrong-namespace overshoot.

Signed-off-by: Christian Brauner <brauner@kernel.org>
fs/kernfs/dir.c

index f5cf1d74b5f138e3aaf0363b11aec232f7eadece..22a4dff2a3af5f4df1762614b80ea8e34d139b50 100644 (file)
@@ -1866,6 +1866,7 @@ static struct kernfs_node *kernfs_dir_pos(const struct ns_common *ns,
        }
        if (!pos && (hash > 1) && (hash < INT_MAX)) {
                struct rb_node *node = parent->dir.children.rb_node;
+               u64 ns_id = kernfs_ns_id(ns);
                while (node) {
                        pos = rb_to_kn(node);
 
@@ -1873,6 +1874,10 @@ static struct kernfs_node *kernfs_dir_pos(const struct ns_common *ns,
                                node = node->rb_left;
                        else if (hash > pos->hash)
                                node = node->rb_right;
+                       else if (ns_id < kernfs_ns_id(pos->ns))
+                               node = node->rb_left;
+                       else if (ns_id > kernfs_ns_id(pos->ns))
+                               node = node->rb_right;
                        else
                                break;
                }