Skip to content

Commit 80d39b5

Browse files
youzhongyanggregkh
authored andcommitted
nfsd: add list_head nf_gc to struct nfsd_file
commit 8e6e2ff upstream. nfsd_file_put() in one thread can race with another thread doing garbage collection (running nfsd_file_gc() -> list_lru_walk() -> nfsd_file_lru_cb()): * In nfsd_file_put(), nf->nf_ref is 1, so it tries to do nfsd_file_lru_add(). * nfsd_file_lru_add() returns true (with NFSD_FILE_REFERENCED bit set) * garbage collector kicks in, nfsd_file_lru_cb() clears REFERENCED bit and returns LRU_ROTATE. * garbage collector kicks in again, nfsd_file_lru_cb() now decrements nf->nf_ref to 0, runs nfsd_file_unhash(), removes it from the LRU and adds to the dispose list [list_lru_isolate_move(lru, &nf->nf_lru, head)] * nfsd_file_put() detects NFSD_FILE_HASHED bit is cleared, so it tries to remove the 'nf' from the LRU [if (!nfsd_file_lru_remove(nf))]. The 'nf' has been added to the 'dispose' list by nfsd_file_lru_cb(), so nfsd_file_lru_remove(nf) simply treats it as part of the LRU and removes it, which leads to its removal from the 'dispose' list. * At this moment, 'nf' is unhashed with its nf_ref being 0, and not on the LRU. nfsd_file_put() continues its execution [if (refcount_dec_and_test(&nf->nf_ref))], as nf->nf_ref is already 0, nf->nf_ref is set to REFCOUNT_SATURATED, and the 'nf' gets no chance of being freed. nfsd_file_put() can also race with nfsd_file_cond_queue(): * In nfsd_file_put(), nf->nf_ref is 1, so it tries to do nfsd_file_lru_add(). * nfsd_file_lru_add() sets REFERENCED bit and returns true. * Some userland application runs 'exportfs -f' or something like that, which triggers __nfsd_file_cache_purge() -> nfsd_file_cond_queue(). * In nfsd_file_cond_queue(), it runs [if (!nfsd_file_unhash(nf))], unhash is done successfully. * nfsd_file_cond_queue() runs [if (!nfsd_file_get(nf))], now nf->nf_ref goes to 2. * nfsd_file_cond_queue() runs [if (nfsd_file_lru_remove(nf))], it succeeds. * nfsd_file_cond_queue() runs [if (refcount_sub_and_test(decrement, &nf->nf_ref))] (with "decrement" being 2), so the nf->nf_ref goes to 0, the 'nf' is added to the dispose list [list_add(&nf->nf_lru, dispose)] * nfsd_file_put() detects NFSD_FILE_HASHED bit is cleared, so it tries to remove the 'nf' from the LRU [if (!nfsd_file_lru_remove(nf))], although the 'nf' is not in the LRU, but it is linked in the 'dispose' list, nfsd_file_lru_remove() simply treats it as part of the LRU and removes it. This leads to its removal from the 'dispose' list! * Now nf->ref is 0, unhashed. nfsd_file_put() continues its execution and set nf->nf_ref to REFCOUNT_SATURATED. As shown in the above analysis, using nf_lru for both the LRU list and dispose list can cause the leaks. This patch adds a new list_head nf_gc in struct nfsd_file, and uses it for the dispose list. This does not fix the nfsd_file leaking issue completely. Signed-off-by: Youzhong Yang <youzhong@gmail.com> Reviewed-by: Jeff Layton <jlayton@kernel.org> Signed-off-by: Chuck Lever <chuck.lever@oracle.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
1 parent 0b7b07c commit 80d39b5

File tree

2 files changed

+11
-8
lines changed

2 files changed

+11
-8
lines changed

fs/nfsd/filecache.c

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,7 @@ nfsd_file_alloc(struct net *net, struct inode *inode, unsigned char need,
219219
return NULL;
220220

221221
INIT_LIST_HEAD(&nf->nf_lru);
222+
INIT_LIST_HEAD(&nf->nf_gc);
222223
nf->nf_birthtime = ktime_get();
223224
nf->nf_file = NULL;
224225
nf->nf_cred = get_current_cred();
@@ -396,8 +397,8 @@ nfsd_file_dispose_list(struct list_head *dispose)
396397
struct nfsd_file *nf;
397398

398399
while (!list_empty(dispose)) {
399-
nf = list_first_entry(dispose, struct nfsd_file, nf_lru);
400-
list_del_init(&nf->nf_lru);
400+
nf = list_first_entry(dispose, struct nfsd_file, nf_gc);
401+
list_del_init(&nf->nf_gc);
401402
nfsd_file_free(nf);
402403
}
403404
}
@@ -414,12 +415,12 @@ nfsd_file_dispose_list_delayed(struct list_head *dispose)
414415
{
415416
while(!list_empty(dispose)) {
416417
struct nfsd_file *nf = list_first_entry(dispose,
417-
struct nfsd_file, nf_lru);
418+
struct nfsd_file, nf_gc);
418419
struct nfsd_net *nn = net_generic(nf->nf_net, nfsd_net_id);
419420
struct nfsd_fcache_disposal *l = nn->fcache_disposal;
420421

421422
spin_lock(&l->lock);
422-
list_move_tail(&nf->nf_lru, &l->freeme);
423+
list_move_tail(&nf->nf_gc, &l->freeme);
423424
spin_unlock(&l->lock);
424425
queue_work(nfsd_filecache_wq, &l->work);
425426
}
@@ -476,7 +477,8 @@ nfsd_file_lru_cb(struct list_head *item, struct list_lru_one *lru,
476477

477478
/* Refcount went to zero. Unhash it and queue it to the dispose list */
478479
nfsd_file_unhash(nf);
479-
list_lru_isolate_move(lru, &nf->nf_lru, head);
480+
list_lru_isolate(lru, &nf->nf_lru);
481+
list_add(&nf->nf_gc, head);
480482
this_cpu_inc(nfsd_file_evictions);
481483
trace_nfsd_file_gc_disposed(nf);
482484
return LRU_REMOVED;
@@ -555,7 +557,7 @@ nfsd_file_cond_queue(struct nfsd_file *nf, struct list_head *dispose)
555557

556558
/* If refcount goes to 0, then put on the dispose list */
557559
if (refcount_sub_and_test(decrement, &nf->nf_ref)) {
558-
list_add(&nf->nf_lru, dispose);
560+
list_add(&nf->nf_gc, dispose);
559561
trace_nfsd_file_closing(nf);
560562
}
561563
}
@@ -631,8 +633,8 @@ nfsd_file_close_inode_sync(struct inode *inode)
631633

632634
nfsd_file_queue_for_close(inode, &dispose);
633635
while (!list_empty(&dispose)) {
634-
nf = list_first_entry(&dispose, struct nfsd_file, nf_lru);
635-
list_del_init(&nf->nf_lru);
636+
nf = list_first_entry(&dispose, struct nfsd_file, nf_gc);
637+
list_del_init(&nf->nf_gc);
636638
nfsd_file_free(nf);
637639
}
638640
flush_delayed_fput();

fs/nfsd/filecache.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ struct nfsd_file {
4444

4545
struct nfsd_file_mark *nf_mark;
4646
struct list_head nf_lru;
47+
struct list_head nf_gc;
4748
struct rcu_head nf_rcu;
4849
ktime_t nf_birthtime;
4950
};

0 commit comments

Comments
 (0)