| From 15ca08d3299682dc49bad73251677b2c5017ef08 Mon Sep 17 00:00:00 2001 |
| From: Trond Myklebust <trond.myklebust@primarydata.com> |
| Date: Fri, 3 Nov 2017 08:00:10 -0400 |
| Subject: nfsd: Fix stateid races between OPEN and CLOSE |
| |
| From: Trond Myklebust <trond.myklebust@primarydata.com> |
| |
| commit 15ca08d3299682dc49bad73251677b2c5017ef08 upstream. |
| |
| Open file stateids can linger on the nfs4_file list of stateids even |
| after they have been closed. In order to avoid reusing such a |
| stateid, and confusing the client, we need to recheck the |
| nfs4_stid's type after taking the mutex. |
| Otherwise, we risk reusing an old stateid that was already closed, |
| which will confuse clients that expect new stateids to conform to |
| RFC7530 Sections 9.1.4.2 and 16.2.5 or RFC5661 Sections 8.2.2 and 18.2.4. |
| |
| Signed-off-by: Trond Myklebust <trond.myklebust@primarydata.com> |
| Signed-off-by: J. Bruce Fields <bfields@redhat.com> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| |
| --- |
| fs/nfsd/nfs4state.c | 67 +++++++++++++++++++++++++++++++++++++++++++++------- |
| 1 file changed, 59 insertions(+), 8 deletions(-) |
| |
| --- a/fs/nfsd/nfs4state.c |
| +++ b/fs/nfsd/nfs4state.c |
| @@ -3379,7 +3379,9 @@ nfsd4_find_existing_open(struct nfs4_fil |
| /* ignore lock owners */ |
| if (local->st_stateowner->so_is_open_owner == 0) |
| continue; |
| - if (local->st_stateowner == &oo->oo_owner) { |
| + if (local->st_stateowner != &oo->oo_owner) |
| + continue; |
| + if (local->st_stid.sc_type == NFS4_OPEN_STID) { |
| ret = local; |
| atomic_inc(&ret->st_stid.sc_count); |
| break; |
| @@ -3388,6 +3390,52 @@ nfsd4_find_existing_open(struct nfs4_fil |
| return ret; |
| } |
| |
| +static __be32 |
| +nfsd4_verify_open_stid(struct nfs4_stid *s) |
| +{ |
| + __be32 ret = nfs_ok; |
| + |
| + switch (s->sc_type) { |
| + default: |
| + break; |
| + case NFS4_CLOSED_STID: |
| + case NFS4_CLOSED_DELEG_STID: |
| + ret = nfserr_bad_stateid; |
| + break; |
| + case NFS4_REVOKED_DELEG_STID: |
| + ret = nfserr_deleg_revoked; |
| + } |
| + return ret; |
| +} |
| + |
| +/* Lock the stateid st_mutex, and deal with races with CLOSE */ |
| +static __be32 |
| +nfsd4_lock_ol_stateid(struct nfs4_ol_stateid *stp) |
| +{ |
| + __be32 ret; |
| + |
| + mutex_lock(&stp->st_mutex); |
| + ret = nfsd4_verify_open_stid(&stp->st_stid); |
| + if (ret != nfs_ok) |
| + mutex_unlock(&stp->st_mutex); |
| + return ret; |
| +} |
| + |
| +static struct nfs4_ol_stateid * |
| +nfsd4_find_and_lock_existing_open(struct nfs4_file *fp, struct nfsd4_open *open) |
| +{ |
| + struct nfs4_ol_stateid *stp; |
| + for (;;) { |
| + spin_lock(&fp->fi_lock); |
| + stp = nfsd4_find_existing_open(fp, open); |
| + spin_unlock(&fp->fi_lock); |
| + if (!stp || nfsd4_lock_ol_stateid(stp) == nfs_ok) |
| + break; |
| + nfs4_put_stid(&stp->st_stid); |
| + } |
| + return stp; |
| +} |
| + |
| static struct nfs4_openowner * |
| alloc_init_open_stateowner(unsigned int strhashval, struct nfsd4_open *open, |
| struct nfsd4_compound_state *cstate) |
| @@ -3432,6 +3480,7 @@ init_open_stateid(struct nfs4_file *fp, |
| mutex_init(&stp->st_mutex); |
| mutex_lock(&stp->st_mutex); |
| |
| +retry: |
| spin_lock(&oo->oo_owner.so_client->cl_lock); |
| spin_lock(&fp->fi_lock); |
| |
| @@ -3456,7 +3505,11 @@ out_unlock: |
| spin_unlock(&fp->fi_lock); |
| spin_unlock(&oo->oo_owner.so_client->cl_lock); |
| if (retstp) { |
| - mutex_lock(&retstp->st_mutex); |
| + /* Handle races with CLOSE */ |
| + if (nfsd4_lock_ol_stateid(retstp) != nfs_ok) { |
| + nfs4_put_stid(&retstp->st_stid); |
| + goto retry; |
| + } |
| /* To keep mutex tracking happy */ |
| mutex_unlock(&stp->st_mutex); |
| stp = retstp; |
| @@ -4277,9 +4330,7 @@ nfsd4_process_open2(struct svc_rqst *rqs |
| status = nfs4_check_deleg(cl, open, &dp); |
| if (status) |
| goto out; |
| - spin_lock(&fp->fi_lock); |
| - stp = nfsd4_find_existing_open(fp, open); |
| - spin_unlock(&fp->fi_lock); |
| + stp = nfsd4_find_and_lock_existing_open(fp, open); |
| } else { |
| open->op_file = NULL; |
| status = nfserr_bad_stateid; |
| @@ -4293,7 +4344,6 @@ nfsd4_process_open2(struct svc_rqst *rqs |
| */ |
| if (stp) { |
| /* Stateid was found, this is an OPEN upgrade */ |
| - mutex_lock(&stp->st_mutex); |
| status = nfs4_upgrade_open(rqstp, fp, current_fh, stp, open); |
| if (status) { |
| mutex_unlock(&stp->st_mutex); |
| @@ -5150,7 +5200,6 @@ static void nfsd4_close_open_stateid(str |
| bool unhashed; |
| LIST_HEAD(reaplist); |
| |
| - s->st_stid.sc_type = NFS4_CLOSED_STID; |
| spin_lock(&clp->cl_lock); |
| unhashed = unhash_open_stateid(s, &reaplist); |
| |
| @@ -5189,10 +5238,12 @@ nfsd4_close(struct svc_rqst *rqstp, stru |
| nfsd4_bump_seqid(cstate, status); |
| if (status) |
| goto out; |
| + |
| + stp->st_stid.sc_type = NFS4_CLOSED_STID; |
| nfs4_inc_and_copy_stateid(&close->cl_stateid, &stp->st_stid); |
| - mutex_unlock(&stp->st_mutex); |
| |
| nfsd4_close_open_stateid(stp); |
| + mutex_unlock(&stp->st_mutex); |
| |
| /* put reference from nfs4_preprocess_seqid_op */ |
| nfs4_put_stid(&stp->st_stid); |