]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
Revert "NFSD: Remove the cap on number of operations per NFSv4 COMPOUND"
authorChuck Lever <chuck.lever@oracle.com>
Thu, 2 Oct 2025 14:00:51 +0000 (10:00 -0400)
committerChuck Lever <chuck.lever@oracle.com>
Tue, 21 Oct 2025 15:03:50 +0000 (11:03 -0400)
I've found that pynfs COMP6 now leaves the connection or lease in a
strange state, which causes CLOSE9 to hang indefinitely. I've dug
into it a little, but I haven't been able to root-cause it yet.
However, I bisected to commit 48aab1606fa8 ("NFSD: Remove the cap on
number of operations per NFSv4 COMPOUND").

Tianshuo Han also reports a potential vulnerability when decoding
an NFSv4 COMPOUND. An attacker can place an arbitrarily large op
count in the COMPOUND header, which results in:

[   51.410584] nfsd: vmalloc error: size 1209533382144, exceeds total
pages, mode:0xdc0(GFP_KERNEL|__GFP_ZERO),
nodemask=(null),cpuset=/,mems_allowed=0

when NFSD attempts to allocate the COMPOUND op array.

Let's restore the operation-per-COMPOUND limit, but increased to 200
for now.

Reported-by: tianshuo han <hantianshuo233@gmail.com>
Reviewed-by: Jeff Layton <jlayton@kernel.org>
Cc: stable@vger.kernel.org
Tested-by: Tianshuo Han <hantianshuo233@gmail.com>
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
fs/nfsd/nfs4proc.c
fs/nfsd/nfs4state.c
fs/nfsd/nfs4xdr.c
fs/nfsd/nfsd.h
fs/nfsd/xdr4.h

index f9aeefc0da731f321af4f812d289951c31c9646e..7f7e6bb23a90d9a1cafd154c0f09e236df75b083 100644 (file)
@@ -2893,10 +2893,20 @@ nfsd4_proc_compound(struct svc_rqst *rqstp)
 
        rqstp->rq_lease_breaker = (void **)&cstate->clp;
 
-       trace_nfsd_compound(rqstp, args->tag, args->taglen, args->opcnt);
+       trace_nfsd_compound(rqstp, args->tag, args->taglen, 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
@@ -2973,7 +2983,7 @@ encode_op:
                        status = op->status;
                }
 
-               trace_nfsd_compound_status(args->opcnt, resp->opcnt,
+               trace_nfsd_compound_status(args->client_opcnt, resp->opcnt,
                                           status, nfsd4_op_name(op->opnum));
 
                nfsd4_cstate_clear_replay(cstate);
index 81fa7cc6c77b3cdc5ff22bc60ab0654f95dc258d..c1b54322c41278b8da08db0e8d901f8bc649a125 100644 (file)
@@ -3902,6 +3902,7 @@ static __be32 check_forechannel_attrs(struct nfsd4_channel_attrs *ca, struct nfs
        ca->headerpadsz = 0;
        ca->maxreq_sz = min_t(u32, ca->maxreq_sz, maxrpc);
        ca->maxresp_sz = min_t(u32, ca->maxresp_sz, maxrpc);
+       ca->maxops = min_t(u32, ca->maxops, NFSD_MAX_OPS_PER_COMPOUND);
        ca->maxresp_cached = min_t(u32, ca->maxresp_cached,
                        NFSD_SLOT_CACHE_SIZE + NFSD_MIN_HDR_SEQ_SZ);
        ca->maxreqs = min_t(u32, ca->maxreqs, NFSD_MAX_SLOTS_PER_SESSION);
index b689b792c21f2b8da39123d78e7046438eaa21e8..6040a6145dad8556fa5e5b47be44828b860a1b9a 100644 (file)
@@ -2488,8 +2488,10 @@ nfsd4_decode_compound(struct nfsd4_compoundargs *argp)
 
        if (xdr_stream_decode_u32(argp->xdr, &argp->minorversion) < 0)
                return false;
-       if (xdr_stream_decode_u32(argp->xdr, &argp->opcnt) < 0)
+       if (xdr_stream_decode_u32(argp->xdr, &argp->client_opcnt) < 0)
                return false;
+       argp->opcnt = min_t(u32, argp->client_opcnt,
+                           NFSD_MAX_OPS_PER_COMPOUND);
 
        if (argp->opcnt > ARRAY_SIZE(argp->iops)) {
                argp->ops = vcalloc(argp->opcnt, sizeof(*argp->ops));
index ea87b42894dd263719df3d42940abc2dd8850592..f19320018639ebe5bdbf02e19ce266f6978bd2cf 100644 (file)
@@ -57,6 +57,9 @@ struct readdir_cd {
        __be32                  err;    /* 0, nfserr, or nfserr_eof */
 };
 
+/* Maximum number of operations per session compound */
+#define NFSD_MAX_OPS_PER_COMPOUND      200
+
 struct nfsd_genl_rqstp {
        struct sockaddr         rq_daddr;
        struct sockaddr         rq_saddr;
index d4b48602b2b0c3854473e8a5812652815ab26c12..ee0570cbdd9e88095fd1bd12a3805795790e99c1 100644 (file)
@@ -903,6 +903,7 @@ struct nfsd4_compoundargs {
        char *                          tag;
        u32                             taglen;
        u32                             minorversion;
+       u32                             client_opcnt;
        u32                             opcnt;
        bool                            splice_ok;
        struct nfsd4_op                 *ops;