]> git.proxmox.com Git - mirror_ubuntu-jammy-kernel.git/commitdiff
NFSD: Fix handling of oversized NFSv4 COMPOUND requests
authorChuck Lever <chuck.lever@oracle.com>
Mon, 5 Sep 2022 19:33:32 +0000 (15:33 -0400)
committerStefan Bader <stefan.bader@canonical.com>
Thu, 24 Nov 2022 13:24:13 +0000 (14:24 +0100)
BugLink: https://bugs.launchpad.net/bugs/1996825
[ Upstream commit 7518a3dc5ea249d4112156ce71b8b184eb786151 ]

If an NFS server returns NFS4ERR_RESOURCE on the first operation in
an NFSv4 COMPOUND, there's no way for a client to know where the
problem is and then simplify the compound to make forward progress.

So instead, make NFSD process as many operations in an oversized
COMPOUND as it can and then return NFS4ERR_RESOURCE on the first
operation it did not process.

pynfs NFSv4.0 COMP6 exercises this case, but checks only for the
COMPOUND status code, not whether the server has processed any
of the operations.

pynfs NFSv4.1 SEQ6 and SEQ7 exercise the NFSv4.1 case, which detects
too many operations per COMPOUND by checking against the limits
negotiated when the session was created.

Suggested-by: Bruce Fields <bfields@fieldses.org>
Fixes: 0078117c6d91 ("nfsd: return RESOURCE not GARBAGE_ARGS on too many ops")
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>
Signed-off-by: Kamal Mostafa <kamal@canonical.com>
Signed-off-by: Stefan Bader <stefan.bader@canonical.com>
fs/nfsd/nfs4proc.c
fs/nfsd/nfs4xdr.c
fs/nfsd/xdr4.h

index 994421752d6c4de8a69a36d06e377926da4bbc57..59061da3b1d101227b34a04d0c5fb8993ddd90e8 100644 (file)
@@ -2493,9 +2493,6 @@ nfsd4_proc_compound(struct svc_rqst *rqstp)
        status = nfserr_minor_vers_mismatch;
        if (nfsd_minorversion(nn, args->minorversion, NFSD_TEST) <= 0)
                goto out;
-       status = nfserr_resource;
-       if (args->opcnt > NFSD_MAX_OPS_PER_COMPOUND)
-               goto out;
 
        status = nfs41_check_op_ordering(args);
        if (status) {
@@ -2508,10 +2505,20 @@ nfsd4_proc_compound(struct svc_rqst *rqstp)
 
        rqstp->rq_lease_breaker = (void **)&cstate->clp;
 
-       trace_nfsd_compound(rqstp, args->opcnt);
+       trace_nfsd_compound(rqstp, args->client_opcnt);
        while (!status && resp->opcnt < args->opcnt) {
                op = &args->ops[resp->opcnt++];
 
+               if (unlikely(resp->opcnt == NFSD_MAX_OPS_PER_COMPOUND)) {
+                       /* If there are still more operations to process,
+                        * stop here and report NFS4ERR_RESOURCE. */
+                       if (cstate->minorversion == 0 &&
+                           args->client_opcnt > resp->opcnt) {
+                               op->status = nfserr_resource;
+                               goto encode_op;
+                       }
+               }
+
                /*
                 * The XDR decode routines may have pre-set op->status;
                 * for example, if there is a miscellaneous XDR error
@@ -2587,8 +2594,8 @@ encode_op:
                        status = op->status;
                }
 
-               trace_nfsd_compound_status(args->opcnt, resp->opcnt, status,
-                                          nfsd4_op_name(op->opnum));
+               trace_nfsd_compound_status(args->client_opcnt, resp->opcnt,
+                                          status, nfsd4_op_name(op->opnum));
 
                nfsd4_cstate_clear_replay(cstate);
                nfsd4_increment_op_stats(op->opnum);
index a7684dea71d0892fb059a8103011399905afe962..65bcd66590362ab4362dc9e7e475185e0809b22e 100644 (file)
@@ -2349,16 +2349,11 @@ nfsd4_decode_compound(struct nfsd4_compoundargs *argp)
 
        if (xdr_stream_decode_u32(argp->xdr, &argp->minorversion) < 0)
                return 0;
-       if (xdr_stream_decode_u32(argp->xdr, &argp->opcnt) < 0)
+       if (xdr_stream_decode_u32(argp->xdr, &argp->client_opcnt) < 0)
                return 0;
 
-       /*
-        * NFS4ERR_RESOURCE is a more helpful error than GARBAGE_ARGS
-        * here, so we return success at the xdr level so that
-        * nfsd4_proc can handle this is an NFS-level error.
-        */
-       if (argp->opcnt > NFSD_MAX_OPS_PER_COMPOUND)
-               return 1;
+       argp->opcnt = min_t(u32, argp->client_opcnt,
+                           NFSD_MAX_OPS_PER_COMPOUND);
 
        if (argp->opcnt > ARRAY_SIZE(argp->iops)) {
                argp->ops = kzalloc(argp->opcnt * sizeof(*argp->ops), GFP_KERNEL);
index d51c5cc2f272f97a11c8b1a96dafbd3a132a0444..e017e3de39016f4b5575a96000c6eac645780010 100644 (file)
@@ -688,9 +688,10 @@ struct nfsd4_compoundargs {
        struct svcxdr_tmpbuf            *to_free;
        struct svc_rqst                 *rqstp;
 
-       u32                             taglen;
        char *                          tag;
+       u32                             taglen;
        u32                             minorversion;
+       u32                             client_opcnt;
        u32                             opcnt;
        struct nfsd4_op                 *ops;
        struct nfsd4_op                 iops[8];