[PATCH 6/7] smb: client: reject short or out-of-range FIND response parameters

From: Diego Oliva

Date: Tue Sep 29 2026 - 05:22:40 EST


CIFSFindFirst() and CIFSFindNext() read the SearchHandle, SearchCount,
EndofSearch and LastNameOffset of a TRANS2_FIND_FIRST2 or
TRANS2_FIND_NEXT2 response at ParameterOffset. validate_t2() bounds
the offset at 1024 and ParameterCount below 512, and nothing compares
them with the number of bytes SendReceive() copied into the buffer or
with the size of the response parameters. smb_init() places the
request and the response in the same cifs_request allocation and
cifs_buf_get() clears only the start of it, so a short response with a
ParameterOffset past its end has the search handle, the entry count,
the end-of-search flag and the last-entry offset read out of the
request that was just sent, or out of whatever the allocation held
before, and a ParameterCount smaller than the parameters has them read
past the end of the parameter block. The reads stay inside the
allocation, but what they return is not part of the response, and
CIFSFindFirst() then hands back a search handle the server never sent.
That handle is sent back to the server in the next FIND_NEXT2 or
FIND_CLOSE2, so two bytes of the buffer leak at an offset the server
picks.

SMB1 is not negotiated by default; reaching this code requires an
explicit vers=1.0 mount.

Reject a response whose parameter area is smaller than the response
parameters or extends past the received length, returning smb_EIO2()
with a new smb_eio_traces value for each of the two responses, like
the other response checks this file gained since v6.19. Backports to
trees before v6.19 need smb_EIO2() replaced with -EINVAL and the
trace.h hunk dropped.

A conforming server is not affected. The parameter block of a
transaction response is part of the SMB, so ParameterOffset +
ParameterCount cannot exceed the smbCalcSize() bytes that
SendReceive() copies, and the response parameters are 10 bytes for
FIND_FIRST2 and 8 bytes for FIND_NEXT2, which is what
T2_FFIRST_RSP_PARMS and T2_FNEXT_RSP_PARMS hold.

This patch depends on "smb: client: reject FIND data areas that run
past the received response", whose declarations it extends.

Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Cc: <stable@xxxxxxxxxxxxxxx> # 6.19.x
Assisted-by: Bynario AI
Signed-off-by: Diego Oliva <diego@xxxxxxxx>
---
fs/smb/client/cifssmb.c | 32 ++++++++++++++++++++++++++------
fs/smb/client/trace.h | 2 ++
2 files changed, 28 insertions(+), 6 deletions(-)

