mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/
synced 2025-04-20 05:08:28 +09:00

Using RCU lifetime rules to access kernfs_node::name can avoid the trouble with kernfs_rename_lock in kernfs_name() and kernfs_path_from_node() if the fs was created with KERNFS_ROOT_INVARIANT_PARENT. This is usefull as it allows to implement kernfs_path_from_node() only with RCU protection and avoiding kernfs_rename_lock. The lock is only required if the __parent node can be changed and the function requires an unchanged hierarchy while it iterates from the node to its parent. The change is needed to allow the lookup of the node's path (kernfs_path_from_node()) from context which runs always with disabled preemption and or interrutps even on PREEMPT_RT. The problem is that kernfs_rename_lock becomes a sleeping lock on PREEMPT_RT. I went through all ::name users and added the required access for the lookup with a few extensions: - rdtgroup_pseudo_lock_create() drops all locks and then uses the name later on. resctrl supports rename with different parents. Here I made a temporal copy of the name while it is used outside of the lock. - kernfs_rename_ns() accepts NULL as new_parent. This simplifies sysfs_move_dir_ns() where it can set NULL in order to reuse the current name. - kernfs_rename_ns() is only using kernfs_rename_lock if the parents are different. All users use either kernfs_rwsem (for stable path view) or just RCU for the lookup. The ::name uses always RCU free. Use RCU lifetime guarantees to access kernfs_node::name. Suggested-by: Tejun Heo <tj@kernel.org> Acked-by: Tejun Heo <tj@kernel.org> Reported-by: syzbot+6ea37e2e6ffccf41a7e6@syzkaller.appspotmail.com Closes: https://lore.kernel.org/lkml/67251dc6.050a0220.529b6.015e.GAE@google.com/ Reported-by: Hillf Danton <hdanton@sina.com> Closes: https://lore.kernel.org/20241102001224.2789-1-hdanton@sina.com Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de> Link: https://lore.kernel.org/r/20250213145023.2820193-7-bigeasy@linutronix.de Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
162 lines
4.1 KiB
C
162 lines
4.1 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* fs/sysfs/dir.c - sysfs core and dir operation implementation
|
|
*
|
|
* Copyright (c) 2001-3 Patrick Mochel
|
|
* Copyright (c) 2007 SUSE Linux Products GmbH
|
|
* Copyright (c) 2007 Tejun Heo <teheo@suse.de>
|
|
*
|
|
* Please see Documentation/filesystems/sysfs.rst for more information.
|
|
*/
|
|
|
|
#define pr_fmt(fmt) "sysfs: " fmt
|
|
|
|
#include <linux/fs.h>
|
|
#include <linux/kobject.h>
|
|
#include <linux/slab.h>
|
|
#include "sysfs.h"
|
|
|
|
DEFINE_SPINLOCK(sysfs_symlink_target_lock);
|
|
|
|
void sysfs_warn_dup(struct kernfs_node *parent, const char *name)
|
|
{
|
|
char *buf;
|
|
|
|
buf = kzalloc(PATH_MAX, GFP_KERNEL);
|
|
if (buf)
|
|
kernfs_path(parent, buf, PATH_MAX);
|
|
|
|
pr_warn("cannot create duplicate filename '%s/%s'\n", buf, name);
|
|
dump_stack();
|
|
|
|
kfree(buf);
|
|
}
|
|
|
|
/**
|
|
* sysfs_create_dir_ns - create a directory for an object with a namespace tag
|
|
* @kobj: object we're creating directory for
|
|
* @ns: the namespace tag to use
|
|
*/
|
|
int sysfs_create_dir_ns(struct kobject *kobj, const void *ns)
|
|
{
|
|
struct kernfs_node *parent, *kn;
|
|
kuid_t uid;
|
|
kgid_t gid;
|
|
|
|
if (WARN_ON(!kobj))
|
|
return -EINVAL;
|
|
|
|
if (kobj->parent)
|
|
parent = kobj->parent->sd;
|
|
else
|
|
parent = sysfs_root_kn;
|
|
|
|
if (!parent)
|
|
return -ENOENT;
|
|
|
|
kobject_get_ownership(kobj, &uid, &gid);
|
|
|
|
kn = kernfs_create_dir_ns(parent, kobject_name(kobj), 0755, uid, gid,
|
|
kobj, ns);
|
|
if (IS_ERR(kn)) {
|
|
if (PTR_ERR(kn) == -EEXIST)
|
|
sysfs_warn_dup(parent, kobject_name(kobj));
|
|
return PTR_ERR(kn);
|
|
}
|
|
|
|
kobj->sd = kn;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* sysfs_remove_dir - remove an object's directory.
|
|
* @kobj: object.
|
|
*
|
|
* The only thing special about this is that we remove any files in
|
|
* the directory before we remove the directory, and we've inlined
|
|
* what used to be sysfs_rmdir() below, instead of calling separately.
|
|
*/
|
|
void sysfs_remove_dir(struct kobject *kobj)
|
|
{
|
|
struct kernfs_node *kn = kobj->sd;
|
|
|
|
/*
|
|
* In general, kobject owner is responsible for ensuring removal
|
|
* doesn't race with other operations and sysfs doesn't provide any
|
|
* protection; however, when @kobj is used as a symlink target, the
|
|
* symlinking entity usually doesn't own @kobj and thus has no
|
|
* control over removal. @kobj->sd may be removed anytime
|
|
* and symlink code may end up dereferencing an already freed node.
|
|
*
|
|
* sysfs_symlink_target_lock synchronizes @kobj->sd
|
|
* disassociation against symlink operations so that symlink code
|
|
* can safely dereference @kobj->sd.
|
|
*/
|
|
spin_lock(&sysfs_symlink_target_lock);
|
|
kobj->sd = NULL;
|
|
spin_unlock(&sysfs_symlink_target_lock);
|
|
|
|
if (kn) {
|
|
WARN_ON_ONCE(kernfs_type(kn) != KERNFS_DIR);
|
|
kernfs_remove(kn);
|
|
}
|
|
}
|
|
|
|
int sysfs_rename_dir_ns(struct kobject *kobj, const char *new_name,
|
|
const void *new_ns)
|
|
{
|
|
struct kernfs_node *parent;
|
|
int ret;
|
|
|
|
parent = kernfs_get_parent(kobj->sd);
|
|
ret = kernfs_rename_ns(kobj->sd, parent, new_name, new_ns);
|
|
kernfs_put(parent);
|
|
return ret;
|
|
}
|
|
|
|
int sysfs_move_dir_ns(struct kobject *kobj, struct kobject *new_parent_kobj,
|
|
const void *new_ns)
|
|
{
|
|
struct kernfs_node *kn = kobj->sd;
|
|
struct kernfs_node *new_parent;
|
|
|
|
new_parent = new_parent_kobj && new_parent_kobj->sd ?
|
|
new_parent_kobj->sd : sysfs_root_kn;
|
|
|
|
return kernfs_rename_ns(kn, new_parent, NULL, new_ns);
|
|
}
|
|
|
|
/**
|
|
* sysfs_create_mount_point - create an always empty directory
|
|
* @parent_kobj: kobject that will contain this always empty directory
|
|
* @name: The name of the always empty directory to add
|
|
*/
|
|
int sysfs_create_mount_point(struct kobject *parent_kobj, const char *name)
|
|
{
|
|
struct kernfs_node *kn, *parent = parent_kobj->sd;
|
|
|
|
kn = kernfs_create_empty_dir(parent, name);
|
|
if (IS_ERR(kn)) {
|
|
if (PTR_ERR(kn) == -EEXIST)
|
|
sysfs_warn_dup(parent, name);
|
|
return PTR_ERR(kn);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(sysfs_create_mount_point);
|
|
|
|
/**
|
|
* sysfs_remove_mount_point - remove an always empty directory.
|
|
* @parent_kobj: kobject that will contain this always empty directory
|
|
* @name: The name of the always empty directory to remove
|
|
*
|
|
*/
|
|
void sysfs_remove_mount_point(struct kobject *parent_kobj, const char *name)
|
|
{
|
|
struct kernfs_node *parent = parent_kobj->sd;
|
|
|
|
kernfs_remove_by_name_ns(parent, name, NULL);
|
|
}
|
|
EXPORT_SYMBOL_GPL(sysfs_remove_mount_point);
|