* FAILED: patch "[PATCH] smb: client: fix use-after-free infoleak via the readdir" failed to apply to 6.1-stable tree
@ 2026-11-04 13:36 gregkh
2026-11-04 19:36 ` [PATCH 6.1.y 1/3] smb: client: fix warning in CIFSFindFirst() Sasha Levin
0 siblings, 1 reply; 4+ messages in thread
From: gregkh @ 2026-11-04 13:36 UTC (permalink / raw)
To: diego, dhowells, pc; +Cc: stable
The patch below does not apply to the 6.1-stable tree.
If someone wants it applied there, or to any other stable or longterm
tree, then please email the backport, including the original git commit
id to <stable@vger.kernel.org>.
To reproduce the conflict and resubmit, you may use the following commands:
git fetch https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/ linux-6.1.y
git checkout FETCH_HEAD
git cherry-pick -x 59131dd72f62b198fc9ea1489ad9d3573154f684
# <resolve conflicts, build, test, etc.>
git commit -s
git send-email --to '<stable@vger.kernel.org>' --in-reply-to '2026110402-earlobe-scanner-01d6@gregkh' --subject-prefix 'PATCH 6.1.y' 'HEAD^..'
Possible dependencies:
thanks,
greg k-h
------------------ original commit in Linus's tree ------------------
From 59131dd72f62b198fc9ea1489ad9d3573154f684 Mon Sep 17 00:00:00 2001
From: Diego Oliva <diego@bynar.io>
Date: Tue, 29 Sep 2026 10:16:30 +0100
Subject: [PATCH] smb: client: fix use-after-free infoleak via the readdir
resume name
CIFSFindNext() copies the resume name that cifs_save_resume_key()
recorded from the last entry of the previous response into its request;
psrch_inf->presume_name points into the response buffer that entry came
from, and nothing ever clears it. find_cifs_entry() records a new
name only from a response with a usable last entry, and cifs_readdir()
only from an entry it walks to, so a response that carries no entries
and leaves last_entry NULL, because CIFSFindFirst() or CIFSFindNext()
rejected LastNameOffset, refreshes neither and presume_name keeps
pointing into the previous response. That buffer has already been
released: by CIFSFindNext() before it installs the new response, or by
find_cifs_entry() when it rewinds the search, which does not drop the
name either and can then fail in initiate_cifs_search() before a new
response replaces it. The next CIFSFindNext() then copies up to
PATH_MAX - 1 bytes of the resume name out of the released buffer into
its request and sends them to the server: a use-after-free read
that leaks kernel memory to a malicious or compromised server, which
can provoke it with a single FindNext response that carries no entries
and a LastNameOffset above CIFSMaxBufSize, which is all the existing
check rejects.
With KASAN enabled, a server that answers a listing that way gives:
CIFS: VFS: ignoring corrupt resume name
==================================================================
BUG: KASAN: slab-use-after-free in CIFSFindNext+0x8ca/0x14c0
Read of size 4080 at addr ffff88807c823f98 by task ls/83
CPU: 0 UID: 0 PID: 83 Comm: ls Tainted: G B 7.3.0-rc4-00457-gf14572c203d5 #69 PREEMPT(lazy)
Tainted: [B]=BAD_PAGE
Call Trace:
<TASK>
kasan_report+0xdf/0x1a0
kasan_check_range+0x10f/0x1e0
__asan_memcpy+0x23/0x60
CIFSFindNext+0x8ca/0x14c0
cifs_readdir+0xf51/0x29c0
iterate_dir+0x1c0/0x570
__x64_sys_getdents64+0x133/0x270
do_syscall_64+0x109/0x5d0
entry_SYSCALL_64_after_hwframe+0x77/0x7f
</TASK>
Allocated by task 83 on cpu 1 at 13.497119s:
cifs_buf_get+0x36/0x90
smb_init+0x4f/0x110
CIFSFindNext+0xf7/0x14c0
cifs_readdir+0xf51/0x29c0
Freed by task 83 on cpu 0 at 13.584018s:
cifs_buf_release+0x41/0x80
CIFSFindNext+0xfce/0x14c0
cifs_readdir+0xf51/0x29c0
The buggy address is located 16280 bytes inside of
freed 16588-byte region [ffff88807c820000, ffff88807c8240cc)
==================================================================
The same memcpy() first reads past the end of that object while it is
still live, a slab-out-of-bounds read that the following patches
bound; that is the taint the report above carries.
SMB1 is not negotiated by default; reaching this code requires an
explicit vers=1.0 mount.
Clear presume_name and resume_name_len whenever a new response is
installed and when the rewind path releases the search buffer, so that
the name never outlives the buffer it points into, and reset
resume_key with them, since it was taken from the same entry and would
otherwise be sent with an empty name. A response without a usable
last entry then continues the search with an empty resume name rather
than a stale one; CIFSFindNext() skips the copy of a zero-length name
so that it never runs on the NULL pointer.
The next patch in the series leaves last_entry NULL for a response
that carries no entries, and bounds LastNameOffset against the
received response rather than against CIFSMaxBufSize. Responses that
are given a last entry today then take the path fixed here, so that
patch must not be applied without this one.
Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Fixes: b77d753c413e ("[CIFS] Check that last search entry resume key is valid")
Cc: stable@vger.kernel.org
Assisted-by: Bynario AI
Signed-off-by: Diego Oliva <diego@bynar.io>
Reviewed-by: David Howells <dhowells@redhat.com>
Signed-off-by: Paulo Alcantara <pc@manguebit.org>
diff --git a/fs/smb/client/cifssmb.c b/fs/smb/client/cifssmb.c
index 6dddbd84b93b..95f339f48ad7 100644
--- a/fs/smb/client/cifssmb.c
+++ b/fs/smb/client/cifssmb.c
@@ -4543,6 +4543,9 @@ CIFSFindFirst(const unsigned int xid, struct cifs_tcon *tcon,
psrch_inf->entries_in_buffer = le16_to_cpu(parms->SearchCount);
psrch_inf->index_of_last_entry = 2 /* skip . and .. */ +
psrch_inf->entries_in_buffer;
+ psrch_inf->presume_name = NULL;
+ psrch_inf->resume_name_len = 0;
+ psrch_inf->resume_key = 0;
lnoff = le16_to_cpu(parms->LastNameOffset);
if (CIFSMaxBufSize < lnoff) {
cifs_dbg(VFS, "ignoring corrupt resume name\n");
@@ -4607,7 +4610,10 @@ int CIFSFindNext(const unsigned int xid, struct cifs_tcon *tcon,
name_len = psrch_inf->resume_name_len;
params += name_len;
if (name_len < PATH_MAX) {
- memcpy(pSMB->ResumeFileName, psrch_inf->presume_name, name_len);
+ if (name_len) {
+ memcpy(pSMB->ResumeFileName,
+ psrch_inf->presume_name, name_len);
+ }
byte_count += name_len;
/* 14 byte parm len above enough for 2 byte null terminator */
pSMB->ResumeFileName[name_len] = 0;
@@ -4662,6 +4668,9 @@ int CIFSFindNext(const unsigned int xid, struct cifs_tcon *tcon,
psrch_inf->endOfSearch = !!parms->EndofSearch;
psrch_inf->entries_in_buffer = le16_to_cpu(parms->SearchCount);
psrch_inf->index_of_last_entry += psrch_inf->entries_in_buffer;
+ psrch_inf->presume_name = NULL;
+ psrch_inf->resume_name_len = 0;
+ psrch_inf->resume_key = 0;
lnoff = le16_to_cpu(parms->LastNameOffset);
if (CIFSMaxBufSize < lnoff) {
cifs_dbg(VFS, "ignoring corrupt resume name\n");
diff --git a/fs/smb/client/readdir.c b/fs/smb/client/readdir.c
index 9530e5b01564..6ab4e687c3e4 100644
--- a/fs/smb/client/readdir.c
+++ b/fs/smb/client/readdir.c
@@ -752,6 +752,9 @@ find_cifs_entry(const unsigned int xid, struct cifs_tcon *tcon, loff_t pos,
cfile->srch_inf.ntwrk_buf_start = NULL;
cfile->srch_inf.srch_entries_start = NULL;
cfile->srch_inf.last_entry = NULL;
+ cfile->srch_inf.presume_name = NULL;
+ cfile->srch_inf.resume_name_len = 0;
+ cfile->srch_inf.resume_key = 0;
}
rc = initiate_cifs_search(xid, file, full_path);
if (rc) {
^ permalink raw reply related [flat|nested] 4+ messages in thread* [PATCH 6.1.y 1/3] smb: client: fix warning in CIFSFindFirst()
2026-11-04 13:36 FAILED: patch "[PATCH] smb: client: fix use-after-free infoleak via the readdir" failed to apply to 6.1-stable tree gregkh
@ 2026-11-04 19:36 ` Sasha Levin
2026-11-04 19:36 ` [PATCH 6.1.y 2/3] smb: client: fix warning in CIFSFindNext() Sasha Levin
2026-11-04 19:36 ` [PATCH 6.1.y 3/3] smb: client: fix use-after-free infoleak via the readdir resume name Sasha Levin
0 siblings, 2 replies; 4+ messages in thread
From: Sasha Levin @ 2026-11-04 19:36 UTC (permalink / raw)
To: stable; +Cc: Paulo Alcantara, Steve French, Sasha Levin
From: Paulo Alcantara <pc@manguebit.com>
[ Upstream commit 032137fe136a6073dcc699ee15fa3fd05fd77f21 ]
This fixes the following warning reported by kernel test robot
fs/smb/client/cifssmb.c:4089 CIFSFindFirst() warn: missing error
code? 'rc'
Link: https://lore.kernel.org/all/202306170124.CtQqzf0I-lkp@intel.com/
Signed-off-by: Paulo Alcantara (SUSE) <pc@manguebit.com>
Signed-off-by: Steve French <stfrench@microsoft.com>
Stable-dep-of: 59131dd72f62 ("smb: client: fix use-after-free infoleak via the readdir resume name")
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
fs/smb/client/cifssmb.c | 98 ++++++++++++++++++-----------------------
1 file changed, 44 insertions(+), 54 deletions(-)
diff --git a/fs/smb/client/cifssmb.c b/fs/smb/client/cifssmb.c
index 65418d15c3a02..735d01f5e992c 100644
--- a/fs/smb/client/cifssmb.c
+++ b/fs/smb/client/cifssmb.c
@@ -3904,11 +3904,12 @@ CIFSFindFirst(const unsigned int xid, struct cifs_tcon *tcon,
TRANSACTION2_FFIRST_REQ *pSMB = NULL;
TRANSACTION2_FFIRST_RSP *pSMBr = NULL;
T2_FFIRST_RSP_PARMS *parms;
- int rc = 0;
+ struct nls_table *nls_codepage;
+ unsigned int lnoff;
+ __u16 params, byte_count;
int bytes_returned = 0;
int name_len, remap;
- __u16 params, byte_count;
- struct nls_table *nls_codepage;
+ int rc = 0;
cifs_dbg(FYI, "In FindFirst for %s\n", searchName);
@@ -3999,63 +4000,52 @@ CIFSFindFirst(const unsigned int xid, struct cifs_tcon *tcon,
(struct smb_hdr *) pSMBr, &bytes_returned, 0);
cifs_stats_inc(&tcon->stats.cifs_stats.num_ffirst);
- if (rc) {/* BB add logic to retry regular search if Unix search
- rejected unexpectedly by server */
- /* BB Add code to handle unsupported level rc */
+ if (rc) {
+ /*
+ * BB: add logic to retry regular search if Unix search rejected
+ * unexpectedly by server.
+ */
+ /* BB: add code to handle unsupported level rc */
cifs_dbg(FYI, "Error in FindFirst = %d\n", rc);
-
cifs_buf_release(pSMB);
-
- /* BB eventually could optimize out free and realloc of buf */
- /* for this case */
+ /*
+ * BB: eventually could optimize out free and realloc of buf for
+ * this case.
+ */
if (rc == -EAGAIN)
goto findFirstRetry;
- } else { /* decode response */
- /* BB remember to free buffer if error BB */
- rc = validate_t2((struct smb_t2_rsp *)pSMBr);
- if (rc == 0) {
- unsigned int lnoff;
-
- if (pSMBr->hdr.Flags2 & SMBFLG2_UNICODE)
- psrch_inf->unicode = true;
- else
- psrch_inf->unicode = false;
-
- psrch_inf->ntwrk_buf_start = (char *)pSMBr;
- psrch_inf->smallBuf = false;
- psrch_inf->srch_entries_start =
- (char *) &pSMBr->hdr.Protocol +
- le16_to_cpu(pSMBr->t2.DataOffset);
- parms = (T2_FFIRST_RSP_PARMS *)((char *) &pSMBr->hdr.Protocol +
- le16_to_cpu(pSMBr->t2.ParameterOffset));
-
- if (parms->EndofSearch)
- psrch_inf->endOfSearch = true;
- else
- psrch_inf->endOfSearch = false;
-
- psrch_inf->entries_in_buffer =
- le16_to_cpu(parms->SearchCount);
- psrch_inf->index_of_last_entry = 2 /* skip . and .. */ +
- psrch_inf->entries_in_buffer;
- lnoff = le16_to_cpu(parms->LastNameOffset);
- if (CIFSMaxBufSize < lnoff) {
- cifs_dbg(VFS, "ignoring corrupt resume name\n");
- psrch_inf->last_entry = NULL;
- return rc;
- }
-
- psrch_inf->last_entry = psrch_inf->srch_entries_start +
- lnoff;
-
- if (pnetfid)
- *pnetfid = parms->SearchHandle;
- } else {
- cifs_buf_release(pSMB);
- }
+ return rc;
+ }
+ /* decode response */
+ rc = validate_t2((struct smb_t2_rsp *)pSMBr);
+ if (rc) {
+ cifs_buf_release(pSMB);
+ return rc;
}
- return rc;
+ psrch_inf->unicode = !!(pSMBr->hdr.Flags2 & SMBFLG2_UNICODE);
+ psrch_inf->ntwrk_buf_start = (char *)pSMBr;
+ psrch_inf->smallBuf = false;
+ psrch_inf->srch_entries_start = (char *)&pSMBr->hdr.Protocol +
+ le16_to_cpu(pSMBr->t2.DataOffset);
+
+ parms = (T2_FFIRST_RSP_PARMS *)((char *)&pSMBr->hdr.Protocol +
+ le16_to_cpu(pSMBr->t2.ParameterOffset));
+ psrch_inf->endOfSearch = !!parms->EndofSearch;
+
+ psrch_inf->entries_in_buffer = le16_to_cpu(parms->SearchCount);
+ psrch_inf->index_of_last_entry = 2 /* skip . and .. */ +
+ psrch_inf->entries_in_buffer;
+ lnoff = le16_to_cpu(parms->LastNameOffset);
+ if (CIFSMaxBufSize < lnoff) {
+ cifs_dbg(VFS, "ignoring corrupt resume name\n");
+ psrch_inf->last_entry = NULL;
+ } else {
+ psrch_inf->last_entry = psrch_inf->srch_entries_start + lnoff;
+ if (pnetfid)
+ *pnetfid = parms->SearchHandle;
+ }
+ return 0;
}
int CIFSFindNext(const unsigned int xid, struct cifs_tcon *tcon,
--
2.53.0
^ permalink raw reply related [flat|nested] 4+ messages in thread* [PATCH 6.1.y 2/3] smb: client: fix warning in CIFSFindNext()
2026-11-04 19:36 ` [PATCH 6.1.y 1/3] smb: client: fix warning in CIFSFindFirst() Sasha Levin
@ 2026-11-04 19:36 ` Sasha Levin
2026-11-04 19:36 ` [PATCH 6.1.y 3/3] smb: client: fix use-after-free infoleak via the readdir resume name Sasha Levin
1 sibling, 0 replies; 4+ messages in thread
From: Sasha Levin @ 2026-11-04 19:36 UTC (permalink / raw)
To: stable; +Cc: Paulo Alcantara, Steve French, Sasha Levin
From: Paulo Alcantara <pc@manguebit.com>
[ Upstream commit 215533f888dcf18f7cfbbf520bdd52e67ac6265a ]
This fixes the following warning reported by kernel test robot
fs/smb/client/cifssmb.c:4216 CIFSFindNext() warn: missing error
code? 'rc'
Link: https://lore.kernel.org/all/202306170124.CtQqzf0I-lkp@intel.com/
Signed-off-by: Paulo Alcantara (SUSE) <pc@manguebit.com>
Signed-off-by: Steve French <stfrench@microsoft.com>
Stable-dep-of: 59131dd72f62 ("smb: client: fix use-after-free infoleak via the readdir resume name")
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
fs/smb/client/cifssmb.c | 111 ++++++++++++++++++----------------------
1 file changed, 51 insertions(+), 60 deletions(-)
diff --git a/fs/smb/client/cifssmb.c b/fs/smb/client/cifssmb.c
index 735d01f5e992c..0c06b79f540b2 100644
--- a/fs/smb/client/cifssmb.c
+++ b/fs/smb/client/cifssmb.c
@@ -4055,11 +4055,12 @@ 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;
- char *response_data;
- int rc = 0;
- int bytes_returned;
unsigned int name_len;
+ unsigned int lnoff;
__u16 params, byte_count;
+ char *response_data;
+ int bytes_returned;
+ int rc = 0;
cifs_dbg(FYI, "In FindNext\n");
@@ -4104,8 +4105,8 @@ int CIFSFindNext(const unsigned int xid, struct cifs_tcon *tcon,
pSMB->ResumeFileName[name_len] = 0;
pSMB->ResumeFileName[name_len+1] = 0;
} else {
- rc = -EINVAL;
- goto FNext2_err_exit;
+ cifs_buf_release(pSMB);
+ return -EINVAL;
}
byte_count = params + 1 /* pad */ ;
pSMB->TotalParameterCount = cpu_to_le16(params);
@@ -4116,71 +4117,61 @@ int CIFSFindNext(const unsigned int xid, struct cifs_tcon *tcon,
rc = SendReceive(xid, tcon->ses, (struct smb_hdr *) pSMB,
(struct smb_hdr *) pSMBr, &bytes_returned, 0);
cifs_stats_inc(&tcon->stats.cifs_stats.num_fnext);
+
if (rc) {
+ cifs_buf_release(pSMB);
if (rc == -EBADF) {
psrch_inf->endOfSearch = true;
- cifs_buf_release(pSMB);
rc = 0; /* search probably was closed at end of search*/
- } else
+ } else {
cifs_dbg(FYI, "FindNext returned = %d\n", rc);
- } else { /* decode response */
- rc = validate_t2((struct smb_t2_rsp *)pSMBr);
-
- if (rc == 0) {
- unsigned int lnoff;
-
- /* BB fixme add lock for file (srch_info) struct here */
- if (pSMBr->hdr.Flags2 & SMBFLG2_UNICODE)
- psrch_inf->unicode = true;
- else
- psrch_inf->unicode = false;
- response_data = (char *) &pSMBr->hdr.Protocol +
- le16_to_cpu(pSMBr->t2.ParameterOffset);
- parms = (T2_FNEXT_RSP_PARMS *)response_data;
- response_data = (char *)&pSMBr->hdr.Protocol +
- le16_to_cpu(pSMBr->t2.DataOffset);
- if (psrch_inf->smallBuf)
- cifs_small_buf_release(
- psrch_inf->ntwrk_buf_start);
- else
- cifs_buf_release(psrch_inf->ntwrk_buf_start);
- psrch_inf->srch_entries_start = response_data;
- psrch_inf->ntwrk_buf_start = (char *)pSMB;
- psrch_inf->smallBuf = false;
- if (parms->EndofSearch)
- psrch_inf->endOfSearch = true;
- else
- psrch_inf->endOfSearch = false;
- psrch_inf->entries_in_buffer =
- le16_to_cpu(parms->SearchCount);
- psrch_inf->index_of_last_entry +=
- psrch_inf->entries_in_buffer;
- lnoff = le16_to_cpu(parms->LastNameOffset);
- if (CIFSMaxBufSize < lnoff) {
- cifs_dbg(VFS, "ignoring corrupt resume name\n");
- psrch_inf->last_entry = NULL;
- return rc;
- } else
- psrch_inf->last_entry =
- psrch_inf->srch_entries_start + lnoff;
-
-/* cifs_dbg(FYI, "fnxt2 entries in buf %d index_of_last %d\n",
- psrch_inf->entries_in_buffer, psrch_inf->index_of_last_entry); */
-
- /* BB fixme add unlock here */
}
+ return rc;
+ }
+ /* decode response */
+ rc = validate_t2((struct smb_t2_rsp *)pSMBr);
+ if (rc) {
+ cifs_buf_release(pSMB);
+ return rc;
}
+ /* 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);
+ parms = (T2_FNEXT_RSP_PARMS *)response_data;
+ response_data = (char *)&pSMBr->hdr.Protocol +
+ le16_to_cpu(pSMBr->t2.DataOffset);
- /* BB On error, should we leave previous search buf (and count and
- last entry fields) intact or free the previous one? */
+ if (psrch_inf->smallBuf)
+ cifs_small_buf_release(psrch_inf->ntwrk_buf_start);
+ else
+ cifs_buf_release(psrch_inf->ntwrk_buf_start);
- /* Note: On -EAGAIN error only caller can retry on handle based calls
- since file handle passed in no longer valid */
-FNext2_err_exit:
- if (rc != 0)
- cifs_buf_release(pSMB);
- return rc;
+ psrch_inf->srch_entries_start = response_data;
+ psrch_inf->ntwrk_buf_start = (char *)pSMB;
+ psrch_inf->smallBuf = false;
+ psrch_inf->endOfSearch = !!parms->EndofSearch;
+ psrch_inf->entries_in_buffer = le16_to_cpu(parms->SearchCount);
+ psrch_inf->index_of_last_entry += psrch_inf->entries_in_buffer;
+ lnoff = le16_to_cpu(parms->LastNameOffset);
+ if (CIFSMaxBufSize < lnoff) {
+ cifs_dbg(VFS, "ignoring corrupt resume name\n");
+ psrch_inf->last_entry = NULL;
+ } else {
+ psrch_inf->last_entry =
+ psrch_inf->srch_entries_start + lnoff;
+ }
+ /* BB fixme add unlock here */
+
+ /*
+ * BB: On error, should we leave previous search buf
+ * (and count and last entry fields) intact or free the previous one?
+ *
+ * Note: On -EAGAIN error only caller can retry on handle based calls
+ * since file handle passed in no longer valid.
+ */
+ return 0;
}
int
--
2.53.0
^ permalink raw reply related [flat|nested] 4+ messages in thread* [PATCH 6.1.y 3/3] smb: client: fix use-after-free infoleak via the readdir resume name
2026-11-04 19:36 ` [PATCH 6.1.y 1/3] smb: client: fix warning in CIFSFindFirst() Sasha Levin
2026-11-04 19:36 ` [PATCH 6.1.y 2/3] smb: client: fix warning in CIFSFindNext() Sasha Levin
@ 2026-11-04 19:36 ` Sasha Levin
1 sibling, 0 replies; 4+ messages in thread
From: Sasha Levin @ 2026-11-04 19:36 UTC (permalink / raw)
To: stable; +Cc: Diego Oliva, David Howells, Paulo Alcantara, Sasha Levin
From: Diego Oliva <diego@bynar.io>
[ Upstream commit 59131dd72f62b198fc9ea1489ad9d3573154f684 ]
CIFSFindNext() copies the resume name that cifs_save_resume_key()
recorded from the last entry of the previous response into its request;
psrch_inf->presume_name points into the response buffer that entry came
from, and nothing ever clears it. find_cifs_entry() records a new
name only from a response with a usable last entry, and cifs_readdir()
only from an entry it walks to, so a response that carries no entries
and leaves last_entry NULL, because CIFSFindFirst() or CIFSFindNext()
rejected LastNameOffset, refreshes neither and presume_name keeps
pointing into the previous response. That buffer has already been
released: by CIFSFindNext() before it installs the new response, or by
find_cifs_entry() when it rewinds the search, which does not drop the
name either and can then fail in initiate_cifs_search() before a new
response replaces it. The next CIFSFindNext() then copies up to
PATH_MAX - 1 bytes of the resume name out of the released buffer into
its request and sends them to the server: a use-after-free read
that leaks kernel memory to a malicious or compromised server, which
can provoke it with a single FindNext response that carries no entries
and a LastNameOffset above CIFSMaxBufSize, which is all the existing
check rejects.
With KASAN enabled, a server that answers a listing that way gives:
CIFS: VFS: ignoring corrupt resume name
==================================================================
BUG: KASAN: slab-use-after-free in CIFSFindNext+0x8ca/0x14c0
Read of size 4080 at addr ffff88807c823f98 by task ls/83
CPU: 0 UID: 0 PID: 83 Comm: ls Tainted: G B 7.3.0-rc4-00457-gf14572c203d5 #69 PREEMPT(lazy)
Tainted: [B]=BAD_PAGE
Call Trace:
<TASK>
kasan_report+0xdf/0x1a0
kasan_check_range+0x10f/0x1e0
__asan_memcpy+0x23/0x60
CIFSFindNext+0x8ca/0x14c0
cifs_readdir+0xf51/0x29c0
iterate_dir+0x1c0/0x570
__x64_sys_getdents64+0x133/0x270
do_syscall_64+0x109/0x5d0
entry_SYSCALL_64_after_hwframe+0x77/0x7f
</TASK>
Allocated by task 83 on cpu 1 at 13.497119s:
cifs_buf_get+0x36/0x90
smb_init+0x4f/0x110
CIFSFindNext+0xf7/0x14c0
cifs_readdir+0xf51/0x29c0
Freed by task 83 on cpu 0 at 13.584018s:
cifs_buf_release+0x41/0x80
CIFSFindNext+0xfce/0x14c0
cifs_readdir+0xf51/0x29c0
The buggy address is located 16280 bytes inside of
freed 16588-byte region [ffff88807c820000, ffff88807c8240cc)
==================================================================
The same memcpy() first reads past the end of that object while it is
still live, a slab-out-of-bounds read that the following patches
bound; that is the taint the report above carries.
SMB1 is not negotiated by default; reaching this code requires an
explicit vers=1.0 mount.
Clear presume_name and resume_name_len whenever a new response is
installed and when the rewind path releases the search buffer, so that
the name never outlives the buffer it points into, and reset
resume_key with them, since it was taken from the same entry and would
otherwise be sent with an empty name. A response without a usable
last entry then continues the search with an empty resume name rather
than a stale one; CIFSFindNext() skips the copy of a zero-length name
so that it never runs on the NULL pointer.
The next patch in the series leaves last_entry NULL for a response
that carries no entries, and bounds LastNameOffset against the
received response rather than against CIFSMaxBufSize. Responses that
are given a last entry today then take the path fixed here, so that
patch must not be applied without this one.
Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Fixes: b77d753c413e ("[CIFS] Check that last search entry resume key is valid")
Cc: stable@vger.kernel.org
Assisted-by: Bynario AI
Signed-off-by: Diego Oliva <diego@bynar.io>
Reviewed-by: David Howells <dhowells@redhat.com>
Signed-off-by: Paulo Alcantara <pc@manguebit.org>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
fs/smb/client/cifssmb.c | 11 ++++++++++-
fs/smb/client/readdir.c | 3 +++
2 files changed, 13 insertions(+), 1 deletion(-)
diff --git a/fs/smb/client/cifssmb.c b/fs/smb/client/cifssmb.c
index 0c06b79f540b2..44128457a87bd 100644
--- a/fs/smb/client/cifssmb.c
+++ b/fs/smb/client/cifssmb.c
@@ -4036,6 +4036,9 @@ CIFSFindFirst(const unsigned int xid, struct cifs_tcon *tcon,
psrch_inf->entries_in_buffer = le16_to_cpu(parms->SearchCount);
psrch_inf->index_of_last_entry = 2 /* skip . and .. */ +
psrch_inf->entries_in_buffer;
+ psrch_inf->presume_name = NULL;
+ psrch_inf->resume_name_len = 0;
+ psrch_inf->resume_key = 0;
lnoff = le16_to_cpu(parms->LastNameOffset);
if (CIFSMaxBufSize < lnoff) {
cifs_dbg(VFS, "ignoring corrupt resume name\n");
@@ -4099,7 +4102,10 @@ int CIFSFindNext(const unsigned int xid, struct cifs_tcon *tcon,
name_len = psrch_inf->resume_name_len;
params += name_len;
if (name_len < PATH_MAX) {
- memcpy(pSMB->ResumeFileName, psrch_inf->presume_name, name_len);
+ if (name_len) {
+ memcpy(pSMB->ResumeFileName,
+ psrch_inf->presume_name, name_len);
+ }
byte_count += name_len;
/* 14 byte parm len above enough for 2 byte null terminator */
pSMB->ResumeFileName[name_len] = 0;
@@ -4154,6 +4160,9 @@ int CIFSFindNext(const unsigned int xid, struct cifs_tcon *tcon,
psrch_inf->endOfSearch = !!parms->EndofSearch;
psrch_inf->entries_in_buffer = le16_to_cpu(parms->SearchCount);
psrch_inf->index_of_last_entry += psrch_inf->entries_in_buffer;
+ psrch_inf->presume_name = NULL;
+ psrch_inf->resume_name_len = 0;
+ psrch_inf->resume_key = 0;
lnoff = le16_to_cpu(parms->LastNameOffset);
if (CIFSMaxBufSize < lnoff) {
cifs_dbg(VFS, "ignoring corrupt resume name\n");
diff --git a/fs/smb/client/readdir.c b/fs/smb/client/readdir.c
index 1ba176a690cc3..58121bb8c3367 100644
--- a/fs/smb/client/readdir.c
+++ b/fs/smb/client/readdir.c
@@ -780,6 +780,9 @@ find_cifs_entry(const unsigned int xid, struct cifs_tcon *tcon, loff_t pos,
cfile->srch_inf.ntwrk_buf_start = NULL;
cfile->srch_inf.srch_entries_start = NULL;
cfile->srch_inf.last_entry = NULL;
+ cfile->srch_inf.presume_name = NULL;
+ cfile->srch_inf.resume_name_len = 0;
+ cfile->srch_inf.resume_key = 0;
}
rc = initiate_cifs_search(xid, file, full_path);
if (rc) {
--
2.53.0
^ permalink raw reply related [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-11-04 19:36 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-04 13:36 FAILED: patch "[PATCH] smb: client: fix use-after-free infoleak via the readdir" failed to apply to 6.1-stable tree gregkh
2026-11-04 19:36 ` [PATCH 6.1.y 1/3] smb: client: fix warning in CIFSFindFirst() Sasha Levin
2026-11-04 19:36 ` [PATCH 6.1.y 2/3] smb: client: fix warning in CIFSFindNext() Sasha Levin
2026-11-04 19:36 ` [PATCH 6.1.y 3/3] smb: client: fix use-after-free infoleak via the readdir resume name Sasha Levin
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.