]> 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)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 13 Nov 2025 20:36:36 +0000 (15:36 -0500)
commit 3e7f011c255582d7c914133785bbba1990441713 upstream.

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>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
fs/nfsd/nfs4proc.c
fs/nfsd/nfs4state.c
fs/nfsd/nfs4xdr.c
fs/nfsd/nfsd.h
fs/nfsd/xdr4.h

index 69c86e0a0893b86e5f2a6d0bdefeed9bc034704c..ebc8689e778177f5f96a1d1b13369b515da78874 100644 (file)
@@ -2859,10 +2859,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
@@ -2939,7 +2949,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 205ee8cc6fa2b9f74d08f7938b323d03bdf8286c..a3fb6caa95f9a1012cc6f79b9638af115154d382 100644 (file)
@@ -3865,6 +3865,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 7a4297267c01ab8691128013bb8fb949ad50faa8..1f6c3db3bc6e59baab53303b98b0fcbb16419414 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 1cd0bed57bc2f27248fd66a8efef692a5e9a390c..ab22a4cef621cd85b1859d97488ada91a5ced8f2 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;