Commit 65f2a5c3 authored by Mike Snitzer's avatar Mike Snitzer Committed by Anna Schumaker
Browse files

nfs_common: fix race in NFS calls to nfsd_file_put_local() and nfsd_serv_put()



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: default avatarNeilBrown <neilb@suse.de>
Signed-off-by: default avatarMike Snitzer <snitzer@kernel.org>
Signed-off-by: default avatarAnna Schumaker <anna.schumaker@oracle.com>
parent a848c29e
Loading
Loading
Loading
Loading
+3 −3
Original line number Diff line number Diff line
@@ -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);
}
+4 −1
Original line number Diff line number Diff line
@@ -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);
+1 −1
Original line number Diff line number Diff line
@@ -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;

+1 −1
Original line number Diff line number Diff line
@@ -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,
+2 −2
Original line number Diff line number Diff line
@@ -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);

Loading