nfsd: handle repeated BIND_CONN_TO_SESSION
authorJ. Bruce Fields <bfields@redhat.com>
Mon, 27 Apr 2020 21:59:01 +0000 (17:59 -0400)
committerJ. Bruce Fields <bfields@redhat.com>
Wed, 6 May 2020 19:59:23 +0000 (15:59 -0400)
If the client attempts BIND_CONN_TO_SESSION on an already bound
connection, it should be either a no-op or an error.

Signed-off-by: J. Bruce Fields <bfields@redhat.com>
fs/nfsd/nfs4state.c

index 2c8567b..fe88f5f 100644 (file)
@@ -3485,6 +3485,45 @@ __be32 nfsd4_backchannel_ctl(struct svc_rqst *rqstp,
        return nfs_ok;
 }
 
+static struct nfsd4_conn *__nfsd4_find_conn(struct svc_xprt *xpt, struct nfsd4_session *s)
+{
+       struct nfsd4_conn *c;
+
+       list_for_each_entry(c, &s->se_conns, cn_persession) {
+               if (c->cn_xprt == xpt) {
+                       return c;
+               }
+       }
+       return NULL;
+}
+
+static __be32 nfsd4_match_existing_connection(struct svc_rqst *rqst,
+                               struct nfsd4_session *session, u32 req)
+{
+       struct nfs4_client *clp = session->se_client;
+       struct svc_xprt *xpt = rqst->rq_xprt;
+       struct nfsd4_conn *c;
+       __be32 status;
+
+       /* Following the last paragraph of RFC 5661 Section 18.34.3: */
+       spin_lock(&clp->cl_lock);
+       c = __nfsd4_find_conn(xpt, session);
+       if (!c)
+               status = nfserr_noent;
+       else if (req == c->cn_flags)
+               status = nfs_ok;
+       else if (req == NFS4_CDFC4_FORE_OR_BOTH &&
+                               c->cn_flags != NFS4_CDFC4_BACK)
+               status = nfs_ok;
+       else if (req == NFS4_CDFC4_BACK_OR_BOTH &&
+                               c->cn_flags != NFS4_CDFC4_FORE)
+               status = nfs_ok;
+       else
+               status = nfserr_inval;
+       spin_unlock(&clp->cl_lock);
+       return status;
+}
+
 __be32 nfsd4_bind_conn_to_session(struct svc_rqst *rqstp,
                     struct nfsd4_compound_state *cstate,
                     union nfsd4_op_u *u)
@@ -3506,6 +3545,9 @@ __be32 nfsd4_bind_conn_to_session(struct svc_rqst *rqstp,
        status = nfserr_wrong_cred;
        if (!nfsd4_mach_creds_match(session->se_client, rqstp))
                goto out;
+       status = nfsd4_match_existing_connection(rqstp, session, bcts->dir);
+       if (status == nfs_ok || status == nfserr_inval)
+               goto out;
        status = nfsd4_map_bcts_dir(&bcts->dir);
        if (status)
                goto out;
@@ -3571,18 +3613,6 @@ out:
        return status;
 }
 
-static struct nfsd4_conn *__nfsd4_find_conn(struct svc_xprt *xpt, struct nfsd4_session *s)
-{
-       struct nfsd4_conn *c;
-
-       list_for_each_entry(c, &s->se_conns, cn_persession) {
-               if (c->cn_xprt == xpt) {
-                       return c;
-               }
-       }
-       return NULL;
-}
-
 static __be32 nfsd4_sequence_check_conn(struct nfsd4_conn *new, struct nfsd4_session *ses)
 {
        struct nfs4_client *clp = ses->se_client;