Skip to content

Commit 92e3cc9

Browse files
committed
afs: Fix rapid cell addition/removal by not using RCU on cells tree
There are a number of problems that are being seen by the rapidly mounting and unmounting an afs dynamic root with an explicit cell and volume specified (which should probably be rejected, but that's a separate issue): What the tests are doing is to look up/create a cell record for the name given and then tear it down again without actually using it to try to talk to a server. This is repeated endlessly, very fast, and the new cell collides with the old one if it's not quick enough to reuse it. It appears (as suggested by Hillf Danton) that the search through the RB tree under a read_seqbegin_or_lock() under RCU conditions isn't safe and that it's not blocking the write_seqlock(), despite taking two passes at it. He suggested that the code should take a ref on the cell it's attempting to look at - but this shouldn't be necessary until we've compared the cell names. It's possible that I'm missing a barrier somewhere. However, using an RCU search for this is overkill, really - we only need to access the cell name in a few places, and they're places where we're may end up sleeping anyway. Fix this by switching to an R/W semaphore instead. Additionally, draw the down_read() call inside the function (renamed to afs_find_cell()) since all the callers were taking the RCU read lock (or should've been[*]). [*] afs_probe_cell_name() should have been, but that doesn't appear to be involved in the bug reports. The symptoms of this look like: general protection fault, probably for non-canonical address 0xf27d208691691fdb: 0000 [#1] PREEMPT SMP KASAN KASAN: maybe wild-memory-access in range [0x93e924348b48fed8-0x93e924348b48fedf] ... RIP: 0010:strncasecmp lib/string.c:52 [inline] RIP: 0010:strncasecmp+0x5f/0x240 lib/string.c:43 afs_lookup_cell_rcu+0x313/0x720 fs/afs/cell.c:88 afs_lookup_cell+0x2ee/0x1440 fs/afs/cell.c:249 afs_parse_source fs/afs/super.c:290 [inline] ... Fixes: 989782d ("afs: Overhaul cell database management") Reported-by: syzbot+459a5dce0b4cb70fd076@syzkaller.appspotmail.com Signed-off-by: David Howells <dhowells@redhat.com> cc: Hillf Danton <hdanton@sina.com> cc: syzkaller-bugs@googlegroups.com
1 parent bbf5c97 commit 92e3cc9

5 files changed

Lines changed: 71 additions & 93 deletions

File tree

fs/afs/cell.c

Lines changed: 58 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,15 @@ static void afs_set_cell_timer(struct afs_net *net, time64_t delay)
4141
}
4242

