linux/fs/nfsd
Youzhong Yang 80d39b50bd nfsd: add list_head nf_gc to struct nfsd_file
commit 8e6e2ffa65 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>
History 2025-01-23 17:21:19 +01:00
..
acl.h NFSD: add posix ACLs to struct nfsd_attrs 2022-08-04 10:28:03 -04:00
auth.c cred: get rid of CONFIG_DEBUG_CREDENTIALS 2023-12-20 17:01:51 +01:00
auth.h License cleanup: add SPDX GPL-2.0 license identifier to files with no license 2017-11-02 11:10:55 +01:00
blocklayout.c nfsd: call op_release, even when op_func returns an error 2023-03-31 17:29:49 -04:00
blocklayoutxdr.c NFSD: da_addr_body field missing in some GETDEVICEINFO replies 2023-08-29 17:45:22 -04:00
blocklayoutxdr.h License cleanup: add SPDX GPL-2.0 license identifier to files with no license 2017-11-02 11:10:55 +01:00
cache.h nfsd: make all of the nfsd stats per-network namespace 2024-08-19 06:04:23 +02:00
current_stateid.h License cleanup: add SPDX GPL-2.0 license identifier to files with no license 2017-11-02 11:10:55 +01:00
export.c nfsd: Revert "nfsd: release svc_expkey/svc_export with rcu_work" 2025-01-02 10:32:00 +01:00
export.h nfsd: Revert "nfsd: release svc_expkey/svc_export with rcu_work" 2025-01-02 10:32:00 +01:00
filecache.c nfsd: add list_head nf_gc to struct nfsd_file 2025-01-23 17:21:19 +01:00
filecache.h nfsd: add list_head nf_gc to struct nfsd_file 2025-01-23 17:21:19 +01:00
flexfilelayout.c nfsd: move nfserrno() to vfs.c 2022-11-28 12:54:44 -05:00
flexfilelayoutxdr.c NFSD: da_addr_body field missing in some GETDEVICEINFO replies 2023-08-29 17:45:22 -04:00
flexfilelayoutxdr.h License cleanup: add SPDX GPL-2.0 license identifier to files with no license 2017-11-02 11:10:55 +01:00
idmap.h nfsd: Remove duplicate define of IDMAP_NAMESZ/IDMAP_TYPE_xx 2015-07-20 14:58:46 -04:00
Kconfig NFS & NFSD: Update GSS dependencies 2023-03-10 09:38:47 -05:00
lockd.c NFSD: simplify struct nfsfh 2021-10-02 15:51:10 -04:00
Makefile nfsd: allow disabling NFSv2 at compile time 2022-11-28 12:54:45 -05:00
netns.h NFSD: Limit the number of concurrent async COPY operations 2024-11-22 15:38:36 +01:00
nfs2acl.c NFSD 6.3 Release Notes 2023-02-22 14:21:40 -08:00
nfs3acl.c NFSD 6.3 Release Notes 2023-02-22 14:21:40 -08:00
nfs3proc.c nfsd: handle failure to collect pre/post-op attrs more sanely 2023-08-29 17:45:22 -04:00
nfs3xdr.c NFSD: Ensure that xdr_write_pages updates rq_next_page 2023-06-05 09:01:44 -04:00
nfs4acl.c nfsd: inherit required unset default acls from effective set 2023-08-29 17:45:22 -04:00
nfs4callback.c nfsd: restore callback functionality for NFSv4.0 2025-01-02 10:32:01 +01:00
nfs4idmap.c nfsd: call cache_put if xdr_reserve_space returns NULL 2024-10-04 16:29:35 +02:00
nfs4layouts.c nfsd: fix race to check ls_layouts 2023-02-20 09:20:56 -05:00
nfs4proc.c NFSD: Fix nfsd4_shutdown_copy() 2024-12-09 10:32:30 +01:00
nfs4recover.c NFSD: Cap the number of bytes copied by nfs4_reset_recoverydir() 2024-12-09 10:32:30 +01:00
nfs4state.c nfsd: fix nfs4_openowner leak when concurrent nfsd4_open occur 2024-12-09 10:33:09 +01:00
nfs4xdr.c NFSD: Fix NFSv4's PUTPUBFH operation 2024-10-10 11:57:53 +02:00
nfscache.c nfsd: make all of the nfsd stats per-network namespace 2024-08-19 06:04:23 +02:00
nfsctl.c nfsd: make svc_stat per-network namespace instead of global 2024-08-19 06:04:24 +02:00
nfsd.h nfsd: remove nfsd_stats, make th_cnt a global counter 2024-08-19 06:04:24 +02:00
nfsfh.c nfsd: make all of the nfsd stats per-network namespace 2024-08-19 06:04:23 +02:00
nfsfh.h nfsd: handle failure to collect pre/post-op attrs more sanely 2023-08-29 17:45:22 -04:00
nfsproc.c NFSD: Hoist rq_vec preparation into nfsd_read() 2023-06-11 16:37:45 -04:00
nfssvc.c NFSD: simplify error paths in nfsd_svc() 2024-08-29 17:33:58 +02:00
nfsxdr.c NFSD: Ensure that xdr_write_pages updates rq_next_page 2023-06-05 09:01:44 -04:00
pnfs.h License cleanup: add SPDX GPL-2.0 license identifier to files with no license 2017-11-02 11:10:55 +01:00
state.h NFSD: handle GETATTR conflict with write delegation 2023-08-29 17:45:22 -04:00
stats.c nfsd: make svc_stat per-network namespace instead of global 2024-08-19 06:04:24 +02:00
stats.h nfsd: make svc_stat per-network namespace instead of global 2024-08-19 06:04:24 +02:00
trace.c NFSD: Add SPDX header for fs/nfsd/trace.c 2020-11-30 13:00:24 -05:00
trace.h NFSD: Fix nfsd_clid_class use of __string_len() macro 2024-04-03 15:28:35 +02:00
vfs.c nfsd: map the EBADMSG to nfserr_io to avoid warning 2024-10-10 11:57:52 +02:00
vfs.h nfsd: Handle EOPENSTALE correctly in the filecache 2023-11-20 11:58:52 +01:00
xdr.h NFSD: prevent underflow in nfssvc_decode_writeargs() 2022-03-15 09:35:56 -04:00
xdr3.h SUNRPC: Change return value type of .pc_encode 2021-10-13 11:34:49 -04:00
xdr4.h NFSD: Limit the number of concurrent async COPY operations 2024-11-22 15:38:36 +01:00
xdr4cb.h NFSD: add support for sending CB_RECALL_ANY 2022-12-10 11:01:12 -05:00