summaryrefslogtreecommitdiff
path: root/fs
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-04-10 08:40:49 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2026-04-10 08:40:49 -0700
commit7c6c4ed80b874f721bc7c2c937e098c56e37d2f0 (patch)
tree6e388c10e8f3a4edf98bdf04c0ae7b8fdb81c5cd /fs
parent96463e4e0268dddbdb60fd1b96800736aa2bade9 (diff)
parentcb76a81c7cec37bdf525164561b02665cd763421 (diff)
downloadlinux-7c6c4ed80b874f721bc7c2c937e098c56e37d2f0.tar.gz
linux-7c6c4ed80b874f721bc7c2c937e098c56e37d2f0.zip
Merge tag 'vfs-7.0-rc8.fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs
Pull vfs fixes from Christian Brauner: "The kernfs rbtree is keyed by (hash, ns, name) where the hash is seeded with the raw namespace pointer via init_name_hash(ns). The resulting hash values are exposed to userspace through readdir seek positions, and the pointer-based ordering in kernfs_name_compare() is observable through entry order. Switch from raw pointers to ns_common::ns_id for both hashing and comparison. A preparatory commit first replaces all const void * namespace parameters with const struct ns_common * throughout kernfs, sysfs, and kobject so the code can access ns->ns_id. Also compare the ns_id when hashes match in the rbtree to handle crafted collisions. Also fix eventpoll RCU grace period issue and a cachefiles refcount problem" * tag 'vfs-7.0-rc8.fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs: kernfs: make directory seek namespace-aware kernfs: use namespace id instead of pointer for hashing and comparison kernfs: pass struct ns_common instead of const void * for namespace tags eventpoll: defer struct eventpoll free to RCU grace period cachefiles: fix incorrect dentry refcount in cachefiles_cull()
Diffstat (limited to 'fs')
-rw-r--r--fs/cachefiles/namei.c5
-rw-r--r--fs/eventpoll.c6
-rw-r--r--fs/kernfs/dir.c68
-rw-r--r--fs/kernfs/file.c2
-rw-r--r--fs/kernfs/kernfs-internal.h2
-rw-r--r--fs/kernfs/mount.c2
-rw-r--r--fs/nfs/sysfs.c16
-rw-r--r--fs/sysfs/dir.c6
-rw-r--r--fs/sysfs/file.c8
-rw-r--r--fs/sysfs/mount.c10
-rw-r--r--fs/sysfs/symlink.c7
-rw-r--r--fs/sysfs/sysfs.h4
12 files changed, 90 insertions, 46 deletions
diff --git a/fs/cachefiles/namei.c b/fs/cachefiles/namei.c
index e5ec90dccc27f..eb9eb7683e3cc 100644
--- a/fs/cachefiles/namei.c
+++ b/fs/cachefiles/namei.c
@@ -810,6 +810,11 @@ int cachefiles_cull(struct cachefiles_cache *cache, struct dentry *dir,
if (ret < 0)
goto error_unlock;
+ /*
+ * cachefiles_bury_object() expects 2 references to 'victim',
+ * and drops one.
+ */
+ dget(victim);
ret = cachefiles_bury_object(cache, NULL, dir, victim,
FSCACHE_OBJECT_WAS_CULLED);
dput(victim);
diff --git a/fs/eventpoll.c b/fs/eventpoll.c
index 5714e900567c4..4b43bf41296d4 100644
--- a/fs/eventpoll.c
+++ b/fs/eventpoll.c
@@ -226,6 +226,9 @@ struct eventpoll {
*/
refcount_t refcount;
+ /* used to defer freeing past ep_get_upwards_depth_proc() RCU walk */
+ struct rcu_head rcu;
+
#ifdef CONFIG_NET_RX_BUSY_POLL
/* used to track busy poll napi_id */
unsigned int napi_id;
@@ -819,7 +822,8 @@ static void ep_free(struct eventpoll *ep)
mutex_destroy(&ep->mtx);
free_uid(ep->user);
wakeup_source_unregister(ep->ws);
- kfree(ep);
+ /* ep_get_upwards_depth_proc() may still hold epi->ep under RCU */
+ kfree_rcu(ep, rcu);
}
/*
diff --git a/fs/kernfs/dir.c b/fs/kernfs/dir.c
index 8d40c4b1db9ff..22a4dff2a3af5 100644
--- a/fs/kernfs/dir.c
+++ b/fs/kernfs/dir.c
@@ -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
@@ -313,9 +326,10 @@ struct kernfs_node *kernfs_get_parent(struct kernfs_node *kn)
*
* Return: 31-bit hash of ns + name (so it fits in an off_t)
*/
-static unsigned int kernfs_name_hash(const char *name, const void *ns)
+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);
@@ -330,15 +344,18 @@ static unsigned int kernfs_name_hash(const char *name, const void *ns)
}
static int kernfs_name_compare(unsigned int hash, const char *name,
- const void *ns, const struct kernfs_node *kn)
+ 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));
}
@@ -856,7 +873,7 @@ out_unlock:
*/
static struct kernfs_node *kernfs_find_ns(struct kernfs_node *parent,
const unsigned char *name,
- const void *ns)
+ const struct ns_common *ns)
{
struct rb_node *node = parent->dir.children.rb_node;
bool has_ns = kernfs_ns_enabled(parent);
@@ -889,7 +906,7 @@ static struct kernfs_node *kernfs_find_ns(struct kernfs_node *parent,
static struct kernfs_node *kernfs_walk_ns(struct kernfs_node *parent,
const unsigned char *path,
- const void *ns)
+ const struct ns_common *ns)
{
ssize_t len;
char *p, *name;
@@ -930,7 +947,8 @@ static struct kernfs_node *kernfs_walk_ns(struct kernfs_node *parent,
* Return: pointer to the found kernfs_node on success, %NULL on failure.
*/
struct kernfs_node *kernfs_find_and_get_ns(struct kernfs_node *parent,
- const char *name, const void *ns)
+ const char *name,
+ const struct ns_common *ns)
{
struct kernfs_node *kn;
struct kernfs_root *root = kernfs_root(parent);
@@ -956,7 +974,8 @@ EXPORT_SYMBOL_GPL(kernfs_find_and_get_ns);
* Return: pointer to the found kernfs_node on success, %NULL on failure.
*/
struct kernfs_node *kernfs_walk_and_get_ns(struct kernfs_node *parent,
- const char *path, const void *ns)
+ const char *path,
+ const struct ns_common *ns)
{
struct kernfs_node *kn;
struct kernfs_root *root = kernfs_root(parent);
@@ -1079,7 +1098,8 @@ struct kernfs_node *kernfs_root_to_node(struct kernfs_root *root)
struct kernfs_node *kernfs_create_dir_ns(struct kernfs_node *parent,
const char *name, umode_t mode,
kuid_t uid, kgid_t gid,
- void *priv, const void *ns)
+ void *priv,
+ const struct ns_common *ns)
{
struct kernfs_node *kn;
int rc;
@@ -1199,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);
@@ -1221,7 +1241,7 @@ static struct dentry *kernfs_iop_lookup(struct inode *dir,
struct kernfs_node *kn;
struct kernfs_root *root;
struct inode *inode = NULL;
- const void *ns = NULL;
+ const struct ns_common *ns = NULL;
root = kernfs_root(parent);
down_read(&root->kernfs_rwsem);
@@ -1702,7 +1722,7 @@ bool kernfs_remove_self(struct kernfs_node *kn)
* Return: %0 on success, -ENOENT if such entry doesn't exist.
*/
int kernfs_remove_by_name_ns(struct kernfs_node *parent, const char *name,
- const void *ns)
+ const struct ns_common *ns)
{
struct kernfs_node *kn;
struct kernfs_root *root;
@@ -1741,7 +1761,7 @@ int kernfs_remove_by_name_ns(struct kernfs_node *parent, const char *name,
* Return: %0 on success, -errno on failure.
*/
int kernfs_rename_ns(struct kernfs_node *kn, struct kernfs_node *new_parent,
- const char *new_name, const void *new_ns)
+ const char *new_name, const struct ns_common *new_ns)
{
struct kernfs_node *old_parent;
struct kernfs_root *root;
@@ -1771,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 */
@@ -1832,7 +1853,7 @@ static int kernfs_dir_fop_release(struct inode *inode, struct file *filp)
return 0;
}
-static struct kernfs_node *kernfs_dir_pos(const void *ns,
+static struct kernfs_node *kernfs_dir_pos(const struct ns_common *ns,
struct kernfs_node *parent, loff_t hash, struct kernfs_node *pos)
{
if (pos) {
@@ -1845,6 +1866,7 @@ static struct kernfs_node *kernfs_dir_pos(const void *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);
@@ -1852,12 +1874,17 @@ static struct kernfs_node *kernfs_dir_pos(const void *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;
}
}
/* 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;
@@ -1867,7 +1894,7 @@ static struct kernfs_node *kernfs_dir_pos(const void *ns,
return pos;
}
-static struct kernfs_node *kernfs_dir_next_pos(const void *ns,
+static struct kernfs_node *kernfs_dir_next_pos(const struct ns_common *ns,
struct kernfs_node *parent, ino_t ino, struct kernfs_node *pos)
{
pos = kernfs_dir_pos(ns, parent, ino, pos);
@@ -1878,7 +1905,8 @@ static struct kernfs_node *kernfs_dir_next_pos(const void *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;
}
@@ -1889,7 +1917,7 @@ static int kernfs_fop_readdir(struct file *file, struct dir_context *ctx)
struct kernfs_node *parent = kernfs_dentry_node(dentry);
struct kernfs_node *pos = file->private_data;
struct kernfs_root *root;
- const void *ns = NULL;
+ const struct ns_common *ns = NULL;
if (!dir_emit_dots(file, ctx))
return 0;
diff --git a/fs/kernfs/file.c b/fs/kernfs/file.c
index e32406d62c0d3..1163aa7697384 100644
--- a/fs/kernfs/file.c
+++ b/fs/kernfs/file.c
@@ -1045,7 +1045,7 @@ struct kernfs_node *__kernfs_create_file(struct kernfs_node *parent,
umode_t mode, kuid_t uid, kgid_t gid,
loff_t size,
const struct kernfs_ops *ops,
- void *priv, const void *ns,
+ void *priv, const struct ns_common *ns,
struct lock_class_key *key)
{
struct kernfs_node *kn;
diff --git a/fs/kernfs/kernfs-internal.h b/fs/kernfs/kernfs-internal.h
index 6061b6f70d2a3..b1fd9622a5e34 100644
--- a/fs/kernfs/kernfs-internal.h
+++ b/fs/kernfs/kernfs-internal.h
@@ -97,7 +97,7 @@ struct kernfs_super_info {
* instance. If multiple tags become necessary, make the following
* an array and compare kernfs_node tag against every entry.
*/
- const void *ns;
+ const struct ns_common *ns;
/* anchored at kernfs_root->supers, protected by kernfs_rwsem */
struct list_head node;
diff --git a/fs/kernfs/mount.c b/fs/kernfs/mount.c
index 048f00b73b717..6e3217b6e4811 100644
--- a/fs/kernfs/mount.c
+++ b/fs/kernfs/mount.c
@@ -345,7 +345,7 @@ static int kernfs_set_super(struct super_block *sb, struct fs_context *fc)
*
* Return: the namespace tag associated with kernfs super_block @sb.
*/
-const void *kernfs_super_ns(struct super_block *sb)
+const struct ns_common *kernfs_super_ns(struct super_block *sb)
{
struct kernfs_super_info *info = kernfs_info(sb);
diff --git a/fs/nfs/sysfs.c b/fs/nfs/sysfs.c
index 7d8921f524a69..1da4f707f9efe 100644
--- a/fs/nfs/sysfs.c
+++ b/fs/nfs/sysfs.c
@@ -11,6 +11,7 @@
#include <linux/netdevice.h>
#include <linux/string.h>
#include <linux/nfs_fs.h>
+#include <net/net_namespace.h>
#include <linux/rcupdate.h>
#include <linux/lockd/lockd.h>
@@ -127,9 +128,10 @@ static void nfs_netns_client_release(struct kobject *kobj)
kfree(rcu_dereference_raw(c->identifier));
}
-static const void *nfs_netns_client_namespace(const struct kobject *kobj)
+static const struct ns_common *nfs_netns_client_namespace(const struct kobject *kobj)
{
- return container_of(kobj, struct nfs_netns_client, kobject)->net;
+ return to_ns_common(container_of(kobj, struct nfs_netns_client,
+ kobject)->net);
}
static struct kobj_attribute nfs_netns_client_id = __ATTR(identifier,
@@ -156,9 +158,10 @@ static void nfs_netns_object_release(struct kobject *kobj)
kfree(c);
}
-static const void *nfs_netns_namespace(const struct kobject *kobj)
+static const struct ns_common *nfs_netns_namespace(const struct kobject *kobj)
{
- return container_of(kobj, struct nfs_netns_client, nfs_net_kobj)->net;
+ return to_ns_common(container_of(kobj, struct nfs_netns_client,
+ nfs_net_kobj)->net);
}
static struct kobj_type nfs_netns_object_type = {
@@ -350,9 +353,10 @@ static void nfs_sysfs_sb_release(struct kobject *kobj)
/* no-op: why? see lib/kobject.c kobject_cleanup() */
}
-static const void *nfs_netns_server_namespace(const struct kobject *kobj)
+static const struct ns_common *nfs_netns_server_namespace(const struct kobject *kobj)
{
- return container_of(kobj, struct nfs_server, kobj)->nfs_client->cl_net;
+ return to_ns_common(container_of(kobj, struct nfs_server,
+ kobj)->nfs_client->cl_net);
}
static struct kobj_type nfs_sb_ktype = {
diff --git a/fs/sysfs/dir.c b/fs/sysfs/dir.c
index 94e12efd92f21..ffdcd4153c584 100644
--- a/fs/sysfs/dir.c
+++ b/fs/sysfs/dir.c
@@ -37,7 +37,7 @@ void sysfs_warn_dup(struct kernfs_node *parent, const char *name)
* @kobj: object we're creating directory for
* @ns: the namespace tag to use
*/
-int sysfs_create_dir_ns(struct kobject *kobj, const void *ns)
+int sysfs_create_dir_ns(struct kobject *kobj, const struct ns_common *ns)
{
struct kernfs_node *parent, *kn;
kuid_t uid;
@@ -103,7 +103,7 @@ void sysfs_remove_dir(struct kobject *kobj)
}
int sysfs_rename_dir_ns(struct kobject *kobj, const char *new_name,
- const void *new_ns)
+ const struct ns_common *new_ns)
{
struct kernfs_node *parent;
int ret;
@@ -115,7 +115,7 @@ int sysfs_rename_dir_ns(struct kobject *kobj, const char *new_name,
}
int sysfs_move_dir_ns(struct kobject *kobj, struct kobject *new_parent_kobj,
- const void *new_ns)
+ const struct ns_common *new_ns)
{
struct kernfs_node *kn = kobj->sd;
struct kernfs_node *new_parent;
diff --git a/fs/sysfs/file.c b/fs/sysfs/file.c
index a8176c875f55e..5709cede1d756 100644
--- a/fs/sysfs/file.c
+++ b/fs/sysfs/file.c
@@ -272,7 +272,7 @@ static const struct kernfs_ops sysfs_bin_kfops_mmap = {
int sysfs_add_file_mode_ns(struct kernfs_node *parent,
const struct attribute *attr, umode_t mode, kuid_t uid,
- kgid_t gid, const void *ns)
+ kgid_t gid, const struct ns_common *ns)
{
struct kobject *kobj = parent->priv;
const struct sysfs_ops *sysfs_ops = kobj->ktype->sysfs_ops;
@@ -322,7 +322,7 @@ int sysfs_add_file_mode_ns(struct kernfs_node *parent,
int sysfs_add_bin_file_mode_ns(struct kernfs_node *parent,
const struct bin_attribute *battr, umode_t mode, size_t size,
- kuid_t uid, kgid_t gid, const void *ns)
+ kuid_t uid, kgid_t gid, const struct ns_common *ns)
{
const struct attribute *attr = &battr->attr;
struct lock_class_key *key = NULL;
@@ -362,7 +362,7 @@ int sysfs_add_bin_file_mode_ns(struct kernfs_node *parent,
* @ns: namespace the new file should belong to
*/
int sysfs_create_file_ns(struct kobject *kobj, const struct attribute *attr,
- const void *ns)
+ const struct ns_common *ns)
{
kuid_t uid;
kgid_t gid;
@@ -505,7 +505,7 @@ EXPORT_SYMBOL_GPL(sysfs_unbreak_active_protection);
* Hash the attribute name and namespace tag and kill the victim.
*/
void sysfs_remove_file_ns(struct kobject *kobj, const struct attribute *attr,
- const void *ns)
+ const struct ns_common *ns)
{
struct kernfs_node *parent = kobj->sd;
diff --git a/fs/sysfs/mount.c b/fs/sysfs/mount.c
index e65c60158a04a..b199e8ff79b1f 100644
--- a/fs/sysfs/mount.c
+++ b/fs/sysfs/mount.c
@@ -55,7 +55,7 @@ static const struct fs_context_operations sysfs_fs_context_ops = {
static int sysfs_init_fs_context(struct fs_context *fc)
{
struct kernfs_fs_context *kfc;
- struct net *netns;
+ struct ns_common *ns;
if (!(fc->sb_flags & SB_KERNMOUNT)) {
if (!kobj_ns_current_may_mount(KOBJ_NS_TYPE_NET))
@@ -66,12 +66,14 @@ static int sysfs_init_fs_context(struct fs_context *fc)
if (!kfc)
return -ENOMEM;
- kfc->ns_tag = netns = kobj_ns_grab_current(KOBJ_NS_TYPE_NET);
+ kfc->ns_tag = ns = kobj_ns_grab_current(KOBJ_NS_TYPE_NET);
kfc->root = sysfs_root;
kfc->magic = SYSFS_MAGIC;
fc->fs_private = kfc;
fc->ops = &sysfs_fs_context_ops;
- if (netns) {
+ if (ns) {
+ struct net *netns = to_net_ns(ns);
+
put_user_ns(fc->user_ns);
fc->user_ns = get_user_ns(netns->user_ns);
}
@@ -81,7 +83,7 @@ static int sysfs_init_fs_context(struct fs_context *fc)
static void sysfs_kill_sb(struct super_block *sb)
{
- void *ns = (void *)kernfs_super_ns(sb);
+ struct ns_common *ns = (struct ns_common *)kernfs_super_ns(sb);
kernfs_kill_sb(sb);
kobj_ns_drop(KOBJ_NS_TYPE_NET, ns);
diff --git a/fs/sysfs/symlink.c b/fs/sysfs/symlink.c
index 5603530a1a520..5f9c05fb13940 100644
--- a/fs/sysfs/symlink.c
+++ b/fs/sysfs/symlink.c
@@ -121,7 +121,7 @@ EXPORT_SYMBOL_GPL(sysfs_create_link_nowarn);
void sysfs_delete_link(struct kobject *kobj, struct kobject *targ,
const char *name)
{
- const void *ns = NULL;
+ const struct ns_common *ns = NULL;
/*
* We don't own @target and it may be removed at any time.
@@ -164,10 +164,11 @@ EXPORT_SYMBOL_GPL(sysfs_remove_link);
* A helper function for the common rename symlink idiom.
*/
int sysfs_rename_link_ns(struct kobject *kobj, struct kobject *targ,
- const char *old, const char *new, const void *new_ns)
+ const char *old, const char *new,
+ const struct ns_common *new_ns)
{
struct kernfs_node *parent, *kn = NULL;
- const void *old_ns = NULL;
+ const struct ns_common *old_ns = NULL;
int result;
if (!kobj)
diff --git a/fs/sysfs/sysfs.h b/fs/sysfs/sysfs.h
index 8e012f25e1c06..f4583dcafcd1e 100644
--- a/fs/sysfs/sysfs.h
+++ b/fs/sysfs/sysfs.h
@@ -29,10 +29,10 @@ void sysfs_warn_dup(struct kernfs_node *parent, const char *name);
*/
int sysfs_add_file_mode_ns(struct kernfs_node *parent,
const struct attribute *attr, umode_t amode, kuid_t uid,
- kgid_t gid, const void *ns);
+ kgid_t gid, const struct ns_common *ns);
int sysfs_add_bin_file_mode_ns(struct kernfs_node *parent,
const struct bin_attribute *battr, umode_t mode, size_t size,
- kuid_t uid, kgid_t gid, const void *ns);
+ kuid_t uid, kgid_t gid, const struct ns_common *ns);
/*
* symlink.c