summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Brauner <brauner@kernel.org>2026-04-02 09:12:11 +0200
committerChristian Brauner <brauner@kernel.org>2026-04-09 14:36:52 +0200
commitcb76a81c7cec37bdf525164561b02665cd763421 (patch)
treea4c89dd9f0d46e75f79248246ce7d4718b0b0f90
parent1fe989e1c42a315c7e7918e7b812377137085036 (diff)
kernfs: make directory seek namespace-aware
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>
-rw-r--r--fs/kernfs/dir.c5
1 files changed, 5 insertions, 0 deletions
diff --git a/fs/kernfs/dir.c b/fs/kernfs/dir.c
index f5cf1d74b5f1..22a4dff2a3af 100644
--- a/fs/kernfs/dir.c
+++ b/fs/kernfs/dir.c
@@ -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;
}