nfsd4: fix unlikely race in session replay case
Subsystems (2)
PATCH 1/1 nfsd4: fix unlikely race in session replay case
From: J. Bruce Fields <[email protected]>
In the replay case, the
renew_client(session->se_client);
happens after we've droppped the sessionid_lock, and without holding a reference on the session; so there's nothing preventing the session being freed before we get here.
--- fs/nfsd/nfs4proc.c | 3 +++ fs/nfsd/nfs4state.c | 7 +++---- fs/nfsd/nfs4xdr.c | 1 - 3 files changed, 6 insertions(+), 5 deletions(-)
fs/nfsd/nfs4proc.c +3 -0
@@ -1027,6 +1027,7 @@ nfsd4_proc_compound(struct svc_rqst *rqstp, resp->rqstp = rqstp; resp->cstate.minorversion = args->minorversion; resp->cstate.replay_owner =NULL ; + resp->cstate.session =NULL ;fh_init (&resp->cstate.current_fh,NFS4_FHSIZE );fh_init (&resp->cstate.save_fh,NFS4_FHSIZE );/* Use the deferral mechanism only for NFSv4.0 compounds */
@@ -1131,6 +1132,8 @@ encode_op:fh_put (&resp->cstate.save_fh);BUG_ON (resp->cstate.replay_owner); out: +if (resp->cstate.session) +nfsd4_put_session (resp->cstate.session);nfsd4_release_compoundargs (args);/* Reset deferral mechanism for RPC deferrals */ rqstp->rq_usedeferral =1 ;
fs/nfsd/nfs4state.c +3 -4
@@ -1443,11 +1443,10 @@ nfsd4_sequence(struct svc_rqst *rqstp, cstate->slot = slot; cstate->session = session; -/* Hold a session reference until done processing the compound: -* nfsd4_put_session called only if the cstate slot is set. -*/ -nfsd4_get_session (session); out: +/* Hold a session reference until done processing the compound. */ +if (cstate->session) +nfsd4_get_session (cstate->session);spin_unlock (&sessionid_lock);/* Renew the clientid on success and on replay */ if (cstate->session) {
fs/nfsd/nfs4xdr.c +0 -1
@@ -3310,7 +3310,6 @@ nfs4svc_encode_compoundres(struct svc_rqst *rqstp, __be32 *p, struct nfsd4_componfsd4_store_cache_entry (resp);dprintk ("%s: SET SLOT STATE TO AVAILABLE\n" , __func__); resp->cstate.slot->sl_inuse = false; -nfsd4_put_session (resp->cstate.session); }return 1 ; }
Signature · 1 line
1.6.3.3
Quoted 30 lines · J. Bruce Fields
From: J. Bruce Fields <[email protected]>
In the replay case, the
renew_client(session->se_client);happens after we've droppped the
sessionid_lock, and without holding a reference on the session; so there's nothing preventing the session being freed before we get here.Signed-off-by J. Bruce Fields <[email protected]>--- fs/nfsd/nfs4proc.c | 3 +++ fs/nfsd/nfs4state.c | 7 +++---- fs/nfsd/nfs4xdr.c | 1 - 3 files changed, 6 insertions(+), 5 deletions(-)fs/nfsd/nfs4proc.c +1
-0@@ -1027,6 +1027,7 @@ nfsd4_proc_compound(struct svc_rqst *rqstp, resp->rqstp = rqstp; resp->cstate.minorversion = args->minorversion; resp->cstate.replay_owner =NULL ; + resp->cstate.session =NULL ;fh_init (&resp->cstate.current_fh,NFS4_FHSIZE );fh_init (&resp->cstate.save_fh,NFS4_FHSIZE );/* Use the deferral mechanism only for NFSv4.0 compounds */
fs/nfsd/nfs4proc.c encode_op: · line 1136
Quoted 5 lines
fs/nfsd/nfs4proc.c +2
-0@@ -1131,6 +1132,8 @@ encode_op:fh_put (&resp->cstate.save_fh);BUG_ON (resp->cstate.replay_owner); out: +if (resp->cstate.session) +nfsd4_put_session (resp->cstate.session);
Bruce, I see what you wanted to do but unfortunately putting the session here won't work since resp->cstate.slot is referred to later on in nfs4svc_encode_compoundres and it points into the slot table which is a member of the same session, therefore the current place in which it gets dereferenced should be maintained.
Please see below for another suggestion
Quoted 27 lines
fs/nfsd/nfs4proc.c +0
-0nfsd4_release_compoundargs (args);/* Reset deferral mechanism for RPC deferrals */ rqstp->rq_usedeferral =1 ;fs/nfsd/nfs4state.c +3
-4@@ -1443,11 +1443,10 @@ nfsd4_sequence(struct svc_rqst *rqstp, cstate->slot = slot; cstate->session = session; -/* Hold a session reference until done processing the compound: -* nfsd4_put_session called only if the cstate slot is set. -*/ -nfsd4_get_session (session); out: +/* Hold a session reference until done processing the compound. */ +if (cstate->session) +nfsd4_get_session (cstate->session);spin_unlock (&sessionid_lock);/* Renew the clientid on success and on replay */ if (cstate->session) {fs/nfsd/nfs4xdr.c +0
-0
fs/nfsd/nfs4xdr.c nfs4svc_encode_compoundres(struct svc_rqst *rqstp, __be32 *p, struct nfsd4_compo · line 3315
Quoted 7 lines
fs/nfsd/nfs4xdr.c +0
-1@@ -3310,7 +3310,6 @@ nfs4svc_encode_compoundres(struct svc_rqst *rqstp, __be32 *p, struct nfsd4_componfsd4_store_cache_entry (resp);dprintk ("%s: SET SLOT STATE TO AVAILABLE\n" , __func__); resp->cstate.slot->sl_inuse = false; -nfsd4_put_session (resp->cstate.session); }return 1 ; }
How about this instead:
fs/nfsd/nfs4xdr.c +6 -4
@@ -3307,10 +3307,12 @@ nfs4svc_encode_compoundres(struct svc_rqst *rqstp, __be32 *p, struct nfsd4_compo iov = &rqstp->rq_res.head[0 ]; iov->iov_len = ((char *)resp->p) - (char *)iov->iov_base;BUG_ON (iov->iov_len >PAGE_SIZE ); -if (nfsd4_has_session (cs) && cs->status != nfserr_replay_cache) { -nfsd4_store_cache_entry (resp); -dprintk ("%s: SET SLOT STATE TO AVAILABLE\n" , __func__); - resp->cstate.slot->sl_inuse = false; +if (nfsd4_has_session (cs)) { +if (cs->status != nfserr_replay_cache) { +nfsd4_store_cache_entry (resp); +dprintk ("%s: SET SLOT STATE TO AVAILABLE\n" , __func__); + resp->cstate.slot->sl_inuse = false; + }nfsd4_put_session (resp->cstate.session); }return 1 ;
Quoted 13 lines · Benny Halevy
Review encode_op: · line 1136
Quoted 6 lines · J. Bruce Fields
(quoted diff) +2
-0@@ -1131,6 +1132,8 @@ encode_op: fh_put(&resp->cstate.save_fh); BUG_ON(resp->cstate.replay_owner); out: + if (resp->cstate.session) + nfsd4_put_session(resp->cstate.session);Bruce, I see what you wanted to do but unfortunately putting the session here won't work since
resp->cstate.slotis referred to later on innfs4svc_encode_compoundresand it points into the slot table which is a member of the same session, therefore the current place in which it gets dereferenced should be maintained.
Whoops, good catch! (Since most of the xdr encoding is done before nfsd4_proc_compound() exits, I think I must have forgotten that nfs4svc_encode_compoundres() is an exception?)
Quoted 1 line
Please see below for another suggestion
Done as you suggest. I'd applied the patch locally but not pushed it out yet, so I've updated it in place; the result is the following. I'll apply and push that to for-2.6.35 if you don't see any further probleme.
--b.
commit 44643622678d011642531915442d404ad14eb6c6
Author: J. Bruce Fields <[email protected]> Date: Sat Apr 24 15:35:43 2010 -0400
nfsd4: fix unlikely race in session replay case
In the replay case, the
renew_client(session->se_client);
happens after we've droppped the sessionid_lock, and without holding a reference on the session; so there's nothing preventing the session being freed before we get here.
Thanks to Benny Halevy for catching a bug in an earlier version of this patch.
fs/nfsd/nfs4proc.c +1 -0
@@ -1027,6 +1027,7 @@ nfsd4_proc_compound(struct svc_rqst *rqstp, resp->rqstp = rqstp; resp->cstate.minorversion = args->minorversion; resp->cstate.replay_owner =NULL ; + resp->cstate.session =NULL ;fh_init (&resp->cstate.current_fh,NFS4_FHSIZE );fh_init (&resp->cstate.save_fh,NFS4_FHSIZE );/* Use the deferral mechanism only for NFSv4.0 compounds */
fs/nfsd/nfs4state.c +3 -4
@@ -1443,11 +1443,10 @@ nfsd4_sequence(struct svc_rqst *rqstp, cstate->slot = slot; cstate->session = session; -/* Hold a session reference until done processing the compound: -* nfsd4_put_session called only if the cstate slot is set. -*/ -nfsd4_get_session (session); out: +/* Hold a session reference until done processing the compound. */ +if (cstate->session) +nfsd4_get_session (cstate->session);spin_unlock (&sessionid_lock);/* Renew the clientid on success and on replay */ if (cstate->session) {
fs/nfsd/nfs4xdr.c +6 -4
@@ -3306,10 +3306,12 @@ nfs4svc_encode_compoundres(struct svc_rqst *rqstp, __be32 *p, struct nfsd4_compo iov = &rqstp->rq_res.head[0 ]; iov->iov_len = ((char *)resp->p) - (char *)iov->iov_base;BUG_ON (iov->iov_len >PAGE_SIZE ); -if (nfsd4_has_session (cs) && cs->status != nfserr_replay_cache) { -nfsd4_store_cache_entry (resp); -dprintk ("%s: SET SLOT STATE TO AVAILABLE\n" , __func__); - resp->cstate.slot->sl_inuse = false; +if (nfsd4_has_session (cs)) { +if (cs->status != nfserr_replay_cache) { +nfsd4_store_cache_entry (resp); +dprintk ("%s: SET SLOT STATE TO AVAILABLE\n" , __func__); + resp->cstate.slot->sl_inuse = false; + }nfsd4_put_session (resp->cstate.session); }return 1 ;
Benny Halevy
Acked-byQuoted 36 lines · J. Bruce Fields
Quoted 12 lines · Benny Halevy
Review encode_op: · line 1136
Quoted 6 lines · J. Bruce Fields
(quoted diff) +2
-0@@ -1131,6 +1132,8 @@ encode_op: fh_put(&resp->cstate.save_fh); BUG_ON(resp->cstate.replay_owner); out: + if (resp->cstate.session) + nfsd4_put_session(resp->cstate.session);Bruce, I see what you wanted to do but unfortunately putting the session here won't work since
resp->cstate.slotis referred to later on innfs4svc_encode_compoundresand it points into the slot table which is a member of the same session, therefore the current place in which it gets dereferenced should be maintained.Whoops, good catch! (Since most of the xdr encoding is done before
nfsd4_proc_compound()exits, I think I must have forgotten thatnfs4svc_encode_compoundres()is an exception?)Quoted 1 line
Please see below for another suggestion
Done as you suggest. I'd applied the patch locally but not pushed it out yet, so I've updated it in place; the result is the following. I'll apply and push that to for-2.6.35 if you don't see any further probleme.
--b.
commit
44643622678d011642531915442d404ad14eb6c6Author: J. Bruce Fields <[email protected]> Date: Sat Apr 24 15:35:43 2010 -0400nfsd4: fix unlikely race in session replay case
In the replay case, the
renew_client(session->se_client);
Quoted 8 lines
happens after we've droppped the
sessionid_lock, and without holding a reference on the session; so there's nothing preventing the session being freed before we get here.Thanks to Benny Halevy for catching a bug in an earlier version of this patch.
Signed-off-by J. Bruce Fields <[email protected]>
Quoted 57 lines
fs/nfsd/nfs4proc.c +1
-0@@ -1027,6 +1027,7 @@ nfsd4_proc_compound(struct svc_rqst *rqstp, resp->rqstp = rqstp; resp->cstate.minorversion = args->minorversion; resp->cstate.replay_owner =NULL ; + resp->cstate.session =NULL ;fh_init (&resp->cstate.current_fh,NFS4_FHSIZE );fh_init (&resp->cstate.save_fh,NFS4_FHSIZE );/* Use the deferral mechanism only for NFSv4.0 compounds */ fs/nfsd/nfs4state.c +3
-4@@ -1443,11 +1443,10 @@ nfsd4_sequence(struct svc_rqst *rqstp, cstate->slot = slot; cstate->session = session; -/* Hold a session reference until done processing the compound: -* nfsd4_put_session called only if the cstate slot is set. -*/ -nfsd4_get_session (session); out: +/* Hold a session reference until done processing the compound. */ +if (cstate->session) +nfsd4_get_session (cstate->session);spin_unlock (&sessionid_lock);/* Renew the clientid on success and on replay */ if (cstate->session) {fs/nfsd/nfs4xdr.c +6
-5@@ -3306,10 +3306,12 @@ nfs4svc_encode_compoundres(struct svc_rqst *rqstp, __be32 *p, struct nfsd4_compo iov = &rqstp->rq_res.head[0 ]; iov->iov_len = ((char *)resp->p) - (char *)iov->iov_base;BUG_ON (iov->iov_len >PAGE_SIZE ); -if (nfsd4_has_session (cs) && cs->status != nfserr_replay_cache) { -nfsd4_store_cache_entry (resp); -dprintk ("%s: SET SLOT STATE TO AVAILABLE\n" , __func__); - resp->cstate.slot->sl_inuse = false; +if (nfsd4_has_session (cs)) { +if (cs->status != nfserr_replay_cache) { +nfsd4_store_cache_entry (resp); +dprintk ("%s: SET SLOT STATE TO AVAILABLE\n" , __func__); + resp->cstate.slot->sl_inuse = false; + }nfsd4_put_session (resp->cstate.session); }return 1 ; --To unsubscribe from this list: send the line "unsubscribe linux-nfs" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html