diff --git a/fs/nfsd/nfs4recover.c b/fs/nfsd/nfs4recover.c index d513971fb..90abdea16 100644 --- a/fs/nfsd/nfs4recover.c +++ b/fs/nfsd/nfs4recover.c @@ -647,6 +647,7 @@ struct cld_upcall { struct list_head cu_list; struct cld_net *cu_net; struct completion cu_done; + struct completion cu_msg_done; union { struct cld_msg_hdr cu_hdr; struct cld_msg cu_msg; @@ -671,6 +672,13 @@ __cld_pipe_upcall(struct rpc_pipe *pipe, void *cmsg, struct nfsd_net *nn) } wait_for_completion(&cup->cu_done); + /* + * A downcall can be received before userspace reads the upcall. Wait + * for cld_pipe_destroy_msg() to signal that @msg has been removed from + * the rpc_pipe, so the on-stack message is not left linked in the pipe + * after this function returns. + */ + wait_for_completion(&cup->cu_msg_done); if (msg.errno < 0) ret = msg.errno; @@ -839,10 +847,15 @@ cld_pipe_destroy_msg(struct rpc_pipe_msg *msg) cu_u.cu_msg); /* errno >= 0 means we got a downcall */ - if (msg->errno >= 0) - return; + if (msg->errno < 0) + complete(&cup->cu_done); - complete(&cup->cu_done); + /* + * Signal __cld_pipe_upcall() that @msg is no longer queued. This is + * needed even when the downcall arrived first, otherwise the caller + * could return with the on-stack message still linked in the pipe. + */ + complete(&cup->cu_msg_done); } static const struct rpc_pipe_ops cld_upcall_ops = { @@ -979,6 +992,7 @@ alloc_cld_upcall(struct nfsd_net *nn) } } init_completion(&new->cu_done); + init_completion(&new->cu_msg_done); new->cu_u.cu_msg.cm_vers = nn->client_tracking_ops->version; put_unaligned(cn->cn_xid++, &new->cu_u.cu_msg.cm_xid); new->cu_net = cn;