mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-11-18 17:54:13 +08:00
ba80aa909c
This patch closes a long standing race in configfs between the creation of a new symlink in create_link(), while the symlink target's config_item is being concurrently removed via configfs_rmdir(). This can happen because the symlink target's reference is obtained by config_item_get() in create_link() before the CONFIGFS_USET_DROPPING bit set by configfs_detach_prep() during configfs_rmdir() shutdown is actually checked.. This originally manifested itself on ppc64 on v4.8.y under heavy load using ibmvscsi target ports with Novalink API: [ 7877.289863] rpadlpar_io: slot U8247.22L.212A91A-V1-C8 added [ 7879.893760] ------------[ cut here ]------------ [ 7879.893768] WARNING: CPU: 15 PID: 17585 at ./include/linux/kref.h:46 config_item_get+0x7c/0x90 [configfs] [ 7879.893811] CPU: 15 PID: 17585 Comm: targetcli Tainted: G O 4.8.17-customv2.22 #12 [ 7879.893812] task: c00000018a0d3400 task.stack: c0000001f3b40000 [ 7879.893813] NIP: d000000002c664ec LR: d000000002c60980 CTR: c000000000b70870 [ 7879.893814] REGS: c0000001f3b43810 TRAP: 0700 Tainted: G O (4.8.17-customv2.22) [ 7879.893815] MSR: 8000000000029033 <SF,EE,ME,IR,DR,RI,LE> CR: 28222242 XER: 00000000 [ 7879.893820] CFAR: d000000002c664bc SOFTE: 1 GPR00: d000000002c60980 c0000001f3b43a90 d000000002c70908 c0000000fbc06820 GPR04: c0000001ef1bd900 0000000000000004 0000000000000001 0000000000000000 GPR08: 0000000000000000 0000000000000001 d000000002c69560 d000000002c66d80 GPR12: c000000000b70870 c00000000e798700 c0000001f3b43ca0 c0000001d4949d40 GPR16: c00000014637e1c0 0000000000000000 0000000000000000 c0000000f2392940 GPR20: c0000001f3b43b98 0000000000000041 0000000000600000 0000000000000000 GPR24: fffffffffffff000 0000000000000000 d000000002c60be0 c0000001f1dac490 GPR28: 0000000000000004 0000000000000000 c0000001ef1bd900 c0000000f2392940 [ 7879.893839] NIP [d000000002c664ec] config_item_get+0x7c/0x90 [configfs] [ 7879.893841] LR [d000000002c60980] check_perm+0x80/0x2e0 [configfs] [ 7879.893842] Call Trace: [ 7879.893844] [c0000001f3b43ac0] [d000000002c60980] check_perm+0x80/0x2e0 [configfs] [ 7879.893847] [c0000001f3b43b10] [c000000000329770] do_dentry_open+0x2c0/0x460 [ 7879.893849] [c0000001f3b43b70] [c000000000344480] path_openat+0x210/0x1490 [ 7879.893851] [c0000001f3b43c80] [c00000000034708c] do_filp_open+0xfc/0x170 [ 7879.893853] [c0000001f3b43db0] [c00000000032b5bc] do_sys_open+0x1cc/0x390 [ 7879.893856] [c0000001f3b43e30] [c000000000009584] system_call+0x38/0xec [ 7879.893856] Instruction dump: [ 7879.893858] 409d0014 38210030 e8010010 7c0803a6 4e800020 3d220000 e94981e0 892a0000 [ 7879.893861] 2f890000 409effe0 39200001 992a0000 <0fe00000> 4bffffd0 60000000 60000000 [ 7879.893866] ---[ end trace 14078f0b3b5ad0aa ]--- To close this race, go ahead and obtain the symlink's target config_item reference only after the existing CONFIGFS_USET_DROPPING check succeeds. This way, if configfs_rmdir() wins create_link() will return -ENONET, and if create_link() wins configfs_rmdir() will return -EBUSY. Reported-by: Bryant G. Ly <bryantly@linux.vnet.ibm.com> Tested-by: Bryant G. Ly <bryantly@linux.vnet.ibm.com> Signed-off-by: Nicholas Bellinger <nab@linux-iscsi.org> Signed-off-by: Christoph Hellwig <hch@lst.de> Cc: stable@vger.kernel.org
310 lines
7.2 KiB
C
310 lines
7.2 KiB
C
/* -*- mode: c; c-basic-offset: 8; -*-
|
|
* vim: noexpandtab sw=8 ts=8 sts=0:
|
|
*
|
|
* symlink.c - operations for configfs symlinks.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public
|
|
* License along with this program; if not, write to the
|
|
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
|
* Boston, MA 021110-1307, USA.
|
|
*
|
|
* Based on sysfs:
|
|
* sysfs is Copyright (C) 2001, 2002, 2003 Patrick Mochel
|
|
*
|
|
* configfs Copyright (C) 2005 Oracle. All rights reserved.
|
|
*/
|
|
|
|
#include <linux/fs.h>
|
|
#include <linux/module.h>
|
|
#include <linux/namei.h>
|
|
#include <linux/slab.h>
|
|
|
|
#include <linux/configfs.h>
|
|
#include "configfs_internal.h"
|
|
|
|
/* Protects attachments of new symlinks */
|
|
DEFINE_MUTEX(configfs_symlink_mutex);
|
|
|
|
static int item_depth(struct config_item * item)
|
|
{
|
|
struct config_item * p = item;
|
|
int depth = 0;
|
|
do { depth++; } while ((p = p->ci_parent) && !configfs_is_root(p));
|
|
return depth;
|
|
}
|
|
|
|
static int item_path_length(struct config_item * item)
|
|
{
|
|
struct config_item * p = item;
|
|
int length = 1;
|
|
do {
|
|
length += strlen(config_item_name(p)) + 1;
|
|
p = p->ci_parent;
|
|
} while (p && !configfs_is_root(p));
|
|
return length;
|
|
}
|
|
|
|
static void fill_item_path(struct config_item * item, char * buffer, int length)
|
|
{
|
|
struct config_item * p;
|
|
|
|
--length;
|
|
for (p = item; p && !configfs_is_root(p); p = p->ci_parent) {
|
|
int cur = strlen(config_item_name(p));
|
|
|
|
/* back up enough to print this bus id with '/' */
|
|
length -= cur;
|
|
strncpy(buffer + length,config_item_name(p),cur);
|
|
*(buffer + --length) = '/';
|
|
}
|
|
}
|
|
|
|
static int create_link(struct config_item *parent_item,
|
|
struct config_item *item,
|
|
struct dentry *dentry)
|
|
{
|
|
struct configfs_dirent *target_sd = item->ci_dentry->d_fsdata;
|
|
struct configfs_symlink *sl;
|
|
int ret;
|
|
|
|
ret = -ENOENT;
|
|
if (!configfs_dirent_is_ready(target_sd))
|
|
goto out;
|
|
ret = -ENOMEM;
|
|
sl = kmalloc(sizeof(struct configfs_symlink), GFP_KERNEL);
|
|
if (sl) {
|
|
spin_lock(&configfs_dirent_lock);
|
|
if (target_sd->s_type & CONFIGFS_USET_DROPPING) {
|
|
spin_unlock(&configfs_dirent_lock);
|
|
kfree(sl);
|
|
return -ENOENT;
|
|
}
|
|
sl->sl_target = config_item_get(item);
|
|
list_add(&sl->sl_list, &target_sd->s_links);
|
|
spin_unlock(&configfs_dirent_lock);
|
|
ret = configfs_create_link(sl, parent_item->ci_dentry,
|
|
dentry);
|
|
if (ret) {
|
|
spin_lock(&configfs_dirent_lock);
|
|
list_del_init(&sl->sl_list);
|
|
spin_unlock(&configfs_dirent_lock);
|
|
config_item_put(item);
|
|
kfree(sl);
|
|
}
|
|
}
|
|
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int get_target(const char *symname, struct path *path,
|
|
struct config_item **target, struct super_block *sb)
|
|
{
|
|
int ret;
|
|
|
|
ret = kern_path(symname, LOOKUP_FOLLOW|LOOKUP_DIRECTORY, path);
|
|
if (!ret) {
|
|
if (path->dentry->d_sb == sb) {
|
|
*target = configfs_get_config_item(path->dentry);
|
|
if (!*target) {
|
|
ret = -ENOENT;
|
|
path_put(path);
|
|
}
|
|
} else {
|
|
ret = -EPERM;
|
|
path_put(path);
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
int configfs_symlink(struct inode *dir, struct dentry *dentry, const char *symname)
|
|
{
|
|
int ret;
|
|
struct path path;
|
|
struct configfs_dirent *sd;
|
|
struct config_item *parent_item;
|
|
struct config_item *target_item = NULL;
|
|
struct config_item_type *type;
|
|
|
|
sd = dentry->d_parent->d_fsdata;
|
|
/*
|
|
* Fake invisibility if dir belongs to a group/default groups hierarchy
|
|
* being attached
|
|
*/
|
|
ret = -ENOENT;
|
|
if (!configfs_dirent_is_ready(sd))
|
|
goto out;
|
|
|
|
parent_item = configfs_get_config_item(dentry->d_parent);
|
|
type = parent_item->ci_type;
|
|
|
|
ret = -EPERM;
|
|
if (!type || !type->ct_item_ops ||
|
|
!type->ct_item_ops->allow_link)
|
|
goto out_put;
|
|
|
|
ret = get_target(symname, &path, &target_item, dentry->d_sb);
|
|
if (ret)
|
|
goto out_put;
|
|
|
|
ret = type->ct_item_ops->allow_link(parent_item, target_item);
|
|
if (!ret) {
|
|
mutex_lock(&configfs_symlink_mutex);
|
|
ret = create_link(parent_item, target_item, dentry);
|
|
mutex_unlock(&configfs_symlink_mutex);
|
|
if (ret && type->ct_item_ops->drop_link)
|
|
type->ct_item_ops->drop_link(parent_item,
|
|
target_item);
|
|
}
|
|
|
|
config_item_put(target_item);
|
|
path_put(&path);
|
|
|
|
out_put:
|
|
config_item_put(parent_item);
|
|
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
int configfs_unlink(struct inode *dir, struct dentry *dentry)
|
|
{
|
|
struct configfs_dirent *sd = dentry->d_fsdata;
|
|
struct configfs_symlink *sl;
|
|
struct config_item *parent_item;
|
|
struct config_item_type *type;
|
|
int ret;
|
|
|
|
ret = -EPERM; /* What lack-of-symlink returns */
|
|
if (!(sd->s_type & CONFIGFS_ITEM_LINK))
|
|
goto out;
|
|
|
|
sl = sd->s_element;
|
|
|
|
parent_item = configfs_get_config_item(dentry->d_parent);
|
|
type = parent_item->ci_type;
|
|
|
|
spin_lock(&configfs_dirent_lock);
|
|
list_del_init(&sd->s_sibling);
|
|
spin_unlock(&configfs_dirent_lock);
|
|
configfs_drop_dentry(sd, dentry->d_parent);
|
|
dput(dentry);
|
|
configfs_put(sd);
|
|
|
|
/*
|
|
* drop_link() must be called before
|
|
* list_del_init(&sl->sl_list), so that the order of
|
|
* drop_link(this, target) and drop_item(target) is preserved.
|
|
*/
|
|
if (type && type->ct_item_ops &&
|
|
type->ct_item_ops->drop_link)
|
|
type->ct_item_ops->drop_link(parent_item,
|
|
sl->sl_target);
|
|
|
|
spin_lock(&configfs_dirent_lock);
|
|
list_del_init(&sl->sl_list);
|
|
spin_unlock(&configfs_dirent_lock);
|
|
|
|
/* Put reference from create_link() */
|
|
config_item_put(sl->sl_target);
|
|
kfree(sl);
|
|
|
|
config_item_put(parent_item);
|
|
|
|
ret = 0;
|
|
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
static int configfs_get_target_path(struct config_item * item, struct config_item * target,
|
|
char *path)
|
|
{
|
|
char * s;
|
|
int depth, size;
|
|
|
|
depth = item_depth(item);
|
|
size = item_path_length(target) + depth * 3 - 1;
|
|
if (size > PATH_MAX)
|
|
return -ENAMETOOLONG;
|
|
|
|
pr_debug("%s: depth = %d, size = %d\n", __func__, depth, size);
|
|
|
|
for (s = path; depth--; s += 3)
|
|
strcpy(s,"../");
|
|
|
|
fill_item_path(target, path, size);
|
|
pr_debug("%s: path = '%s'\n", __func__, path);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int configfs_getlink(struct dentry *dentry, char * path)
|
|
{
|
|
struct config_item *item, *target_item;
|
|
int error = 0;
|
|
|
|
item = configfs_get_config_item(dentry->d_parent);
|
|
if (!item)
|
|
return -EINVAL;
|
|
|
|
target_item = configfs_get_config_item(dentry);
|
|
if (!target_item) {
|
|
config_item_put(item);
|
|
return -EINVAL;
|
|
}
|
|
|
|
down_read(&configfs_rename_sem);
|
|
error = configfs_get_target_path(item, target_item, path);
|
|
up_read(&configfs_rename_sem);
|
|
|
|
config_item_put(item);
|
|
config_item_put(target_item);
|
|
return error;
|
|
|
|
}
|
|
|
|
static const char *configfs_get_link(struct dentry *dentry,
|
|
struct inode *inode,
|
|
struct delayed_call *done)
|
|
{
|
|
char *body;
|
|
int error;
|
|
|
|
if (!dentry)
|
|
return ERR_PTR(-ECHILD);
|
|
|
|
body = kzalloc(PAGE_SIZE, GFP_KERNEL);
|
|
if (!body)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
error = configfs_getlink(dentry, body);
|
|
if (!error) {
|
|
set_delayed_call(done, kfree_link, body);
|
|
return body;
|
|
}
|
|
|
|
kfree(body);
|
|
return ERR_PTR(error);
|
|
}
|
|
|
|
const struct inode_operations configfs_symlink_inode_operations = {
|
|
.get_link = configfs_get_link,
|
|
.setattr = configfs_setattr,
|
|
};
|
|
|