mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/chenhuacai/linux-loongson
synced 2025-08-28 00:19:36 +00:00
afs: Fix afs_atcell_get_link() to handle RCU pathwalk
The ->get_link() method may be entered under RCU pathwalk conditions (in
which case, the dentry pointer is NULL). This is not taken account of by
afs_atcell_get_link() and lockdep will complain when it tries to lock an
rwsem.
Fix this by marking net->ws_cell as __rcu and using RCU access macros on it
and by making afs_atcell_get_link() just return a pointer to the name in
RCU pathwalk without taking net->cells_lock or a ref on the cell as RCU
will protect the name storage (the cell is already freed via call_rcu()).
Fixes: 30bca65bbb
("afs: Make /afs/@cell and /afs/.@cell symlinks")
Reported-by: Alexander Viro <viro@zeniv.linux.org.uk>
Signed-off-by: David Howells <dhowells@redhat.com>
cc: Marc Dionne <marc.dionne@auristor.com>
cc: linux-afs@lists.infradead.org
cc: linux-fsdevel@vger.kernel.org
Link: https://lore.kernel.org/r/20250310094206.801057-2-dhowells@redhat.com/ # v4
This commit is contained in:
parent
1e15510b71
commit
823869e1e6
@ -64,7 +64,8 @@ static struct afs_cell *afs_find_cell_locked(struct afs_net *net,
|
|||||||
return ERR_PTR(-ENAMETOOLONG);
|
return ERR_PTR(-ENAMETOOLONG);
|
||||||
|
|
||||||
if (!name) {
|
if (!name) {
|
||||||
cell = net->ws_cell;
|
cell = rcu_dereference_protected(net->ws_cell,
|
||||||
|
lockdep_is_held(&net->cells_lock));
|
||||||
if (!cell)
|
if (!cell)
|
||||||
return ERR_PTR(-EDESTADDRREQ);
|
return ERR_PTR(-EDESTADDRREQ);
|
||||||
goto found;
|
goto found;
|
||||||
@ -388,8 +389,8 @@ int afs_cell_init(struct afs_net *net, const char *rootcell)
|
|||||||
/* install the new cell */
|
/* install the new cell */
|
||||||
down_write(&net->cells_lock);
|
down_write(&net->cells_lock);
|
||||||
afs_see_cell(new_root, afs_cell_trace_see_ws);
|
afs_see_cell(new_root, afs_cell_trace_see_ws);
|
||||||
old_root = net->ws_cell;
|
old_root = rcu_replace_pointer(net->ws_cell, new_root,
|
||||||
net->ws_cell = new_root;
|
lockdep_is_held(&net->cells_lock));
|
||||||
up_write(&net->cells_lock);
|
up_write(&net->cells_lock);
|
||||||
|
|
||||||
afs_unuse_cell(net, old_root, afs_cell_trace_unuse_ws);
|
afs_unuse_cell(net, old_root, afs_cell_trace_unuse_ws);
|
||||||
@ -945,8 +946,8 @@ void afs_cell_purge(struct afs_net *net)
|
|||||||
_enter("");
|
_enter("");
|
||||||
|
|
||||||
down_write(&net->cells_lock);
|
down_write(&net->cells_lock);
|
||||||
ws = net->ws_cell;
|
ws = rcu_replace_pointer(net->ws_cell, NULL,
|
||||||
net->ws_cell = NULL;
|
lockdep_is_held(&net->cells_lock));
|
||||||
up_write(&net->cells_lock);
|
up_write(&net->cells_lock);
|
||||||
afs_unuse_cell(net, ws, afs_cell_trace_unuse_ws);
|
afs_unuse_cell(net, ws, afs_cell_trace_unuse_ws);
|
||||||
|
|
||||||
|
@ -314,12 +314,23 @@ static const char *afs_atcell_get_link(struct dentry *dentry, struct inode *inod
|
|||||||
const char *name;
|
const char *name;
|
||||||
bool dotted = vnode->fid.vnode == 3;
|
bool dotted = vnode->fid.vnode == 3;
|
||||||
|
|
||||||
if (!net->ws_cell)
|
if (!dentry) {
|
||||||
|
/* We're in RCU-pathwalk. */
|
||||||
|
cell = rcu_dereference(net->ws_cell);
|
||||||
|
if (dotted)
|
||||||
|
name = cell->name - 1;
|
||||||
|
else
|
||||||
|
name = cell->name;
|
||||||
|
/* Shouldn't need to set a delayed call. */
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!rcu_access_pointer(net->ws_cell))
|
||||||
return ERR_PTR(-ENOENT);
|
return ERR_PTR(-ENOENT);
|
||||||
|
|
||||||
down_read(&net->cells_lock);
|
down_read(&net->cells_lock);
|
||||||
|
|
||||||
cell = net->ws_cell;
|
cell = rcu_dereference_protected(net->ws_cell, lockdep_is_held(&net->cells_lock));
|
||||||
if (dotted)
|
if (dotted)
|
||||||
name = cell->name - 1;
|
name = cell->name - 1;
|
||||||
else
|
else
|
||||||
|
@ -287,7 +287,7 @@ struct afs_net {
|
|||||||
|
|
||||||
/* Cell database */
|
/* Cell database */
|
||||||
struct rb_root cells;
|
struct rb_root cells;
|
||||||
struct afs_cell *ws_cell;
|
struct afs_cell __rcu *ws_cell;
|
||||||
struct work_struct cells_manager;
|
struct work_struct cells_manager;
|
||||||
struct timer_list cells_timer;
|
struct timer_list cells_timer;
|
||||||
atomic_t cells_outstanding;
|
atomic_t cells_outstanding;
|
||||||
|
@ -206,7 +206,7 @@ static int afs_proc_rootcell_show(struct seq_file *m, void *v)
|
|||||||
|
|
||||||
net = afs_seq2net_single(m);
|
net = afs_seq2net_single(m);
|
||||||
down_read(&net->cells_lock);
|
down_read(&net->cells_lock);
|
||||||
cell = net->ws_cell;
|
cell = rcu_dereference_protected(net->ws_cell, lockdep_is_held(&net->cells_lock));
|
||||||
if (cell)
|
if (cell)
|
||||||
seq_printf(m, "%s\n", cell->name);
|
seq_printf(m, "%s\n", cell->name);
|
||||||
up_read(&net->cells_lock);
|
up_read(&net->cells_lock);
|
||||||
@ -242,7 +242,7 @@ static int afs_proc_rootcell_write(struct file *file, char *buf, size_t size)
|
|||||||
|
|
||||||
ret = -EEXIST;
|
ret = -EEXIST;
|
||||||
inode_lock(file_inode(file));
|
inode_lock(file_inode(file));
|
||||||
if (!net->ws_cell)
|
if (!rcu_access_pointer(net->ws_cell))
|
||||||
ret = afs_cell_init(net, buf);
|
ret = afs_cell_init(net, buf);
|
||||||
else
|
else
|
||||||
printk("busy\n");
|
printk("busy\n");
|
||||||
|
Loading…
Reference in New Issue
Block a user