[PATCH] nfsd: prevent NFSv4.1 SEQUENCE reply-cache overflow
From: Mayank Jangid (OpenSec Intelligence)
Date: Tue Sep 22 2026 - 08:13:14 EST
nfsd4_sequence() narrows the reply buffer to the session cached-response
limit before accepting the slot sequence ID. A client can negotiate
ca_maxresponsesize_cached down to NFSD_MIN_HDR_SEQ_SZ, leaving no storage
in the slot trailing sl_data[] array.
A padded COMPOUND tag can then leave enough space for the SEQUENCE opcode
but not its status word. The encoder returns without setting
cstate.data_offset, and nfsd4_sequence_done() consequently copies the
whole reply from offset zero into the zero-capacity slot cache, causing a
heap out-of-bounds write.
Compute the fixed SEQUENCE reply size, including room for a following
operation error when necessary, before restricting the reply buffer and
accepting the slot. Return NFS4ERR_REP_TOO_BIG_TO_CACHE without changing
the slot when the result cannot fit.
Also record the appropriate NFS error when an operation header cannot be
encoded. This prevents an incomplete operation from remaining marked
successful and being treated as cacheable.
Fixes: 47ee52986472 ("nfsd4: adjust buflen to session channel limit")
Cc: stable@xxxxxxxxxxxxxxx
Assisted-by: LLM
Co-developed-by: Kushal Khemka (OpenSec Intelligence) <kushalkhemka559@xxxxxxxxx>
Signed-off-by: Kushal Khemka (OpenSec Intelligence) <kushalkhemka559@xxxxxxxxx>
Co-developed-by: Kkartik Aggarwal (OpenSec Intelligence) <aggarwalkkartik@xxxxxxxxx>
Signed-off-by: Kkartik Aggarwal (OpenSec Intelligence) <aggarwalkkartik@xxxxxxxxx>
Signed-off-by: Mayank Jangid (OpenSec Intelligence) <mayank.jangid.moon@xxxxxxxxx>
---
fs/nfsd/nfs4state.c | 18 +++++++++++++++++-
fs/nfsd/nfs4xdr.c | 12 ++++++++++--
2 files changed, 27 insertions(+), 3 deletions(-)
diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c
index 9c4adf311..6c246d851 100644
--- a/fs/nfsd/nfs4state.c
+++ b/fs/nfsd/nfs4state.c
@@ -5016,6 +5016,7 @@ __be32
nfsd4_sequence(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
union nfsd4_op_u *u)
{
+ struct nfsd4_compoundargs *args = rqstp->rq_argp;
struct nfsd4_sequence *seq = &u->sequence;
struct nfsd4_compoundres *resp = rqstp->rq_resp;
struct xdr_stream *xdr = resp->xdr;
@@ -5025,6 +5026,7 @@ nfsd4_sequence(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
struct nfsd4_conn *conn;
__be32 status;
int buflen;
+ u32 maxlen, respsize;
struct net *net = SVC_NET(rqstp);
struct nfsd_net *nn = net_generic(net, nfsd_net_id);
@@ -5102,7 +5104,21 @@ nfsd4_sequence(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
session->se_fchannel.maxresp_sz;
status = (seq->cachethis) ? nfserr_rep_too_big_to_cache :
nfserr_rep_too_big;
- if (xdr_restrict_buflen(xdr, buflen - rqstp->rq_auth_slack))
+ if (buflen < rqstp->rq_auth_slack)
+ goto out_put_session;
+ maxlen = buflen - rqstp->rq_auth_slack;
+
+ /*
+ * Ensure the SEQUENCE result and, when needed, the next operation's
+ * error result fit before narrowing the buffer and accepting the slot.
+ */
+ respsize = nfsd4_max_reply(rqstp, &args->ops[0]);
+ if (!nfsd4_last_compound_op(rqstp))
+ respsize += COMPOUND_ERR_SLACK_SPACE;
+ if (xdr->buf->len > maxlen || respsize > maxlen - xdr->buf->len)
+ goto out_put_session;
+
+ if (xdr_restrict_buflen(xdr, maxlen))
goto out_put_session;
svc_reserve_auth(rqstp, buflen);
diff --git a/fs/nfsd/nfs4xdr.c b/fs/nfsd/nfs4xdr.c
index 606ddcb08..e5489eec6 100644
--- a/fs/nfsd/nfs4xdr.c
+++ b/fs/nfsd/nfs4xdr.c
@@ -6637,11 +6637,19 @@ nfsd4_encode_operation(struct nfsd4_compoundres *resp, struct nfsd4_op *op)
unsigned int op_status_offset;
nfsd4_enc encoder;
- if (xdr_stream_encode_u32(xdr, op->opnum) != XDR_UNIT)
+ /*
+ * nfsd4_proc_compound() stops only when op->status records an error.
+ * Do not leave an operation that has no encoded header marked nfs_ok.
+ */
+ if (xdr_stream_encode_u32(xdr, op->opnum) != XDR_UNIT) {
+ op->status = nfsd4_check_resp_size(resp, XDR_UNIT * 2);
goto release;
+ }
op_status_offset = xdr->buf->len;
- if (!xdr_reserve_space(xdr, XDR_UNIT))
+ if (!xdr_reserve_space(xdr, XDR_UNIT)) {
+ op->status = nfsd4_check_resp_size(resp, XDR_UNIT);
goto release;
+ }
if (op->opnum == OP_ILLEGAL)
goto status;