diff mbox

[v3] nfsd: deal with revoked delegations appropriately

Message ID 20171103180631.76071-1-aweits@rit.edu (mailing list archive)
State New, archived
Headers show

Commit Message

Andrew W Elble Nov. 3, 2017, 6:06 p.m. UTC
If a delegation has been revoked by the server, operations using that
delegation should error out with NFS4ERR_DELEG_REVOKED in the >4.1
case, and NFS4ERR_BAD_STATEID otherwise.

Signed-off-by: Andrew Elble <aweits@rit.edu>
---
 v2: deconflicting with Trond's OPEN/CLOSE locking work
 v3: don't return NFS4_OK on DELEGRETURN with revoked delegation
 
 fs/nfsd/nfs4state.c | 25 ++++++++++++++++++++++++-
 1 file changed, 24 insertions(+), 1 deletion(-)

Comments

Trond Myklebust Nov. 3, 2017, 6:36 p.m. UTC | #1
Thanks for the quick turnaround!

On Fri, 2017-11-03 at 14:06 -0400, Andrew Elble wrote:
> If a delegation has been revoked by the server, operations using that

> delegation should error out with NFS4ERR_DELEG_REVOKED in the >4.1

> case, and NFS4ERR_BAD_STATEID otherwise.

> 

> Signed-off-by: Andrew Elble <aweits@rit.edu>


Reviewed-by: Trond Myklebust <trond.myklebust@primarydata.com>


> ---

>  v2: deconflicting with Trond's OPEN/CLOSE locking work

>  v3: don't return NFS4_OK on DELEGRETURN with revoked delegation

>  

>  fs/nfsd/nfs4state.c | 25 ++++++++++++++++++++++++-

>  1 file changed, 24 insertions(+), 1 deletion(-)

> 

> diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c

> index 0c04f81aa63b..d386d569edbc 100644

> --- a/fs/nfsd/nfs4state.c

> +++ b/fs/nfsd/nfs4state.c

> @@ -3966,7 +3966,8 @@ static struct nfs4_delegation

