]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
kernfs: use namespace id instead of pointer for hashing and comparison
authorChristian Brauner <brauner@kernel.org>
Wed, 1 Apr 2026 10:21:16 +0000 (12:21 +0200)
committerChristian Brauner <brauner@kernel.org>
Thu, 9 Apr 2026 12:36:52 +0000 (14:36 +0200)
kernfs uses the namespace tag as both a hash seed (via init_name_hash())
and a comparison key in the rbtree. The resulting hash values are exposed
to userspace through directory seek positions (ctx->pos), and the raw
pointer comparisons in kernfs_name_compare() encode kernel pointer
ordering into the rbtree layout.

This constitutes a KASLR information leak since the hash and ordering
derived from kernel pointers can be observed from userspace.

Fix this by using the 64-bit namespace id (ns_common::ns_id) instead of
the raw pointer value for both hashing and comparison. The namespace id
is a stable, non-secret identifier that is already exposed to userspace
through other interfaces (e.g., /proc/pid/ns/, ioctl NS_GET_NSID).

Introduce kernfs_ns_id() as a helper that extracts the namespace id from
a potentially-NULL ns_common pointer, returning 0 for the no-namespace
case.

All namespace equality checks in the directory iteration and dentry
revalidation paths are also switched from pointer comparison to ns_id
comparison for consistency.

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

index be262145ae0847551f6ff39df0e03f5dc54fcfde..f5cf1d74b5f138e3aaf0363b11aec232f7eadece 100644 (file)
@@ -14,6 +14,7 @@
 #include <linux/slab.h>
 #include <linux/security.h>
 #include <linux/hash.h>
+#include <linux/ns_common.h>
 
 #include "kernfs-internal.h"
 
@@ -306,6 +307,18 @@ struct kernfs_node *kernfs_get_parent(struct kernfs_node *kn)
        return parent;
 }
 
+/*
+ * kernfs_ns_id - return the namespace id for a given namespace
+ * @ns: namespace tag (may be NULL)
+ *
+ * Use the 64-bit namespace id instead of raw pointers for hashing
+ * and comparison to avoid leaking kernel addresses to userspace.
+ */
+static u64 kernfs_ns_id(const struct ns_common *ns)
+{
+       return ns ? ns->ns_id : 0;
+}
+
 /**
  *     kernfs_name_hash - calculate hash of @ns + @name
  *     @name: Null terminated string to hash
@@ -316,7 +329,7 @@ struct kernfs_node *kernfs_get_parent(struct kernfs_node *kn)
 static unsigned int kernfs_name_hash(const char *name,
                                     const struct ns_common *ns)
 {
-       unsigned long hash = init_name_hash(ns);
+       unsigned long hash = init_name_hash(kernfs_ns_id(ns));
        unsigned int len = strlen(name);
        while (len--)
                hash = partial_name_hash(*name++, hash);
@@ -333,13 +346,16 @@ static unsigned int kernfs_name_hash(const char *name,
 static int kernfs_name_compare(unsigned int hash, const char *name,
                               const struct ns_common *ns, const struct kernfs_node *kn)
 {
+       u64 ns_id = kernfs_ns_id(ns);
+       u64 kn_ns_id = kernfs_ns_id(kn->ns);
+
        if (hash < kn->hash)
                return -1;
        if (hash > kn->hash)
                return 1;
-       if (ns < kn->ns)
+       if (ns_id < kn_ns_id)
                return -1;
-       if (ns > kn->ns)
+       if (ns_id > kn_ns_id)
                return 1;
        return strcmp(name, kernfs_rcu_name(kn));
 }
@@ -1203,7 +1219,7 @@ static int kernfs_dop_revalidate(struct inode *dir, const struct qstr *name,
 
        /* The kernfs node has been moved to a different namespace */
        if (parent && kernfs_ns_enabled(parent) &&
-           kernfs_info(dentry->d_sb)->ns != kn->ns)
+           kernfs_ns_id(kernfs_info(dentry->d_sb)->ns) != kernfs_ns_id(kn->ns))
                goto out_bad;
 
        up_read(&root->kernfs_rwsem);
@@ -1775,7 +1791,8 @@ int kernfs_rename_ns(struct kernfs_node *kn, struct kernfs_node *new_parent,
        old_name = kernfs_rcu_name(kn);
        if (!new_name)
                new_name = old_name;
-       if ((old_parent == new_parent) && (kn->ns == new_ns) &&
+       if ((old_parent == new_parent) &&
+           (kernfs_ns_id(kn->ns) == kernfs_ns_id(new_ns)) &&
            (strcmp(old_name, new_name) == 0))
                goto out;       /* nothing to rename */
 
@@ -1861,7 +1878,8 @@ static struct kernfs_node *kernfs_dir_pos(const struct ns_common *ns,
                }
        }
        /* Skip over entries which are dying/dead or in the wrong namespace */
-       while (pos && (!kernfs_active(pos) || pos->ns != ns)) {
+       while (pos && (!kernfs_active(pos) ||
+                      kernfs_ns_id(pos->ns) != kernfs_ns_id(ns))) {
                struct rb_node *node = rb_next(&pos->rb);
                if (!node)
                        pos = NULL;
@@ -1882,7 +1900,8 @@ static struct kernfs_node *kernfs_dir_next_pos(const struct ns_common *ns,
                                pos = NULL;
                        else
                                pos = rb_to_kn(node);
-               } while (pos && (!kernfs_active(pos) || pos->ns != ns));
+               } while (pos && (!kernfs_active(pos) ||
+                       kernfs_ns_id(pos->ns) != kernfs_ns_id(ns)));
        }
        return pos;
 }