]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
nfs_common: fix race in NFS calls to nfsd_file_put_local() and nfsd_serv_put()
authorMike Snitzer <snitzer@kernel.org>
Thu, 3 Oct 2024 19:34:58 +0000 (15:34 -0400)
committerAnna Schumaker <anna.schumaker@oracle.com>
Thu, 3 Oct 2024 20:19:43 +0000 (16:19 -0400)
Add nfs_to_nfsd_file_put_local() interface to fix race with nfsd
module unload.  Similarly, use RCU around nfs_open_local_fh()'s error
path call to nfs_to->nfsd_serv_put().  Holding RCU ensures that NFS
will safely _call and return_ from its nfs_to calls into the NFSD
functions nfsd_file_put_local() and nfsd_serv_put().

Otherwise, if RCU isn't used then there is a narrow window when NFS's
reference for the nfsd_file and nfsd_serv are dropped and the NFSD
module could be unloaded, which could result in a crash from the
return instruction for either nfs_to->nfsd_file_put_local() or
nfs_to->nfsd_serv_put().

Reported-by: NeilBrown <neilb@suse.de>
Signed-off-by: Mike Snitzer <snitzer@kernel.org>
Signed-off-by: Anna Schumaker <anna.schumaker@oracle.com>
fs/nfs/localio.c
fs/nfs_common/nfslocalio.c
fs/nfsd/filecache.c
fs/nfsd/localio.c
fs/nfsd/nfssvc.c
include/linux/nfslocalio.h

index c5922b1a77c0ad1e807b2eb21c07a2d1816cd0fc..d0aa680ec8168b7d281e46db64af68860a846a72 100644 (file)
@@ -340,7 +340,7 @@ nfs_local_pgio_release(struct nfs_local_kiocb *iocb)
 {
        struct nfs_pgio_header *hdr = iocb->hdr;
 
-       nfs_to->nfsd_file_put_local(iocb->localio);
+       nfs_to_nfsd_file_put_local(iocb->localio);
        nfs_local_iocb_free(iocb);
        nfs_local_hdr_release(hdr, hdr->task.tk_ops);
 }
@@ -621,7 +621,7 @@ int nfs_local_doio(struct nfs_client *clp, struct nfsd_file *localio,
        }
 out:
        if (status != 0) {
-               nfs_to->nfsd_file_put_local(localio);
+               nfs_to_nfsd_file_put_local(localio);
                hdr->task.tk_status = status;
                nfs_local_hdr_release(hdr, call_ops);
        }
@@ -672,7 +672,7 @@ nfs_local_release_commit_data(struct nfsd_file *localio,
                struct nfs_commit_data *data,
                const struct rpc_call_ops *call_ops)
 {
-       nfs_to->nfsd_file_put_local(localio);
+       nfs_to_nfsd_file_put_local(localio);
        call_ops->rpc_call_done(&data->task, data);
        call_ops->rpc_release(data);
 }
index 42b479b9191f42447f0f05795c31f4e0bb0327bf..5c8ce5066c166c2c68e988a12f393db6440e52a5 100644 (file)
@@ -142,8 +142,11 @@ struct nfsd_file *nfs_open_local_fh(nfs_uuid_t *uuid,
        /* We have an implied reference to net thanks to nfsd_serv_try_get */
        localio = nfs_to->nfsd_open_local_fh(net, uuid->dom, rpc_clnt,
                                             cred, nfs_fh, fmode);
-       if (IS_ERR(localio))
+       if (IS_ERR(localio)) {
+               rcu_read_lock();
                nfs_to->nfsd_serv_put(net);
+               rcu_read_unlock();
+       }
        return localio;
 }
 EXPORT_SYMBOL_GPL(nfs_open_local_fh);
index 19bb88c7eebd9b7621398d46ff9a971b55037781..53070e1de3d929471f9614ce6c44b57158af0fd3 100644 (file)
@@ -398,7 +398,7 @@ nfsd_file_put(struct nfsd_file *nf)
  * reference to the associated nn->nfsd_serv.
  */
 void
-nfsd_file_put_local(struct nfsd_file *nf)
+nfsd_file_put_local(struct nfsd_file *nf) __must_hold(rcu)
 {
        struct net *net = nf->nf_net;
 
index 291e9c69cae48a14a1e083dfefeb22da0f2155b6..f441cb9f74d56cb91534ef81f4130aa176771992 100644 (file)
@@ -53,7 +53,7 @@ void nfsd_localio_ops_init(void)
  *
  * On successful return, returned nfsd_file will have its nf_net member
  * set. Caller (NFS client) is responsible for calling nfsd_serv_put and
- * nfsd_file_put (via nfs_to->nfsd_file_put_local).
+ * nfsd_file_put (via nfs_to_nfsd_file_put_local).
  */
 struct nfsd_file *
 nfsd_open_local_fh(struct net *net, struct auth_domain *dom,
index e236135ddc63fd1f0173b5bec05234c02127e224..47172b407be848137621a2935f343623105b1e2b 100644 (file)
@@ -214,14 +214,14 @@ int nfsd_minorversion(struct nfsd_net *nn, u32 minorversion, enum vers_op change
        return 0;
 }
 
-bool nfsd_serv_try_get(struct net *net)
+bool nfsd_serv_try_get(struct net *net) __must_hold(rcu)
 {
        struct nfsd_net *nn = net_generic(net, nfsd_net_id);
 
        return (nn && percpu_ref_tryget_live(&nn->nfsd_serv_ref));
 }
 
-void nfsd_serv_put(struct net *net)
+void nfsd_serv_put(struct net *net) __must_hold(rcu)
 {
        struct nfsd_net *nn = net_generic(net, nfsd_net_id);
 
index b353abe00357a11d7eb77b9d03bc59ba50646cd1..b0dd9b1eef4f9cd9dfdb48e0eb1b75f349ea2520 100644 (file)
@@ -65,10 +65,25 @@ struct nfsd_file *nfs_open_local_fh(nfs_uuid_t *,
                   struct rpc_clnt *, const struct cred *,
                   const struct nfs_fh *, const fmode_t);
 
+static inline void nfs_to_nfsd_file_put_local(struct nfsd_file *localio)
+{
+       /*
+        * Once reference to nfsd_serv is dropped, NFSD could be
+        * unloaded, so ensure safe return from nfsd_file_put_local()
+        * by always taking RCU.
+        */
+       rcu_read_lock();
+       nfs_to->nfsd_file_put_local(localio);
+       rcu_read_unlock();
+}
+
 #else   /* CONFIG_NFS_LOCALIO */
 static inline void nfsd_localio_ops_init(void)
 {
 }
+static inline void nfs_to_nfsd_file_put_local(struct nfsd_file *localio)
+{
+}
 #endif  /* CONFIG_NFS_LOCALIO */
 
 #endif  /* __LINUX_NFSLOCALIO_H */