4343
/*
44-
* Look up and get an activation reference on a cell record under RCU
45-
* conditions. The caller must hold the RCU read lock.
44+
* Look up and get an activation reference on a cell record. The caller must
45+
* hold net->cells_lock at least read-locked.
4646
*/
47-
struct afs_cell *afs_lookup_cell_rcu(struct afs_net *net,
48-
const char *name, unsigned int namesz)
47+
static struct afs_cell *afs_find_cell_locked(struct afs_net *net,
48+
const char *name, unsigned int namesz)
4949
{
5050
struct afs_cell *cell = NULL;
5151
struct rb_node *p;
52-
int n, seq = 0, ret = 0;
52+
int n;
5353

5454
_enter("%*.*s", namesz, namesz, name);
5555

@@ -58,61 +58,48 @@ struct afs_cell *afs_lookup_cell_rcu(struct afs_net *net,
5858
if (namesz > AFS_MAXCELLNAME)
5959
return ERR_PTR(-ENAMETOOLONG);
6060

61-
do {
62-
/* Unfortunately, rbtree walking doesn't give reliable results
63-
* under just the RCU read lock, so we have to check for
64-
* changes.
65-
*/
66-
if (cell)
67-
afs_put_cell(net, cell);
68-
cell = NULL;
69-
ret = -ENOENT;
70-
71-
read_seqbegin_or_lock(&net->cells_lock, &seq);
72-
73-
if (!name) {
74-
cell = rcu_dereference_raw(net->ws_cell);
75-
if (cell) {
76-
afs_get_cell(cell);
77-
ret = 0;
78-
break;
79-
}
80-
ret = -EDESTADDRREQ;
81-
continue;
82-
}
61+
if (!name) {
62+
cell = net->ws_cell;
63+
if (!cell)
64+
return ERR_PTR(-EDESTADDRREQ);
65+
afs_get_cell(cell);
66+
return cell;
67+
}
8368

84-
p = rcu_dereference_raw(net->cells.rb_node);
85-
while (p) {
86-
cell = rb_entry(p, struct afs_cell, net_node);
87-
88-
n = strncasecmp(cell->name, name,
89-
min_t(size_t, cell->name_len, namesz));
90-
if (n == 0)
91-
n = cell->name_len - namesz;
92-
if (n < 0) {
93-
p = rcu_dereference_raw(p->rb_left);
94-
} else if (n > 0) {
95-
p = rcu_dereference_raw(p->rb_right);
96-
} else {
97-
if (atomic_inc_not_zero(&cell->usage)) {
98-
ret = 0;
99-
break;
100-
}
101-
/* We want to repeat the search, this time with
102-
* the lock properly locked.
103-
*/
104-
}
105-
cell = NULL;
106-
}
69+
p = net->cells.rb_node;
70+
while (p) {
71+
cell = rb_entry(p, struct afs_cell, net_node);
10772

108-
} while (need_seqretry(&net->cells_lock, seq));
73+
n = strncasecmp(cell->name, name,
74+
min_t(size_t, cell->name_len, namesz));
75+
if (n == 0)
76+
n = cell->name_len - namesz;
77+
if (n < 0)
78+
p = p->rb_left;
79+
else if (n > 0)
80+
p = p->rb_right;
81+
else
82+
goto found;
83+
}
84+
85+
return ERR_PTR(-ENOENT);
10986

110-
done_seqretry(&net->cells_lock, seq);
87+
found:
88+
if (!atomic_inc_not_zero(&cell->usage))
89+
return ERR_PTR(-ENOENT);
11190

112-
if (ret != 0 && cell)
113-
afs_put_cell(net, cell);
91+
return cell;
92+
}
11493

115-
return ret == 0 ? cell : ERR_PTR(ret);
94+
struct afs_cell *afs_find_cell(struct afs_net *net,
95+
const char *name, unsigned int namesz)
96+
{
97+
struct afs_cell *cell;
98+
99+
down_read(&net->cells_lock);
100+
cell = afs_find_cell_locked(net, name, namesz);
101+
up_read(&net->cells_lock);
102+
return cell;
116103
}
117104

118105
/*
@@ -245,9 +232,7 @@ struct afs_cell *afs_lookup_cell(struct afs_net *net,
245232
_enter("%s,%s", name, vllist);
246233

247234
if (!excl) {
248-
rcu_read_lock();
249-
cell = afs_lookup_cell_rcu(net, name, namesz);
250-
rcu_read_unlock();
235+
cell = afs_find_cell(net, name, namesz);
251236
if (!IS_ERR(cell))
252237
goto wait_for_cell;
253238
}
@@ -268,7 +253,7 @@ struct afs_cell *afs_lookup_cell(struct afs_net *net,
268253
/* Find the insertion point and check to see if someone else added a
269254
* cell whilst we were allocating.
270255
*/
271-
write_seqlock(&net->cells_lock);
256+
down_write(&net->cells_lock);
272257

273258
pp = &net->cells.rb_node;
274259
parent = NULL;
@@ -293,7 +278,7 @@ struct afs_cell *afs_lookup_cell(struct afs_net *net,
293278
rb_link_node_rcu(&cell->net_node, parent, pp);
294279
rb_insert_color(&cell->net_node, &net->cells);
295280
atomic_inc(&net->cells_outstanding);
296-
write_sequnlock(&net->cells_lock);
281+
up_write(&net->cells_lock);
297282

298283
queue_work(afs_wq, &cell->manager);
299284

@@ -323,7 +308,7 @@ struct afs_cell *afs_lookup_cell(struct afs_net *net,
323308
afs_get_cell(cursor);
324309
ret = 0;
325310
}
326-
write_sequnlock(&net->cells_lock);
311+
up_write(&net->cells_lock);
327312
kfree(candidate);
328313
if (ret == 0)
329314
goto wait_for_cell;
@@ -377,10 +362,10 @@ int afs_cell_init(struct afs_net *net, const char *rootcell)
377362
afs_get_cell(new_root);
378363

379364
/* install the new cell */
380-
write_seqlock(&net->cells_lock);
381-
old_root = rcu_access_pointer(net->ws_cell);
382-
rcu_assign_pointer(net->ws_cell, new_root);
383-
write_sequnlock(&net->cells_lock);
365+
down_write(&net->cells_lock);
366+
old_root = net->ws_cell;
367+
net->ws_cell = new_root;
368+
up_write(&net->cells_lock);
384369

385370
afs_put_cell(net, old_root);
386371
_leave(" = 0");
@@ -674,12 +659,12 @@ static void afs_manage_cell(struct work_struct *work)
674659
switch (cell->state) {
675660
case AFS_CELL_INACTIVE:
676661
case AFS_CELL_FAILED:
677-
write_seqlock(&net->cells_lock);
662+
down_write(&net->cells_lock);
678663
usage = 1;
679664
deleted = atomic_try_cmpxchg_relaxed(&cell->usage, &usage, 0);
680665
if (deleted)
681666
rb_erase(&cell->net_node, &net->cells);
682-
write_sequnlock(&net->cells_lock);
667+
up_write(&net->cells_lock);
683668
if (deleted)
684669
goto final_destruction;
685670
if (cell->state == AFS_CELL_FAILED)
@@ -779,7 +764,7 @@ void afs_manage_cells(struct work_struct *work)
779764
* lack of use and cells whose DNS results have expired and dispatch
780765
* their managers.
781766
*/
782-
read_seqlock_excl(&net->cells_lock);
767+
down_read(&net->cells_lock);
783768

784769
for (cursor = rb_first(&net->cells); cursor; cursor = rb_next(cursor)) {
785770
struct afs_cell *cell =
@@ -824,7 +809,7 @@ void afs_manage_cells(struct work_struct *work)
824809
queue_work(afs_wq, &cell->manager);
825810
}
826811

827-
read_sequnlock_excl(&net->cells_lock);
812+
up_read(&net->cells_lock);
828813

829814
/* Update the timer on the way out. We have to pass an increment on
830815
* cells_outstanding in the namespace that we are in to the timer or
@@ -854,10 +839,10 @@ void afs_cell_purge(struct afs_net *net)
854839

855840
_enter("");
856841

857-
write_seqlock(&net->cells_lock);
858-
ws = rcu_access_pointer(net->ws_cell);
859-
RCU_INIT_POINTER(net->ws_cell, NULL);
860-
write_sequnlock(&net->cells_lock);
842+
down_write(&net->cells_lock);
843+
ws = net->ws_cell;
844+
net->ws_cell = NULL;
845+
up_write(&net->cells_lock);
861846
afs_put_cell(net, ws);
862847

863848
_debug("del timer");

fs/afs/dynroot.c

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ static int afs_probe_cell_name(struct dentry *dentry)
123123
len--;
124124
}
125125

126-
cell = afs_lookup_cell_rcu(net, name, len);
126+
cell = afs_find_cell(net, name, len);
127127
if (!IS_ERR(cell)) {
128128
afs_put_cell(net, cell);
129129
return 0;
@@ -179,7 +179,6 @@ static struct dentry *afs_lookup_atcell(struct dentry *dentry)
179179
struct afs_cell *cell;
180180
struct afs_net *net = afs_d2net(dentry);
181181
struct dentry *ret;
182-
unsigned int seq = 0;
183182
char *name;
184183
int len;
185184

@@ -191,17 +190,13 @@ static struct dentry *afs_lookup_atcell(struct dentry *dentry)
191190
if (!name)
192191
goto out_p;
193192

194-
rcu_read_lock();
195-
do {
196-
read_seqbegin_or_lock(&net->cells_lock, &seq);
197-
cell = rcu_dereference_raw(net->ws_cell);
198-
if (cell) {
199-
len = cell->name_len;
200-
memcpy(name, cell->name, len + 1);
201-
}
202-
} while (need_seqretry(&net->cells_lock, seq));
203-
done_seqretry(&net->cells_lock, seq);
204-
rcu_read_unlock();
193+
down_read(&net->cells_lock);
194+
cell = net->ws_cell;
195+
if (cell) {
196+
len = cell->name_len;
197+
memcpy(name, cell->name, len + 1);
198+
}
199+
up_read(&net->cells_lock);
205200

206201
ret = ERR_PTR(-ENOENT);
207202
if (!cell)

fs/afs/internal.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -263,11 +263,11 @@ struct afs_net {
263263

264264
/* Cell database */
265265
struct rb_root cells;
266-
struct afs_cell __rcu *ws_cell;
266+
struct afs_cell *ws_cell;
267267
struct work_struct cells_manager;
268268
struct timer_list cells_timer;
269269
atomic_t cells_outstanding;
270-
seqlock_t cells_lock;
270+
struct rw_semaphore cells_lock;
271271
struct mutex cells_alias_lock;
272272

273273
struct mutex proc_cells_lock;
@@ -917,7 +917,7 @@ static inline bool afs_cb_is_broken(unsigned int cb_break,
917917
* cell.c
918918
*/
919919
extern int afs_cell_init(struct afs_net *, const char *);
920-
extern struct afs_cell *afs_lookup_cell_rcu(struct afs_net *, const char *, unsigned);
920+
extern struct afs_cell *afs_find_cell(struct afs_net *, const char *, unsigned);
921921
extern struct afs_cell *afs_lookup_cell(struct afs_net *, const char *, unsigned,
922922
const char *, bool);
923923
extern struct afs_cell *afs_get_cell(struct afs_cell *);

fs/afs/main.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ static int __net_init afs_net_init(struct net *net_ns)
7878
mutex_init(&net->socket_mutex);
7979

8080
net->cells = RB_ROOT;
81-
seqlock_init(&net->cells_lock);
81+
init_rwsem(&net->cells_lock);
8282
INIT_WORK(&net->cells_manager, afs_manage_cells);
8383
timer_setup(&net->cells_timer, afs_cells_timer, 0);
8484

fs/afs/super.c

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -634,9 +634,7 @@ static int afs_init_fs_context(struct fs_context *fc)
634634
ctx->net = afs_net(fc->net_ns);
635635

636636
/* Default to the workstation cell. */
637-
rcu_read_lock();
638-
cell = afs_lookup_cell_rcu(ctx->net, NULL, 0);
639-
rcu_read_unlock();
637+
cell = afs_find_cell(ctx->net, NULL, 0);
640638
if (IS_ERR(cell))
641639
cell = NULL;
642640
ctx->cell = cell;

0 commit comments

Comments
 (0)