> *find_deleg_stateid(struct nfs4_client *cl, statei

>  {

>  	struct nfs4_stid *ret;

>  

> -	ret = find_stateid_by_type(cl, s, NFS4_DELEG_STID);

> +	ret = find_stateid_by_type(cl, s,

> +				NFS4_DELEG_STID|NFS4_REVOKED_DELEG_S

> TID);

>  	if (!ret)

>  		return NULL;

>  	return delegstateid(ret);

> @@ -3989,6 +3990,12 @@ static bool nfsd4_is_deleg_cur(struct

> nfsd4_open *open)

>  	deleg = find_deleg_stateid(cl, &open->op_delegate_stateid);

>  	if (deleg == NULL)

>  		goto out;

> +	if (deleg->dl_stid.sc_type == NFS4_REVOKED_DELEG_STID) {

> +		nfs4_put_stid(&deleg->dl_stid);

> +		if (cl->cl_minorversion)

> +			status = nfserr_deleg_revoked;

> +		goto out;

> +	}

>  	flags = share_access_to_flags(open->op_share_access);

>  	status = nfs4_check_delegmode(deleg, flags);

>  	if (status) {

> @@ -4858,6 +4865,16 @@ static __be32 nfsd4_validate_stateid(struct

> nfs4_client *cl, stateid_t *stateid)

>  		     struct nfs4_stid **s, struct nfsd_net *nn)

>  {

>  	__be32 status;

> +	bool return_revoked = false;

> +

> +	/*

> +	 *  only return revoked delegations if explicitly asked.

> +	 *  otherwise we report revoked or bad_stateid status.

> +	 */

> +	if (typemask & NFS4_REVOKED_DELEG_STID)

> +		return_revoked = true;

> +	else if (typemask & NFS4_DELEG_STID)

> +		typemask |= NFS4_REVOKED_DELEG_STID;

>  

>  	if (ZERO_STATEID(stateid) || ONE_STATEID(stateid))

>  		return nfserr_bad_stateid;

> @@ -4872,6 +4889,12 @@ static __be32 nfsd4_validate_stateid(struct

> nfs4_client *cl, stateid_t *stateid)

>  	*s = find_stateid_by_type(cstate->clp, stateid, typemask);

>  	if (!*s)

>  		return nfserr_bad_stateid;

> +	if (((*s)->sc_type == NFS4_REVOKED_DELEG_STID) &&

> !return_revoked) {

> +		nfs4_put_stid(*s);

> +		if (cstate->minorversion)

> +			return nfserr_deleg_revoked;

> +		return nfserr_bad_stateid;

> +	}

>  	return nfs_ok;

>  }

>  

-- 
Trond Myklebust
Linux NFS client maintainer, PrimaryData
trond.myklebust@primarydata.com
J. Bruce Fields Nov. 6, 2017, 3:15 p.m. UTC | #2
On Fri, Nov 03, 2017 at 06:36:55PM +0000, Trond Myklebust wrote:
> Thanks for the quick turnaround!
> 
> On Fri, 2017-11-03 at 14:06 -0400, Andrew Elble wrote:
> > If a delegation has been revoked by the server, operations using that
> > delegation should error out with NFS4ERR_DELEG_REVOKED in the >4.1
> > case, and NFS4ERR_BAD_STATEID otherwise.
> > 
> > Signed-off-by: Andrew Elble <aweits@rit.edu>
> 
> Reviewed-by: Trond Myklebust <trond.myklebust@primarydata.com>

I wonder if there's a way to simplify the resulting logic in
nfsd4_lookup_stateid--I guess I don't see anything.

Could we get some context here in the changelog, though?  What actual
problem was this causing?

--b.

> 
> > ---
> >  v2: deconflicting with Trond's OPEN/CLOSE locking work
> >  v3: don't return NFS4_OK on DELEGRETURN with revoked delegation
> >  
> >  fs/nfsd/nfs4state.c | 25 ++++++++++++++++++++++++-
> >  1 file changed, 24 insertions(+), 1 deletion(-)
> > 
> > diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c
> > index 0c04f81aa63b..d386d569edbc 100644
> > --- a/fs/nfsd/nfs4state.c
> > +++ b/fs/nfsd/nfs4state.c
> > @@ -3966,7 +3966,8 @@ static struct nfs4_delegation
> > *find_deleg_stateid(struct nfs4_client *cl, statei
> >  {
> >  	struct nfs4_stid *ret;
> >  
> > -	ret = find_stateid_by_type(cl, s, NFS4_DELEG_STID);
> > +	ret = find_stateid_by_type(cl, s,
> > +				NFS4_DELEG_STID|NFS4_REVOKED_DELEG_S
> > TID);
> >  	if (!ret)
> >  		return NULL;
> >  	return delegstateid(ret);
> > @@ -3989,6 +3990,12 @@ static bool nfsd4_is_deleg_cur(struct
> > nfsd4_open *open)
> >  	deleg = find_deleg_stateid(cl, &open->op_delegate_stateid);
> >  	if (deleg == NULL)
> >  		goto out;
> > +	if (deleg->dl_stid.sc_type == NFS4_REVOKED_DELEG_STID) {
> > +		nfs4_put_stid(&deleg->dl_stid);
> > +		if (cl->cl_minorversion)
> > +			status = nfserr_deleg_revoked;
> > +		goto out;
> > +	}
> >  	flags = share_access_to_flags(open->op_share_access);
> >  	status = nfs4_check_delegmode(deleg, flags);
> >  	if (status) {
> > @@ -4858,6 +4865,16 @@ static __be32 nfsd4_validate_stateid(struct
> > nfs4_client *cl, stateid_t *stateid)
> >  		     struct nfs4_stid **s, struct nfsd_net *nn)
> >  {
> >  	__be32 status;
> > +	bool return_revoked = false;
> > +
> > +	/*
> > +	 *  only return revoked delegations if explicitly asked.
> > +	 *  otherwise we report revoked or bad_stateid status.
> > +	 */
> > +	if (typemask & NFS4_REVOKED_DELEG_STID)
> > +		return_revoked = true;
> > +	else if (typemask & NFS4_DELEG_STID)
> > +		typemask |= NFS4_REVOKED_DELEG_STID;
> >  
> >  	if (ZERO_STATEID(stateid) || ONE_STATEID(stateid))
> >  		return nfserr_bad_stateid;
> > @@ -4872,6 +4889,12 @@ static __be32 nfsd4_validate_stateid(struct
> > nfs4_client *cl, stateid_t *stateid)
> >  	*s = find_stateid_by_type(cstate->clp, stateid, typemask);
> >  	if (!*s)
> >  		return nfserr_bad_stateid;
> > +	if (((*s)->sc_type == NFS4_REVOKED_DELEG_STID) &&
> > !return_revoked) {
> > +		nfs4_put_stid(*s);
> > +		if (cstate->minorversion)
> > +			return nfserr_deleg_revoked;
> > +		return nfserr_bad_stateid;
> > +	}
> >  	return nfs_ok;
> >  }
> >  
> -- 
> Trond Myklebust
> Linux NFS client maintainer, PrimaryData
> trond.myklebust@primarydata.com
--
To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Andrew W Elble Nov. 6, 2017, 5:30 p.m. UTC | #3
"bfields@fieldses.org" <bfields@fieldses.org> writes:

> On Fri, Nov 03, 2017 at 06:36:55PM +0000, Trond Myklebust wrote:
>> Thanks for the quick turnaround!
>> 
>> On Fri, 2017-11-03 at 14:06 -0400, Andrew Elble wrote:
>> > If a delegation has been revoked by the server, operations using that
>> > delegation should error out with NFS4ERR_DELEG_REVOKED in the >4.1
>> > case, and NFS4ERR_BAD_STATEID otherwise.
>> > 
>> > Signed-off-by: Andrew Elble <aweits@rit.edu>
>> 
>> Reviewed-by: Trond Myklebust <trond.myklebust@primarydata.com>
>
> I wonder if there's a way to simplify the resulting logic in
> nfsd4_lookup_stateid--I guess I don't see anything.
>
> Could we get some context here in the changelog, though?  What actual
> problem was this causing?

Prior thread (roughly) here: http://www.spinics.net/lists/linux-nfs/msg55260.html

This is the one patch I'm still carrying from the lost delegation work a
while back. Testing showed that there is a path still open to lost
delegations via delegreturn.

running with:
echo "error != 0" | sudo tee /sys/kernel/debug/tracing/events/nfs4/nfs4_delegreturn_exit/filter

gave us this at one point with an interim version of this patch:

kworker/0:0H-3990  [000] .... 5899655.609266: nfs4_delegreturn_exit: 
               error=-10087 (DELEG_REVOKED) dev=00:30 fhandle=0xe43d9d3a
kworker/0:2H-12665 [000] .... 5900011.719468: nfs4_delegreturn_exit: 
               error=-10087 (DELEG_REVOKED) dev=00:30 fhandle=0x16e37c0a

The Linux client prior to 26d36301bd653df6481fd38f3e1435a1f15e56d1 would
just drop delegations that suffered from a nfserr_bad_stateid during
delegreturn. Now it will do test/free if the return error is
nfserr_deleg_revoked.

If the client drops a delegation while the server has it on the revoked
list, we stay stuck in endless state manager looping for that client.

It might be a good idea for a stable backport of aforementioned commit,
or some kind of other workaround? Possibly interpreting
nfserr_bad_stateid an analogue of nfserr_deleg_revoked clientside
when dealing with a >4.0 mount? (also seems like an error on the putfh
might need to be caught as well?)

Thanks,

Andy
J. Bruce Fields Nov. 6, 2017, 7:35 p.m. UTC | #4
On Mon, Nov 06, 2017 at 12:30:23PM -0500, Andrew W Elble wrote:
> Prior thread (roughly) here: http://www.spinics.net/lists/linux-nfs/msg55260.html
> 
> This is the one patch I'm still carrying from the lost delegation work a
> while back. Testing showed that there is a path still open to lost
> delegations via delegreturn.
> 
> running with:
> echo "error != 0" | sudo tee /sys/kernel/debug/tracing/events/nfs4/nfs4_delegreturn_exit/filter
> 
> gave us this at one point with an interim version of this patch:
> 
> kworker/0:0H-3990  [000] .... 5899655.609266: nfs4_delegreturn_exit: 
>                error=-10087 (DELEG_REVOKED) dev=00:30 fhandle=0xe43d9d3a
> kworker/0:2H-12665 [000] .... 5900011.719468: nfs4_delegreturn_exit: 
>                error=-10087 (DELEG_REVOKED) dev=00:30 fhandle=0x16e37c0a
> 
> The Linux client prior to 26d36301bd653df6481fd38f3e1435a1f15e56d1 would
> just drop delegations that suffered from a nfserr_bad_stateid during
> delegreturn. Now it will do test/free if the return error is
> nfserr_deleg_revoked.
> 
> If the client drops a delegation while the server has it on the revoked
> list, we stay stuck in endless state manager looping for that client.
> 
> It might be a good idea for a stable backport of aforementioned commit,
> or some kind of other workaround? Possibly interpreting
> nfserr_bad_stateid an analogue of nfserr_deleg_revoked clientside
> when dealing with a >4.0 mount? (also seems like an error on the putfh
> might need to be caught as well?)

I'm just looking for a concise explanation of why your patch is
important.  And I probably haven't dug enough, but I'm still not quite
following.

If I understand right, the only visible change from your patch will be
returning DELEG_REVOKED instead of BAD_STATEID to 4.1 clients in some
cases.  I'm not clear what the result was (for old or new clients)--a
client left believing it held a delegation when it didn't, or a client
entering an infinite state manager loop?

--b.
--
To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Andrew W Elble Nov. 6, 2017, 9:25 p.m. UTC | #5
"bfields@fieldses.org" <bfields@fieldses.org> writes:

> I'm just looking for a concise explanation of why your patch is
> important.  And I probably haven't dug enough, but I'm still not quite
> following.
>
> If I understand right, the only visible change from your patch will be
> returning DELEG_REVOKED instead of BAD_STATEID to 4.1 clients in some
> cases.  I'm not clear what the result was (for old or new clients)--a
> client left believing it held a delegation when it didn't, or a client
> entering an infinite state manager loop?

The current Linux client will "lose" a delegation on DELEGRETURN if it
does not see NFS4ERR_DELEG_REVOKED. This is unrecoverable and will
result in the client state manager looping unable to satisfy the
server's inevitable assertion of SEQ4_STATUS_RECALLABLE_STATE_REVOKED.

RFC5661 10.2.1: "A client normally finds out about revocation of a delegation
when it uses a stateid associated with a delegation and receives one of
the errors NFS4ERR_EXPIRED, NFS4ERR_ADMIN_REVOKED, or NFS4ERR_DELEG_REVOKED."

Thanks,

Andy
diff mbox

Patch

diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c
index 0c04f81aa63b..d386d569edbc 100644
--- a/fs/nfsd/nfs4state.c
+++ b/fs/nfsd/nfs4state.c
@@ -3966,7 +3966,8 @@  static struct nfs4_delegation *find_deleg_stateid(struct nfs4_client *cl, statei
 {
 	struct nfs4_stid *ret;
 
-	ret = find_stateid_by_type(cl, s, NFS4_DELEG_STID);
+	ret = find_stateid_by_type(cl, s,
+				NFS4_DELEG_STID|NFS4_REVOKED_DELEG_STID);
 	if (!ret)
 		return NULL;
 	return delegstateid(ret);
@@ -3989,6 +3990,12 @@  static bool nfsd4_is_deleg_cur(struct nfsd4_open *open)
 	deleg = find_deleg_stateid(cl, &open->op_delegate_stateid);
 	if (deleg == NULL)
 		goto out;
+	if (deleg->dl_stid.sc_type == NFS4_REVOKED_DELEG_STID) {
+		nfs4_put_stid(&deleg->dl_stid);
+		if (cl->cl_minorversion)
+			status = nfserr_deleg_revoked;
+		goto out;
+	}
 	flags = share_access_to_flags(open->op_share_access);
 	status = nfs4_check_delegmode(deleg, flags);
 	if (status) {
@@ -4858,6 +4865,16 @@  static __be32 nfsd4_validate_stateid(struct nfs4_client *cl, stateid_t *stateid)
 		     struct nfs4_stid **s, struct nfsd_net *nn)
 {
 	__be32 status;
+	bool return_revoked = false;
+
+	/*
+	 *  only return revoked delegations if explicitly asked.
+	 *  otherwise we report revoked or bad_stateid status.
+	 */
+	if (typemask & NFS4_REVOKED_DELEG_STID)
+		return_revoked = true;
+	else if (typemask & NFS4_DELEG_STID)
+		typemask |= NFS4_REVOKED_DELEG_STID;
 
 	if (ZERO_STATEID(stateid) || ONE_STATEID(stateid))
 		return nfserr_bad_stateid;
@@ -4872,6 +4889,12 @@  static __be32 nfsd4_validate_stateid(struct nfs4_client *cl, stateid_t *stateid)
 	*s = find_stateid_by_type(cstate->clp, stateid, typemask);
 	if (!*s)
 		return nfserr_bad_stateid;
+	if (((*s)->sc_type == NFS4_REVOKED_DELEG_STID) && !return_revoked) {
+		nfs4_put_stid(*s);
+		if (cstate->minorversion)
+			return nfserr_deleg_revoked;
+		return nfserr_bad_stateid;
+	}
 	return nfs_ok;
 }