diff --git a/fs/smb/client/cifssmb.c b/fs/smb/client/cifssmb.c
index 107ca76e53d1..962877450eca 100644
--- a/fs/smb/client/cifssmb.c
+++ b/fs/smb/client/cifssmb.c
@@ -4412,7 +4412,7 @@ CIFSFindFirst(const unsigned int xid, struct cifs_tcon *tcon,
TRANSACTION2_FFIRST_RSP *pSMBr = NULL;
T2_FFIRST_RSP_PARMS *parms;
struct nls_table *nls_codepage;
- unsigned int data_off, data_count;
+ unsigned int parm_off, parm_count, data_off, data_count;
unsigned int in_len, lnoff;
__u16 params, byte_count;
int bytes_returned = 0;
@@ -4531,6 +4531,17 @@ CIFSFindFirst(const unsigned int xid, struct cifs_tcon *tcon,
return rc;
}

+ parm_off = le16_to_cpu(pSMBr->t2.ParameterOffset);
+ parm_count = le16_to_cpu(pSMBr->t2.ParameterCount);
+ if (parm_count < sizeof(*parms) ||
+ parm_off + parm_count > (unsigned int)bytes_returned) {
+ cifs_dbg(FYI, "%s: bad parameter area offset %u count %u for response of %d\n",
+ __func__, parm_off, parm_count, bytes_returned);
+ rc = smb_EIO2(smb_eio_trace_ffirst_param_area, parm_off, parm_count);
+ cifs_buf_release(pSMB);
+ return rc;
+ }
+
data_off = le16_to_cpu(pSMBr->t2.DataOffset);
data_count = le16_to_cpu(pSMBr->t2.DataCount);
if (data_off + data_count > (unsigned int)bytes_returned) {
@@ -4545,8 +4556,7 @@ CIFSFindFirst(const unsigned int xid, struct cifs_tcon *tcon,
psrch_inf->smallBuf = false;
psrch_inf->srch_entries_start = (char *)&pSMBr->hdr.Protocol + data_off;

- parms = (T2_FFIRST_RSP_PARMS *)((char *)&pSMBr->hdr.Protocol +
- le16_to_cpu(pSMBr->t2.ParameterOffset));
+ parms = (T2_FFIRST_RSP_PARMS *)((char *)&pSMBr->hdr.Protocol + parm_off);
psrch_inf->endOfSearch = !!parms->EndofSearch;

psrch_inf->entries_in_buffer = le16_to_cpu(parms->SearchCount);
@@ -4577,7 +4587,7 @@ int CIFSFindNext(const unsigned int xid, struct cifs_tcon *tcon,
TRANSACTION2_FNEXT_REQ *pSMB = NULL;
TRANSACTION2_FNEXT_RSP *pSMBr = NULL;
T2_FNEXT_RSP_PARMS *parms;
- unsigned int data_off, data_count;
+ unsigned int parm_off, parm_count, data_off, data_count;
unsigned int name_len, in_len;
unsigned int lnoff;
__u16 params, byte_count;
@@ -4661,6 +4671,17 @@ int CIFSFindNext(const unsigned int xid, struct cifs_tcon *tcon,
return rc;
}

+ parm_off = le16_to_cpu(pSMBr->t2.ParameterOffset);
+ parm_count = le16_to_cpu(pSMBr->t2.ParameterCount);
+ if (parm_count < sizeof(*parms) ||
+ parm_off + parm_count > (unsigned int)bytes_returned) {
+ cifs_dbg(FYI, "%s: bad parameter area offset %u count %u for response of %d\n",
+ __func__, parm_off, parm_count, bytes_returned);
+ rc = smb_EIO2(smb_eio_trace_fnext_param_area, parm_off, parm_count);
+ cifs_buf_release(pSMB);
+ return rc;
+ }
+
data_off = le16_to_cpu(pSMBr->t2.DataOffset);
data_count = le16_to_cpu(pSMBr->t2.DataCount);
if (data_off + data_count > (unsigned int)bytes_returned) {
@@ -4672,8 +4693,7 @@ int CIFSFindNext(const unsigned int xid, struct cifs_tcon *tcon,

/* BB fixme add lock for file (srch_info) struct here */
psrch_inf->unicode = !!(pSMBr->hdr.Flags2 & SMBFLG2_UNICODE);
- response_data = (char *)&pSMBr->hdr.Protocol +
- le16_to_cpu(pSMBr->t2.ParameterOffset);
+ response_data = (char *)&pSMBr->hdr.Protocol + parm_off;
parms = (T2_FNEXT_RSP_PARMS *)response_data;
response_data = (char *)&pSMBr->hdr.Protocol + data_off;

diff --git a/fs/smb/client/trace.h b/fs/smb/client/trace.h
index bb8d0197cb54..2708bedb49ab 100644
--- a/fs/smb/client/trace.h
+++ b/fs/smb/client/trace.h
@@ -30,6 +30,8 @@
EM(smb_eio_trace_ea_next_offset, "ea_next_offset") \
EM(smb_eio_trace_ea_overrun, "ea_overrun") \
EM(smb_eio_trace_extract_will_pin, "extract_will_pin") \
+ EM(smb_eio_trace_ffirst_param_area, "ffirst_param_area") \
+ EM(smb_eio_trace_fnext_param_area, "fnext_param_area") \
EM(smb_eio_trace_forced_shutdown, "forced_shutdown") \
EM(smb_eio_trace_getacl_bcc_too_small, "getacl_bcc_too_small") \
EM(smb_eio_trace_getcifsacl_param_count, "getcifsacl_param_count") \
--
2.39.5