From: Christian Brauner Date: Wed, 1 Apr 2026 10:21:16 +0000 (+0200) Subject: kernfs: use namespace id instead of pointer for hashing and comparison X-Git-Tag: v7.0~17^2~1 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=1fe989e1c42a315c7e7918e7b812377137085036;p=thirdparty%2Fkernel%2Flinux.git kernfs: use namespace id instead of pointer for hashing and comparison 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 --- diff --git a/fs/kernfs/dir.c b/fs/kernfs/dir.c index be262145ae084..f5cf1d74b5f13 100644 --- a/fs/kernfs/dir.c +++ b/fs/kernfs/dir.c @@ -14,6 +14,7 @@ #include #include #include +#include #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; }