]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
NFS/localio: nfs_uuid_put() fix races with nfs_open/close_local_fh()
authorTrond Myklebust <trond.myklebust@hammerspace.com>
Tue, 15 Jul 2025 19:49:00 +0000 (12:49 -0700)
committerTrond Myklebust <trond.myklebust@hammerspace.com>
Tue, 5 Aug 2025 23:45:40 +0000 (16:45 -0700)
In order for the wait in nfs_uuid_put() to be safe, it is necessary to
ensure that nfs_uuid_add_file() doesn't add a new entry once the
nfs_uuid->net has been NULLed out.

Also fix up the wake_up_var_locked() / wait_var_event_spinlock() to both
use the nfs_uuid address, since nfl, and &nfl->uuid could be used elsewhere.

Acked-by: Mike Snitzer <snitzer@kernel.org>
Tested-by: Mike Snitzer <snitzer@kernel.org>
Link: https://lore.kernel.org/all/175262893035.2234665.1735173020338594784@noble.neil.brown.name/
Fixes: 21fb44034695 ("nfs_localio: protect race between nfs_uuid_put() and nfs_close_local_fh()")
Signed-off-by: Trond Myklebust <trond.myklebust@hammerspace.com>
fs/nfs_common/nfslocalio.c

index 64949c46c1741351e4df9e3e7cc838a0fabdaa23..f1f1592ac13487e40e91e8d3ddb7918e29a0b41e 100644 (file)
@@ -177,7 +177,7 @@ static bool nfs_uuid_put(nfs_uuid_t *nfs_uuid)
                        /* nfs_close_local_fh() is doing the
                         * close and we must wait. until it unlinks
                         */
-                       wait_var_event_spinlock(nfl,
+                       wait_var_event_spinlock(nfs_uuid,
                                                list_first_entry_or_null(
                                                        &nfs_uuid->files,
                                                        struct nfs_file_localio,
@@ -243,15 +243,20 @@ void nfs_localio_invalidate_clients(struct list_head *nn_local_clients,
 }
 EXPORT_SYMBOL_GPL(nfs_localio_invalidate_clients);
 
-static void nfs_uuid_add_file(nfs_uuid_t *nfs_uuid, struct nfs_file_localio *nfl)
+static int nfs_uuid_add_file(nfs_uuid_t *nfs_uuid, struct nfs_file_localio *nfl)
 {
+       int ret = 0;
+
        /* Add nfl to nfs_uuid->files if it isn't already */
        spin_lock(&nfs_uuid->lock);
-       if (list_empty(&nfl->list)) {
+       if (rcu_access_pointer(nfs_uuid->net) == NULL) {
+               ret = -ENXIO;
+       } else if (list_empty(&nfl->list)) {
                rcu_assign_pointer(nfl->nfs_uuid, nfs_uuid);
                list_add_tail(&nfl->list, &nfs_uuid->files);
        }
        spin_unlock(&nfs_uuid->lock);
+       return ret;
 }
 
 /*
@@ -285,11 +290,13 @@ struct nfsd_file *nfs_open_local_fh(nfs_uuid_t *uuid,
        }
        rcu_read_unlock();
        /* We have an implied reference to net thanks to nfsd_net_try_get */
-       localio = nfs_to->nfsd_open_local_fh(net, uuid->dom, rpc_clnt,
-                                            cred, nfs_fh, pnf, fmode);
+       localio = nfs_to->nfsd_open_local_fh(net, uuid->dom, rpc_clnt, cred,
+                                            nfs_fh, pnf, fmode);
+       if (!IS_ERR(localio) && nfs_uuid_add_file(uuid, nfl) < 0) {
+               /* Delete the cached file when racing with nfs_uuid_put() */
+               nfs_to_nfsd_file_put_local(pnf);
+       }
        nfs_to_nfsd_net_put(net);
-       if (!IS_ERR(localio))
-               nfs_uuid_add_file(uuid, nfl);
 
        return localio;
 }
@@ -338,7 +345,7 @@ void nfs_close_local_fh(struct nfs_file_localio *nfl)
         */
        spin_lock(&nfs_uuid->lock);
        list_del_init(&nfl->list);
-       wake_up_var_locked(&nfl->nfs_uuid, &nfs_uuid->lock);
+       wake_up_var_locked(nfs_uuid, &nfs_uuid->lock);
        spin_unlock(&nfs_uuid->lock);
 }
 EXPORT_SYMBOL_GPL(nfs_close_local_fh);