fs/nfsd/nfs4state.c | 18 +++++++++++++++++- fs/nfsd/nfs4xdr.c | 12 ++++++++++-- 2 files changed, 27 insertions(+), 3 deletions(-)
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@vger.kernel.org
Assisted-by: LLM
Co-developed-by: Kushal Khemka (OpenSec Intelligence) <kushalkhemka559@gmail.com>
Signed-off-by: Kushal Khemka (OpenSec Intelligence) <kushalkhemka559@gmail.com>
Co-developed-by: Kkartik Aggarwal (OpenSec Intelligence) <aggarwalkkartik@gmail.com>
Signed-off-by: Kkartik Aggarwal (OpenSec Intelligence) <aggarwalkkartik@gmail.com>
Signed-off-by: Mayank Jangid (OpenSec Intelligence) <mayank.jangid.moon@gmail.com>
---
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;
On 9/22/26 7:26 AM, Mayank Jangid (OpenSec Intelligence) wrote: > 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. Thanks for the report and the patch. Jérémy Jean reported the same issue in August, and a fix for it is already queued in the nfsd-testing branch: https://lore.kernel.org/linux-nfs/20260817-jean-v1-0-9e356596ab85@kernel.org/ Patch 1/2 there adds the same pre-flight check of the SEQUENCE reply size before nfsd4_sequence() narrows the buffer and accepts the slot, and patch 2/2 sets op->status when an operation header cannot be encoded. Your patch does the same two things, so I won't apply it on top of that series. If you can test the queued fix against your reproducer and confirm it addresses the overflow you found, a Tested-by: on that thread would be welcome. -- Chuck Lever (Come to NFS bake-a-thon! https://nfsv4bat.org)
Hi Chuck, Thanks for pointing me to Jérémy's series. I tested the queued fix against our reproducer, and it resolves the issue. I can no longer reproduce the reply buffer overflow with the two patches applied. Please feel free to add: Tested-by: Mayank Jangid (OpenSec Intelligence) mayank.jangid.moon@gmail.com <mailto:mayank.jangid.moon@gmail.com> Thanks, Mayank Jangid OpenSec Intelligence > On 22 Sep 2026, at 7:36 PM, Chuck Lever <cel@kernel.org> wrote: > > On 9/22/26 7:26 AM, Mayank Jangid (OpenSec Intelligence) wrote: >> 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. > > Thanks for the report and the patch. Jérémy Jean reported the same > issue in August, and a fix for it is already queued in the nfsd-testing > branch: > > https://lore.kernel.org/linux-nfs/20260817-jean-v1-0-9e356596ab85@kernel.org/ > > Patch 1/2 there adds the same pre-flight check of the SEQUENCE reply > size before nfsd4_sequence() narrows the buffer and accepts the slot, > and patch 2/2 sets op->status when an operation header cannot be > encoded. Your patch does the same two things, so I won't apply it on > top of that series. > > If you can test the queued fix against your reproducer and confirm it > addresses the overflow you found, a Tested-by: on that thread would be > welcome. > > -- > Chuck Lever (Come to NFS bake-a-thon! https://nfsv4bat.org)
© 2016 - 2026 Red Hat, Inc.