forked from mirrors/linux
		
	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: NeilBrown <neilb@suse.de> Signed-off-by: Mike Snitzer <snitzer@kernel.org> Signed-off-by: Anna Schumaker <anna.schumaker@oracle.com>
This commit is contained in:
		
							parent
							
								
									a848c29e34
								
							
						
					
					
						commit
						65f2a5c366
					
				
					 6 changed files with 26 additions and 8 deletions
				
			
		| 
						 | 
					@ -340,7 +340,7 @@ nfs_local_pgio_release(struct nfs_local_kiocb *iocb)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	struct nfs_pgio_header *hdr = iocb->hdr;
 | 
						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_iocb_free(iocb);
 | 
				
			||||||
	nfs_local_hdr_release(hdr, hdr->task.tk_ops);
 | 
						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:
 | 
					out:
 | 
				
			||||||
	if (status != 0) {
 | 
						if (status != 0) {
 | 
				
			||||||
		nfs_to->nfsd_file_put_local(localio);
 | 
							nfs_to_nfsd_file_put_local(localio);
 | 
				
			||||||
		hdr->task.tk_status = status;
 | 
							hdr->task.tk_status = status;
 | 
				
			||||||
		nfs_local_hdr_release(hdr, call_ops);
 | 
							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,
 | 
							struct nfs_commit_data *data,
 | 
				
			||||||
		const struct rpc_call_ops *call_ops)
 | 
							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_call_done(&data->task, data);
 | 
				
			||||||
	call_ops->rpc_release(data);
 | 
						call_ops->rpc_release(data);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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 */
 | 
						/* 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,
 | 
						localio = nfs_to->nfsd_open_local_fh(net, uuid->dom, rpc_clnt,
 | 
				
			||||||
					     cred, nfs_fh, fmode);
 | 
										     cred, nfs_fh, fmode);
 | 
				
			||||||
	if (IS_ERR(localio))
 | 
						if (IS_ERR(localio)) {
 | 
				
			||||||
 | 
							rcu_read_lock();
 | 
				
			||||||
		nfs_to->nfsd_serv_put(net);
 | 
							nfs_to->nfsd_serv_put(net);
 | 
				
			||||||
 | 
							rcu_read_unlock();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	return localio;
 | 
						return localio;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
EXPORT_SYMBOL_GPL(nfs_open_local_fh);
 | 
					EXPORT_SYMBOL_GPL(nfs_open_local_fh);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -398,7 +398,7 @@ nfsd_file_put(struct nfsd_file *nf)
 | 
				
			||||||
 * reference to the associated nn->nfsd_serv.
 | 
					 * reference to the associated nn->nfsd_serv.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
void
 | 
					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;
 | 
						struct net *net = nf->nf_net;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -53,7 +53,7 @@ void nfsd_localio_ops_init(void)
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * On successful return, returned nfsd_file will have its nf_net member
 | 
					 * On successful return, returned nfsd_file will have its nf_net member
 | 
				
			||||||
 * set. Caller (NFS client) is responsible for calling nfsd_serv_put and
 | 
					 * 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 *
 | 
					struct nfsd_file *
 | 
				
			||||||
nfsd_open_local_fh(struct net *net, struct auth_domain *dom,
 | 
					nfsd_open_local_fh(struct net *net, struct auth_domain *dom,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -214,14 +214,14 @@ int nfsd_minorversion(struct nfsd_net *nn, u32 minorversion, enum vers_op change
 | 
				
			||||||
	return 0;
 | 
						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);
 | 
						struct nfsd_net *nn = net_generic(net, nfsd_net_id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return (nn && percpu_ref_tryget_live(&nn->nfsd_serv_ref));
 | 
						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);
 | 
						struct nfsd_net *nn = net_generic(net, nfsd_net_id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -65,10 +65,25 @@ struct nfsd_file *nfs_open_local_fh(nfs_uuid_t *,
 | 
				
			||||||
		   struct rpc_clnt *, const struct cred *,
 | 
							   struct rpc_clnt *, const struct cred *,
 | 
				
			||||||
		   const struct nfs_fh *, const fmode_t);
 | 
							   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 */
 | 
					#else   /* CONFIG_NFS_LOCALIO */
 | 
				
			||||||
static inline void nfsd_localio_ops_init(void)
 | 
					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  /* CONFIG_NFS_LOCALIO */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#endif  /* __LINUX_NFSLOCALIO_H */
 | 
					#endif  /* __LINUX_NFSLOCALIO_H */
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in a new issue