From a2f9fb451c68adf2b3f7cae1bb66fb5801769ac6 Mon Sep 17 00:00:00 2001 From: ChenXiaoSong Date: Fri, 7 Aug 2026 08:58:06 +0000 Subject: [PATCH 01/33] smb/client: return EOPNOTSUPP for unsupported O_TMPFILE Some SMB servers return STATUS_OBJECT_NAME_NOT_FOUND or STATUS_DELETE_PENDING when creating an O_TMPFILE. The SMB client maps them to ENOENT. The ENOENT error is supposed to be returned when the path (a target directory in this case) does not exist, while a lack of support of O_TMPFILE by the target file system should be indicated by EOPNOTSUPP. Reported-by: Mikhail Stefantsev Fixes: 3e7d63037a2b ("smb: client: add support for O_TMPFILE") Closes: https://lore.kernel.org/linux-cifs/75fb5359-b268-492d-8d4a-504d1af60f2a@app.fastmail.com/ Signed-off-by: ChenXiaoSong Signed-off-by: Namjae Jeon Signed-off-by: Paulo Alcantara --- fs/smb/client/dir.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/smb/client/dir.c b/fs/smb/client/dir.c index 88a4a1787ff0..b0ddcaa2d815 100644 --- a/fs/smb/client/dir.c +++ b/fs/smb/client/dir.c @@ -1138,6 +1138,8 @@ int cifs_tmpfile(struct mnt_idmap *idmap, struct inode *dir, } while (unlikely(rc == -EEXIST) && ++retries < max_retries); if (rc) { + if (rc == -ENOENT) + rc = -EOPNOTSUPP; cifs_del_pending_open(&open); goto out; } From deb6468f4164640e4dc875f008aa449cf55987a5 Mon Sep 17 00:00:00 2001 From: Christopher Lusk Date: Wed, 29 Jul 2026 18:00:17 -0400 Subject: [PATCH 02/33] smb: client: fix request buffer leak in smb2_new_read_req() smb2_new_read_req() allocates the request buffer with smb2_plain_req_init() but only publishes it to the caller with *buf = req at the very end of the function. Two error returns sit in between: rc = smb2_plain_req_init(SMB2_READ, io_parms->tcon, server, (void **) &req, total_len); if (rc) return rc; if (server == NULL) return -ECONNABORTED; [...] rdata->mr = smbd_register_mr(server->smbd_conn, &rdata->subreq.io_iter, true, need_invalidate); if (!rdata->mr) return -EAGAIN; On either of them the buffer is neither released nor handed back, so it is leaked. The caller cannot clean up after it: smb2_async_readv() does 'goto out' on a non-zero return, which skips the cifs_small_buf_release(buf) at async_readv_out, and buf has not been assigned at that point in any case. The write path has never had this problem. smb2_async_writev() registers the memory region inline and jumps to its release label instead of returning: wdata->mr = smbd_register_mr(...); if (!wdata->mr) { rc = -EAGAIN; goto async_writev_out; } Commit b7972092199f ("cifs: smbd: Retry on memory registration failure") changed both sides from -ENOBUFS to -EAGAIN in a single patch, which puts the two shapes next to each other. Only the -EAGAIN return is reachable in practice, because smb2_plain_req_init() calls smb2_reconnect() first and that already fails with -EIO when server is NULL, before anything is allocated. Both returns are given the same treatment here rather than leaving one of them correct only by accident. Because -EAGAIN is a replayable error, the failure also reaches the retry block at the end of smb2_async_readv(), which marks the subrequest NETFS_SREQ_NEED_RETRY, so a failing registration can be retried rather than ending the I/O, and every attempt that reaches it leaks another buffer. smb2_should_replay() short-circuits on tcon->retry, so on a hard mount the attempt count is not bounded by the retrans setting. Only the asynchronous read path is affected. The synchronous SMB2_read() caller passes rdata == NULL and the memory registration block is guarded on rdata. The memory registration failure path was pointed out by the Sashiko AI reviewer while it was reviewing an unrelated patch to smb2_async_readv(). Fixes: bd3dcc6a22a9 ("CIFS: SMBD: Upper layer performs SMB read via RDMA write through memory registration") Link: https://sashiko.dev/#/patchset/20260729192002.876156-1-clusk%40northecho.dev Link: https://lore.kernel.org/all/20260729192002.876156-1-clusk@northecho.dev/ Assisted-by: Claude:claude-opus-5 Signed-off-by: Christopher Lusk Signed-off-by: Namjae Jeon Signed-off-by: Paulo Alcantara --- fs/smb/client/smb2pdu.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/fs/smb/client/smb2pdu.c b/fs/smb/client/smb2pdu.c index 4ce165e40657..b885060be200 100644 --- a/fs/smb/client/smb2pdu.c +++ b/fs/smb/client/smb2pdu.c @@ -4564,8 +4564,10 @@ smb2_new_read_req(void **buf, unsigned int *total_len, if (rc) return rc; - if (server == NULL) - return -ECONNABORTED; + if (!server) { + rc = -ECONNABORTED; + goto free_req; + } shdr = &req->hdr; shdr->Id.SyncId.ProcessId = cpu_to_le32(io_parms->pid); @@ -4596,8 +4598,10 @@ smb2_new_read_req(void **buf, unsigned int *total_len, rdata->mr = smbd_register_mr(server->smbd_conn, &rdata->subreq.io_iter, true, need_invalidate); - if (!rdata->mr) - return -EAGAIN; + if (!rdata->mr) { + rc = -EAGAIN; + goto free_req; + } req->Channel = SMB2_CHANNEL_RDMA_V1_INVALIDATE; if (need_invalidate) @@ -4638,6 +4642,10 @@ smb2_new_read_req(void **buf, unsigned int *total_len, *buf = req; return rc; + +free_req: + cifs_small_buf_release(req); + return rc; } static void From 45f84cf25a0879a4d4e3ec3079982537be57c564 Mon Sep 17 00:00:00 2001 From: Christopher Lusk Date: Wed, 29 Jul 2026 15:20:02 -0400 Subject: [PATCH 03/33] smb: client: set replay flag on the read send-error retry path smb2_async_readv() and smb2_async_writev() end with the same send-error block: if the error is replayable and smb2_should_replay() agrees, tell netfs to retry the subrequest. The write path also sets wdata->replay. The read path does not set rdata->replay. smb2_should_replay() is not a pure predicate. It consumes the retry budget and computes the exponential back-off, doubling cur_sleep up to CIFS_MAX_SLEEP. That back-off is only applied where the replay flag is tested at the top of the reissued request: if (rdata->replay) { /* Back-off before retry */ if (rdata->cur_sleep) msleep(rdata->cur_sleep); smb2_set_replay(server, &rqst); } So on the read path the back-off is recomputed on every send-error retry and then discarded, and SMB2_FLAGS_REPLAY_OPERATION is not set on the reissued request. netfs does not pace the retry either. netfs_reissue_read() calls ->issue_read() directly, and fs/netfs/read_retry.c contains no delay of its own, so read send-error retries reissue immediately while the equivalent write retries back off. The read response callback already sets rdata->replay under the same conditions, so the read path does use the replay mechanism. Only this send-error path omits it. Where the back-off belongs was settled while the commit below was under review. David Howells asked whether netfslib should be doing the back-off [1], and objected to sleeping inside the response callback because that runs in the cifsd thread and would stall the socket [2]. The sleep was therefore taken out of smb2_should_replay() and moved to just before the replay in smb2_async_readv() and smb2_async_writev() [3]. Setting the flag here preserves that arrangement: the sleep still happens at the top of the reissued request, not in a callback. Set rdata->replay here, matching smb2_async_writev(). Fixes: 2c1238a7477a ("cifs: make retry logic in read/write path consistent with other paths") Link: https://lore.kernel.org/all/1652858.1769038134@warthog.procyon.org.uk/ [1] Link: https://lore.kernel.org/all/1653031.1769038583@warthog.procyon.org.uk/ [2] Link: https://lore.kernel.org/all/CANT5p=pXP3+CywpmK-on2uTvxO3S=31_B85_UDR7RoK1dQVtMA@mail.gmail.com/ [3] Assisted-by: Codex:gpt-5.5 Assisted-by: Claude:claude-opus-5 Signed-off-by: Christopher Lusk Signed-off-by: Namjae Jeon Signed-off-by: Paulo Alcantara --- fs/smb/client/smb2pdu.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/smb/client/smb2pdu.c b/fs/smb/client/smb2pdu.c index b885060be200..d207591cc889 100644 --- a/fs/smb/client/smb2pdu.c +++ b/fs/smb/client/smb2pdu.c @@ -4893,6 +4893,7 @@ smb2_async_readv(struct cifs_io_subrequest *rdata) smb2_should_replay(tcon, &rdata->retries, &rdata->cur_sleep)) { + rdata->replay = true; trace_netfs_sreq(&rdata->subreq, netfs_sreq_trace_io_retry_needed); __set_bit(NETFS_SREQ_NEED_RETRY, &rdata->subreq.flags); } From b8e5dc4f95e5484159b343903f302eb6d783f2e6 Mon Sep 17 00:00:00 2001 From: Jiangshan Yi Date: Thu, 16 Jul 2026 10:22:14 +0800 Subject: [PATCH 04/33] smb: client: clear setuid/setgid bit on write with cifsacl/modefromsid/posix extensions When a file has the setuid or setgid bit set and is written to, the VFS strips those bits and issues a setattr with ATTR_KILL_SUID/ATTR_KILL_SGID together with an ATTR_MODE carrying the already-cleared mode. Both cifs_setattr_unix() and cifs_setattr_nounix() unconditionally dropped ATTR_MODE in that case: /* skip mode change if it's just for clearing setuid/setgid */ if (attrs->ia_valid & (ATTR_KILL_SUID|ATTR_KILL_SGID)) attrs->ia_valid &= ~ATTR_MODE; This is fine for the default mount, where the mode is only emulated via the DOS read-only attribute and cannot represent the setuid/setgid bits anyway. However, with the "cifsacl" or "modefromsid" mount options the mode is stored on the server through an ACL (id_mode_to_cifs_acl()), with the SMB3.1.1 POSIX extensions the mode is sent to the server directly, and with the SMB1 Unix extensions (cifs_setattr_unix) the mode is sent via CIFSSMBUnixSetPathInfo(). In all those cases dropping ATTR_MODE means the cleared mode is never pushed to the server, so the setuid/setgid bit survives the write. This is a security issue: on local filesystems the setuid bit is stripped when a file is written, but over these cifs.ko mounts the bit persists on the server, potentially allowing an unexpected privilege escalation on subsequent execution. Fix this in two places: 1. cifs_setattr_nounix(): only take the "skip mode change" shortcut when the mode is emulated via the DOS read-only attribute (i.e. neither cifsacl/modefromsid nor the SMB3.1.1 POSIX extensions are in effect), so that the cleared mode is propagated to the server in the ACL / POSIX cases. 2. cifs_setattr_unix(): this function is only called when Unix extensions are in effect, so the mode is always stored on the server. Remove the shortcut entirely so that the cleared mode is always pushed. Fixes: d32c4f2626ac ("CIFS: ignore mode change if it's just for clearing setuid/setgid bits") Cc: stable@vger.kernel.org Signed-off-by: Jiangshan Yi Signed-off-by: Namjae Jeon Signed-off-by: Paulo Alcantara --- fs/smb/client/inode.c | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/fs/smb/client/inode.c b/fs/smb/client/inode.c index 0afff761aab9..5f58fb46363d 100644 --- a/fs/smb/client/inode.c +++ b/fs/smb/client/inode.c @@ -3214,9 +3214,13 @@ cifs_setattr_unix(struct dentry *direntry, struct iattr *attrs) attrs->ia_valid &= ~(ATTR_CTIME | ATTR_MTIME); } - /* skip mode change if it's just for clearing setuid/setgid */ - if (attrs->ia_valid & (ATTR_KILL_SUID|ATTR_KILL_SGID)) - attrs->ia_valid &= ~ATTR_MODE; + /* + * This function is only called when Unix extensions are in effect, + * so the mode is always sent to and stored on the server. Do not + * skip the mode change when clearing setuid/setgid bits: dropping + * ATTR_MODE here would leave those bits set on the server after a + * write, which is a security issue. + */ args = kmalloc_obj(*args); if (args == NULL) { @@ -3425,8 +3429,23 @@ cifs_setattr_nounix(struct dentry *direntry, struct iattr *attrs) attrs->ia_valid &= ~(ATTR_UID | ATTR_GID); } - /* skip mode change if it's just for clearing setuid/setgid */ - if (attrs->ia_valid & (ATTR_KILL_SUID|ATTR_KILL_SGID)) + /* + * Skip the mode change if it is only being done to clear the + * setuid/setgid bits *and* the mode is emulated via the DOS + * read-only attribute (the default, non-ACL case), which cannot + * represent the setuid/setgid bits anyway. + * + * When the mode is instead stored on the server - i.e. with the + * cifsacl or modefromsid mount options (via an ACL) or with the + * SMB3.1.1 POSIX extensions - the cleared mode must be pushed to + * the server. Dropping ATTR_MODE here would leave the setuid/ + * setgid bit set on the server after a write, which is a security + * issue (the bits are not stripped as they are on local + * filesystems). + */ + if ((attrs->ia_valid & (ATTR_KILL_SUID|ATTR_KILL_SGID)) && + !((sbflags & (CIFS_MOUNT_CIFS_ACL | CIFS_MOUNT_MODE_FROM_SID)) || + cifs_sb_master_tcon(cifs_sb)->posix_extensions)) attrs->ia_valid &= ~ATTR_MODE; if (attrs->ia_valid & ATTR_MODE) { From bf86c08123c6ab8c61cc0be1dad7540db93738ff Mon Sep 17 00:00:00 2001 From: Fredric Cover Date: Fri, 24 Jul 2026 15:01:45 -0700 Subject: [PATCH 05/33] smb: client: harden DFS cache against invalid target hints Currently, get_tgt_name() returns ERR_PTR(-ENOENT) when ce->tgthint is NULL, and dfs_cache_noreq_update_tgthint() assumes ce->tgthint is always valid. In preparation for clearing ce->tgthint in free_tgts(), harden callers of get_tgt_name() against ERR_PTR results and harden dfs_cache_noreq_update_tgthint() against NULL pointer dereferences. Cc: stable@vger.kernel.org Signed-off-by: Fredric Cover Reviewed-by: ChenXiaoSong Signed-off-by: Namjae Jeon Signed-off-by: Paulo Alcantara --- fs/smb/client/dfs_cache.c | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/fs/smb/client/dfs_cache.c b/fs/smb/client/dfs_cache.c index 8cd93cd2f00f..c9ace4326a35 100644 --- a/fs/smb/client/dfs_cache.c +++ b/fs/smb/client/dfs_cache.c @@ -869,13 +869,22 @@ int dfs_cache_find(const unsigned int xid, struct cifs_ses *ses, const struct nl goto out_free_path; } - if (ref) - rc = setup_referral(path, ce, ref, get_tgt_name(ce)); - else + if (ref) { + char *target = get_tgt_name(ce); + + if (IS_ERR(target)) { + rc = PTR_ERR(target); + goto out_unlock; + } + rc = setup_referral(path, ce, ref, target); + } else { rc = 0; + } + if (!rc && tgt_list) rc = get_targets(ce, tgt_list); +out_unlock: up_read(&htable_rw_lock); out_free_path: @@ -915,10 +924,17 @@ int dfs_cache_noreq_find(const char *path, struct dfs_info3_param *ref, goto out_unlock; } - if (ref) - rc = setup_referral(path, ce, ref, get_tgt_name(ce)); - else + if (ref) { + char *target = get_tgt_name(ce); + + if (IS_ERR(target)) { + rc = PTR_ERR(target); + goto out_unlock; + } + rc = setup_referral(path, ce, ref, target); + } else { rc = 0; + } if (!rc && tgt_list) rc = get_targets(ce, tgt_list); @@ -959,7 +975,8 @@ void dfs_cache_noreq_update_tgthint(const char *path, const struct dfs_cache_tgt t = READ_ONCE(ce->tgthint); - if (unlikely(!strcasecmp(it->it_name, t->name))) + /* Check 't' in case ce->tgthint was cleared by free_tgts() */ + if (t && unlikely(!strcasecmp(it->it_name, t->name))) goto out_unlock; list_for_each_entry(t, &ce->tlist, list) { From b1b741cf8e7ce1b91d937e23decd3d3358748700 Mon Sep 17 00:00:00 2001 From: Fredric Cover Date: Fri, 24 Jul 2026 15:01:46 -0700 Subject: [PATCH 06/33] smb: client: clear ce->tgthint in free_tgts() When free_tgts() frees all structures in ce->tlist, ce->tgthint is left pointing to one of the freed cache_dfs_tgt structures. If ce->tgthint is not reset before it is used later, it results in a use-after-free. Set ce->tgthint to NULL in free_tgts() after the elements are freed to reflect that no elements remain. Fixes: 54be1f6c1c37 ("cifs: Add DFS cache routines") Cc: stable@vger.kernel.org # depends on: smb: client: harden DFS cache against invalid target hints Signed-off-by: Fredric Cover Reviewed-by: ChenXiaoSong Signed-off-by: Namjae Jeon Signed-off-by: Paulo Alcantara --- fs/smb/client/dfs_cache.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/smb/client/dfs_cache.c b/fs/smb/client/dfs_cache.c index c9ace4326a35..86dba25b7a5a 100644 --- a/fs/smb/client/dfs_cache.c +++ b/fs/smb/client/dfs_cache.c @@ -122,6 +122,8 @@ static inline void free_tgts(struct cache_entry *ce) kfree(t->name); kfree(t); } + + WRITE_ONCE(ce->tgthint, NULL); } static inline void flush_cache_ent(struct cache_entry *ce) From 364b183230586a62660a7280c1eb20138338eeb5 Mon Sep 17 00:00:00 2001 From: Frank Sorenson Date: Fri, 31 Jul 2026 12:12:28 -0500 Subject: [PATCH 07/33] cifs: use cifs_invalidate_cache() in cifs_do_truncate() for O_TRUNC cifs_do_truncate() is invoked from cifs_open() without i_rwsem, so it cannot use cifs_resize_file_locked() to perform a proper fscache cookie resize. Instead, add cifs_invalidate_cache() after cifs_setsize(). cifs_invalidate_cache() calls fscache_invalidate(), which works without holding i_rwsem: it unconditionally increments inval_counter and sets FSCACHE_COOKIE_NO_DATA_TO_READ, ensuring that stale cached data is not served once the cookie is later activated by fscache_use_cookie(). Truncation to zero leaves no valid cached data, making invalidation the correct semantic here. Fixes: fa724e235cfd ("cifs: add fscache_resize_cookie() to cifs_setsize()") Cc: stable@vger.kernel.org Cc: David Howells Cc: Paulo Alcantara Signed-off-by: Frank Sorenson Signed-off-by: Namjae Jeon Signed-off-by: Paulo Alcantara --- fs/smb/client/file.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/smb/client/file.c b/fs/smb/client/file.c index ac89c1ba56b1..389083f9ce00 100644 --- a/fs/smb/client/file.c +++ b/fs/smb/client/file.c @@ -1016,6 +1016,7 @@ static int cifs_do_truncate(const unsigned int xid, struct dentry *dentry) if (!rc) { netfs_resize_file(&cinode->netfs, 0, true); cifs_setsize(inode, 0); + cifs_invalidate_cache(inode, 0); } } if (cfile) From 32a7af68df7361fe7cf153cf36124d04b94aec00 Mon Sep 17 00:00:00 2001 From: Frank Sorenson Date: Fri, 31 Jul 2026 12:12:29 -0500 Subject: [PATCH 08/33] cifs: add cifs_resize_file_locked() to guard fscache_resize_cookie() under i_rwsem cifs_setsize() calls fscache_resize_cookie() without holding i_rwsem. When the fscache cookie is active (FSCACHE_COOKIE_IS_CACHING is set), fscache_resize_cookie() performs a real resize that requires i_rwsem held exclusively. If another file descriptor has the same inode open, fscache_use_cookie() was already called from that cifs_open(), making the cookie active. In that case, calling cifs_setsize() from cifs_do_truncate() (invoked from cifs_open() without i_rwsem) races against concurrent fscache I/O. Strip fscache_resize_cookie() from cifs_setsize(), making it a pure size/page-cache helper. Add cifs_resize_file_locked() for callers that already hold i_rwsem: it calls netfs_resize_file() and cifs_setsize(), then temporarily activates the cookie with fscache_use_cookie() to perform the resize under the lock, then deactivates it with cifs_fscache_unuse_inode_cookie(). Using fscache_use_cookie() before the resize ensures correctness whether or not another fd already holds the cookie active. Switch cifs_file_set_size(), smb2_duplicate_extents(), and both size- extension branches of smb3_simple_falloc() to the new wrapper; those paths already hold i_rwsem via VFS setattr, lock_two_nondirectories(), or cifs_fallocate() respectively. cifs_do_truncate() continues to call cifs_setsize() followed by cifs_invalidate_cache(), since it runs without i_rwsem. Fixes: fa724e235cfd ("cifs: add fscache_resize_cookie() to cifs_setsize()") Cc: stable@vger.kernel.org Cc: David Howells Cc: Paulo Alcantara Signed-off-by: Frank Sorenson Signed-off-by: Namjae Jeon Signed-off-by: Paulo Alcantara --- fs/smb/client/cifsfs.h | 1 + fs/smb/client/inode.c | 24 +++++++++++++++++++----- fs/smb/client/smb2ops.c | 9 +++------ 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/fs/smb/client/cifsfs.h b/fs/smb/client/cifsfs.h index 854e672a4e37..651670c19c2b 100644 --- a/fs/smb/client/cifsfs.h +++ b/fs/smb/client/cifsfs.h @@ -147,6 +147,7 @@ ssize_t cifs_file_copychunk_range(unsigned int xid, struct file *src_file, long cifs_ioctl(struct file *filep, unsigned int command, unsigned long arg); void cifs_setsize(struct inode *inode, loff_t offset); +void cifs_resize_file_locked(struct inode *inode, loff_t offset); struct fs_context; struct smb3_fs_context; diff --git a/fs/smb/client/inode.c b/fs/smb/client/inode.c index 5f58fb46363d..098d5496e779 100644 --- a/fs/smb/client/inode.c +++ b/fs/smb/client/inode.c @@ -3059,7 +3059,23 @@ void cifs_setsize(struct inode *inode, loff_t offset) inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode)); truncate_pagecache(inode, offset); netfs_wait_for_outstanding_io(inode); - fscache_resize_cookie(cifs_inode_cookie(inode), offset); +} + +void cifs_resize_file_locked(struct inode *inode, loff_t offset) +{ + struct fscache_cookie *cookie = cifs_inode_cookie(inode); + + lockdep_assert_held_write(&inode->i_rwsem); + + netfs_resize_file(netfs_inode(inode), offset, true); + cifs_setsize(inode, offset); + + if (!cookie) + return; + + fscache_use_cookie(cookie, true); + fscache_resize_cookie(cookie, offset); + cifs_fscache_unuse_inode_cookie(inode, true); } int cifs_file_set_size(const unsigned int xid, struct dentry *dentry, @@ -3125,10 +3141,8 @@ int cifs_file_set_size(const unsigned int xid, struct dentry *dentry, cifs_put_tlink(tlink); set_size_out: - if (rc == 0) { - netfs_resize_file(&cifsInode->netfs, size, true); - cifs_setsize(inode, size); - } + if (rc == 0) + cifs_resize_file_locked(inode, size); return rc; } diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c index 192649fec25d..0e872d58fae7 100644 --- a/fs/smb/client/smb2ops.c +++ b/fs/smb/client/smb2ops.c @@ -2222,8 +2222,7 @@ smb2_duplicate_extents(const unsigned int xid, rc = smb2_set_file_size(xid, tcon, trgtfile, dest_off + len, false); if (rc) goto duplicate_extents_out; - netfs_resize_file(netfs_inode(inode), dest_off + len, true); - cifs_setsize(inode, dest_off + len); + cifs_resize_file_locked(inode, dest_off + len); } rc = SMB2_ioctl(xid, tcon, trgtfile->fid.persistent_fid, trgtfile->fid.volatile_fid, @@ -3776,8 +3775,7 @@ static long smb3_simple_falloc(struct file *file, struct cifs_tcon *tcon, } new_eof = off + len; - netfs_resize_file(&cifsi->netfs, new_eof, true); - cifs_setsize(inode, new_eof); + cifs_resize_file_locked(inode, new_eof); qrc = SMB2_query_info(xid, tcon, cfile->fid.persistent_fid, @@ -3825,8 +3823,7 @@ static long smb3_simple_falloc(struct file *file, struct cifs_tcon *tcon, if (rc) goto out; - netfs_resize_file(&cifsi->netfs, new_eof, true); - cifs_setsize(inode, new_eof); + cifs_resize_file_locked(inode, new_eof); qrc = SMB2_query_info(xid, tcon, cfile->fid.persistent_fid, From 297d8026a570f2d5552d34e2aa143408da1d57c9 Mon Sep 17 00:00:00 2001 From: Frank Sorenson Date: Fri, 31 Jul 2026 12:12:30 -0500 Subject: [PATCH 09/33] cifs: remove redundant size-update block in cifs_remap_file_range() cifs_remap_file_range() acquires i_rwsem on both inodes via lock_two_nondirectories() before calling smb2_duplicate_extents(). cifs_setsize() (called inside smb2_duplicate_extents() when the clone extends the file) therefore already runs under the lock, meaning the fscache_resize_cookie() added to cifs_setsize() by commit fa724e235cfd ("cifs: add fscache_resize_cookie() to cifs_setsize()") is correctly serialised for this path without further changes. That same commit made the caller-side block: if (rc == 0 && new_size > i_size) { truncate_setsize(target_inode, new_size); fscache_resize_cookie(cifs_inode_cookie(target_inode), new_size); } redundant: smb2_duplicate_extents() already performs the full size update via cifs_setsize() when the operation extends the file. Remove the now-dead block. Signed-off-by: Frank Sorenson Reviewed-by: Huiwen He Signed-off-by: Namjae Jeon Signed-off-by: Paulo Alcantara --- fs/smb/client/cifsfs.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/fs/smb/client/cifsfs.c b/fs/smb/client/cifsfs.c index a1dacc7d8f74..2073b29ff969 100644 --- a/fs/smb/client/cifsfs.c +++ b/fs/smb/client/cifsfs.c @@ -1466,11 +1466,7 @@ static loff_t cifs_remap_file_range(struct file *src_file, loff_t off, if (target_tcon->ses->server->ops->duplicate_extents) { rc = target_tcon->ses->server->ops->duplicate_extents(xid, smb_file_src, smb_file_target, off, len, destoff); - if (rc == 0 && new_size > i_size) { - truncate_setsize(target_inode, new_size); - fscache_resize_cookie(cifs_inode_cookie(target_inode), - new_size); - } else if (rc == -EOPNOTSUPP) { + if (rc == -EOPNOTSUPP) { /* * copy_file_range syscall man page indicates EINVAL * is returned e.g when "fd_in and fd_out refer to the From 60be95527bc8d1b33dca25d2a849268cca11d139 Mon Sep 17 00:00:00 2001 From: Frank Sorenson Date: Fri, 31 Jul 2026 12:12:31 -0500 Subject: [PATCH 10/33] cifs: remove dead size-update blocks in cifs_setattr_unix/nounix Commit 110fee6b9bb5 ("smb: client: fix missing timestamp updates with O_TRUNC") introduced cifs_file_set_size(), which calls netfs_resize_file() and cifs_setsize() on success. cifs_setsize() calls i_size_write(), updating i_size to the new value. The subsequent blocks in both cifs_setattr_unix() and cifs_setattr_nounix(): if ((attrs->ia_valid & ATTR_SIZE) && attrs->ia_size != i_size_read(inode)) { truncate_setsize(inode, attrs->ia_size); netfs_resize_file(&cifsInode->netfs, attrs->ia_size, true); fscache_resize_cookie(cifs_inode_cookie(inode), attrs->ia_size); } are therefore unreachable on the success path: attrs->ia_size == i_size_read(inode) always holds after cifs_file_set_size() succeeds. On the failure path, execution jumps to out/cifs_setattr_exit before reaching these blocks. truncate_setsize() and netfs_resize_file() are redundant with what cifs_file_set_size() already did; fscache_resize_cookie() was moved there by commit fa724e235cfd ("cifs: add fscache_resize_cookie() to cifs_setsize()"). Remove both dead blocks. Fixes: 110fee6b9bb5 ("smb: client: fix missing timestamp updates with O_TRUNC") Signed-off-by: Frank Sorenson Reviewed-by: Huiwen He Signed-off-by: Namjae Jeon Signed-off-by: Paulo Alcantara --- fs/smb/client/inode.c | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/fs/smb/client/inode.c b/fs/smb/client/inode.c index 098d5496e779..ec1679ad4a87 100644 --- a/fs/smb/client/inode.c +++ b/fs/smb/client/inode.c @@ -3307,13 +3307,6 @@ cifs_setattr_unix(struct dentry *direntry, struct iattr *attrs) if (rc) goto out; - if ((attrs->ia_valid & ATTR_SIZE) && - attrs->ia_size != i_size_read(inode)) { - truncate_setsize(inode, attrs->ia_size); - netfs_resize_file(&cifsInode->netfs, attrs->ia_size, true); - fscache_resize_cookie(cifs_inode_cookie(inode), attrs->ia_size); - } - setattr_copy(&nop_mnt_idmap, inode, attrs); mark_inode_dirty(inode); @@ -3534,13 +3527,6 @@ cifs_setattr_nounix(struct dentry *direntry, struct iattr *attrs) if (rc) goto cifs_setattr_exit; - if ((attrs->ia_valid & ATTR_SIZE) && - attrs->ia_size != i_size_read(inode)) { - truncate_setsize(inode, attrs->ia_size); - netfs_resize_file(&cifsInode->netfs, attrs->ia_size, true); - fscache_resize_cookie(cifs_inode_cookie(inode), attrs->ia_size); - } - setattr_copy(&nop_mnt_idmap, inode, attrs); mark_inode_dirty(inode); From 77d852c76342ff4922f7fabef465cc5d012ab0a7 Mon Sep 17 00:00:00 2001 From: ChenXiaoSong Date: Tue, 4 Aug 2026 18:42:05 -0500 Subject: [PATCH 11/33] smb/client: fix nlink of an overwritten open file Reproducer: 1. server: systemctl start ksmbd 2. client: mount with `posix` option mount -t cifs -o posix //${server_ip}/export /mnt 3. client: touch /mnt/file1 /mnt/file2 4. client: C program: int fd = open("/mnt/file2", O_RDONLY); 5. client: C program: rename("/mnt/file1", "/mnt/file2"); 6. client: C program: struct stat stbuf; fstat(fd, &stbuf); stbuf.st_nlink is 1, should be 0 This patch fixes xfstests generic/035 when mounted with `posix` option. Signed-off-by: ChenXiaoSong Signed-off-by: Steve French Signed-off-by: Namjae Jeon Signed-off-by: Paulo Alcantara --- fs/smb/client/inode.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/fs/smb/client/inode.c b/fs/smb/client/inode.c index ec1679ad4a87..3a0263df104b 100644 --- a/fs/smb/client/inode.c +++ b/fs/smb/client/inode.c @@ -2648,11 +2648,8 @@ cifs_rename2(struct mnt_idmap *idmap, struct inode *source_dir, if (d_really_is_positive(target_dentry)) { if (!rc) { struct inode *inode = d_inode(target_dentry); - /* - * Samba and ksmbd servers allow renaming a target - * directory that is open, so make sure to update - * ->i_nlink and then mark it as delete pending. - */ + + /* Update the target link count after rename. */ if (S_ISDIR(inode->i_mode)) { drop_cached_dir_by_name(xid, tcon, to_name, cifs_sb); spin_lock(&inode->i_lock); @@ -2663,6 +2660,10 @@ cifs_rename2(struct mnt_idmap *idmap, struct inode *source_dir, CIFS_I(inode)->time = 0; /* force reval */ inode_set_ctime_current(inode); inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode)); + } else { + cifs_mark_open_handles_for_deleted_file(inode, to_name); + cifs_drop_nlink(inode); + inode_set_ctime_current(inode); } } else if (rc == -EACCES || rc == -EEXIST) { /* From 48cab1fd5720508148673f59d8ed52c7c7fffca2 Mon Sep 17 00:00:00 2001 From: Frank Sorenson Date: Sat, 8 Aug 2026 15:29:03 -0500 Subject: [PATCH 12/33] cifs: fix clearing stats for fastest execution of each smb2 command The code to clear the 'fastest_cmd' statistics has a typo that repeatedly clears the stat for cmd 0, rather than iterating through each cmd. Fix the typo (0->i). Fixes: 433b8dd7672be ("SMB3: Track total time spent on roundtrips for each SMB3 command") Signed-off-by: Frank Sorenson Signed-off-by: Namjae Jeon Signed-off-by: Paulo Alcantara --- fs/smb/client/cifs_debug.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/smb/client/cifs_debug.c b/fs/smb/client/cifs_debug.c index 4ed4f55a0bb7..3761d3ad6088 100644 --- a/fs/smb/client/cifs_debug.c +++ b/fs/smb/client/cifs_debug.c @@ -754,7 +754,7 @@ static ssize_t cifs_stats_proc_write(struct file *file, atomic_set(&server->smb2slowcmd[i], 0); server->time_per_cmd[i] = 0; server->slowest_cmd[i] = 0; - server->fastest_cmd[0] = 0; + server->fastest_cmd[i] = 0; } #endif /* CONFIG_CIFS_STATS2 */ list_for_each_entry(ses, &server->smb_ses_list, smb_ses_list) { From ebdc1afb1e268de4c01814fa960286392801b604 Mon Sep 17 00:00:00 2001 From: Ze Tan Date: Tue, 11 Aug 2026 14:00:45 +0800 Subject: [PATCH 13/33] smb/client: mark missing nlink values as unknown Several SMB1 fallback and open responses do not provide the hard link count. The SMB2 create-only query fallback has the same limitation. These paths currently leave a zero link count or synthesize a value of one and then expose it as authoritative metadata. Mark those results with unknown_nlink so existing inodes keep their cached link count and new inodes receive the usual sane default. This was tested against Samba with "server min protocol = NT1". Mount the share using SMB1 with Unix extensions disabled: mount -t cifs /// /mnt/cifs \ -o username=,vers=1.0,nounix Create three names for the same inode and cache its real link count: TESTDIR=/mnt/cifs/nlink-repro-$$ mkdir "$TESTDIR" touch "$TESTDIR/file1" ln "$TESTDIR/file1" "$TESTDIR/file2" ln "$TESTDIR/file1" "$TESTDIR/file3" stat -c 'before open: %h' "$TESTDIR/file1" Open the file and read the link count through the open descriptor: exec 3<"$TESTDIR/file1" stat -Lc 'after open: %h' /proc/$$/fd/3 exec 3<&- Clean up the test files: rm -f "$TESTDIR/file1" "$TESTDIR/file2" "$TESTDIR/file3" rmdir "$TESTDIR" Before this change, the two stat commands report 3 and 1 because the SMB1 open response overwrites the known link count. With this change, both commands report 3. Signed-off-by: Ze Tan Signed-off-by: Namjae Jeon Signed-off-by: Paulo Alcantara --- fs/smb/client/smb1ops.c | 8 +++++++- fs/smb/client/smb2inode.c | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/fs/smb/client/smb1ops.c b/fs/smb/client/smb1ops.c index dc5a8c1da623..7e2b29060f51 100644 --- a/fs/smb/client/smb1ops.c +++ b/fs/smb/client/smb1ops.c @@ -542,6 +542,7 @@ static int cifs_query_path_info(const unsigned int xid, data->reparse_point = false; data->adjust_tz = false; + data->unknown_nlink = false; /* * First try CIFSSMBQPathInfo() function which returns more info @@ -608,6 +609,7 @@ static int cifs_query_path_info(const unsigned int xid, fi.EASize = di->EaSize; } fi.NumberOfLinks = cpu_to_le32(1); + data->unknown_nlink = true; fi.DeletePending = 0; fi.Directory = !!(le32_to_cpu(fi.Attributes) & ATTR_DIRECTORY); cifs_buf_release(search_info.ntwrk_buf_start); @@ -630,6 +632,8 @@ static int cifs_query_path_info(const unsigned int xid, rc = SMBQueryInformation(xid, tcon, full_path, &fi, cifs_sb->local_nls, cifs_remap(cifs_sb)); data->adjust_tz = true; + if (!rc) + data->unknown_nlink = true; } else if ((rc == -EOPNOTSUPP || rc == -EINVAL) && non_unicode_wildcard) { /* Path with non-UNICODE wildcard character cannot exist. */ rc = -ENOENT; @@ -893,8 +897,10 @@ static int cifs_open_file(const unsigned int xid, struct cifs_open_parms *oparms else rc = CIFS_open(xid, oparms, oplock, &fi); - if (!rc && data) + if (!rc && data) { move_cifs_info_to_smb2(&data->fi, &fi); + data->unknown_nlink = true; + } return rc; } diff --git a/fs/smb/client/smb2inode.c b/fs/smb/client/smb2inode.c index 213bc298cdf2..d4ae8a5ad463 100644 --- a/fs/smb/client/smb2inode.c +++ b/fs/smb/client/smb2inode.c @@ -576,6 +576,7 @@ static int smb2_compound_op(const unsigned int xid, struct cifs_tcon *tcon, idata->fi.EndOfFile = create_rsp->EndofFile; if (le32_to_cpu(idata->fi.NumberOfLinks) == 0) idata->fi.NumberOfLinks = cpu_to_le32(1); /* dummy value */ + idata->unknown_nlink = true; idata->fi.DeletePending = 0; /* successful open = not delete pending */ idata->fi.Directory = !!(le32_to_cpu(create_rsp->FileAttributes) & ATTR_DIRECTORY); From 9437f2113b60a5a8593aa4b41b6f6632f5cabcfd Mon Sep 17 00:00:00 2001 From: Ze Tan Date: Tue, 11 Aug 2026 14:00:46 +0800 Subject: [PATCH 14/33] smb/client: preserve open info type across compound queries contains_posix_file_info describes the metadata stored in the fi/posix_fi union. GET_REPARSE and QUERY_WSL_EA do not update that union, so clearing the flag while processing those responses can make POSIX metadata look like FILE_ALL_INFORMATION. Set the flag when CREATE or a validated query response actually populates the union, and leave it unchanged for auxiliary compound operations. This also avoids changing the type when a query fails before copying any metadata. The issue can be reproduced against a Samba server with SMB3 UNIX extensions enabled: mount -t cifs /// /mnt/cifs \ -o vers=3.1.1,posix,reparse=nfs,actimeo=0 mkfifo /mnt/cifs/test-fifo umount /mnt/cifs mount -t cifs /// /mnt/cifs \ -o vers=3.1.1,posix,reparse=nfs,actimeo=0 stat -c '%F %s' /mnt/cifs/test-fifo Before this change, stat reports "fifo 1024" although the server-side EOF is zero. After this change, it reports "fifo 0". Fixes: 9df23801c83d ("smb311: failure to open files of length 1040 when mounting with SMB3.1.1 POSIX extensions") Signed-off-by: Ze Tan Signed-off-by: Namjae Jeon Signed-off-by: Paulo Alcantara --- fs/smb/client/smb2inode.c | 9 +++++---- fs/smb/client/smb2pdu.c | 1 + 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/fs/smb/client/smb2inode.c b/fs/smb/client/smb2inode.c index d4ae8a5ad463..058b05f7a3e5 100644 --- a/fs/smb/client/smb2inode.c +++ b/fs/smb/client/smb2inode.c @@ -574,6 +574,7 @@ static int smb2_compound_op(const unsigned int xid, struct cifs_tcon *tcon, idata->fi.Attributes = create_rsp->FileAttributes; idata->fi.AllocationSize = create_rsp->AllocationSize; idata->fi.EndOfFile = create_rsp->EndofFile; + idata->contains_posix_file_info = false; if (le32_to_cpu(idata->fi.NumberOfLinks) == 0) idata->fi.NumberOfLinks = cpu_to_le32(1); /* dummy value */ idata->unknown_nlink = true; @@ -597,7 +598,6 @@ static int smb2_compound_op(const unsigned int xid, struct cifs_tcon *tcon, switch (cmds[i]) { case SMB2_OP_QUERY_INFO: idata = in_iov[i].iov_base; - idata->contains_posix_file_info = false; if (rc == 0 && cfile && cfile->symlink_target) { idata->symlink_target = kstrdup(cfile->symlink_target, GFP_KERNEL); if (!idata->symlink_target) @@ -610,6 +610,8 @@ static int smb2_compound_op(const unsigned int xid, struct cifs_tcon *tcon, le16_to_cpu(qi_rsp->OutputBufferOffset), le32_to_cpu(qi_rsp->OutputBufferLength), &rsp_iov[i + 1], sizeof(idata->fi), (char *)&idata->fi); + if (!rc) + idata->contains_posix_file_info = false; } SMB2_query_info_free(&rqst[num_rqst++]); if (rc) @@ -621,7 +623,6 @@ static int smb2_compound_op(const unsigned int xid, struct cifs_tcon *tcon, break; case SMB2_OP_POSIX_QUERY_INFO: idata = in_iov[i].iov_base; - idata->contains_posix_file_info = true; if (rc == 0 && cfile && cfile->symlink_target) { idata->symlink_target = kstrdup(cfile->symlink_target, GFP_KERNEL); if (!idata->symlink_target) @@ -635,6 +636,8 @@ static int smb2_compound_op(const unsigned int xid, struct cifs_tcon *tcon, le32_to_cpu(qi_rsp->OutputBufferLength), &rsp_iov[i + 1], sizeof(idata->posix_fi) /* add SIDs */, (char *)&idata->posix_fi); + if (!rc) + idata->contains_posix_file_info = true; } if (rc == 0) rc = parse_posix_sids(idata, &rsp_iov[i + 1]); @@ -706,7 +709,6 @@ static int smb2_compound_op(const unsigned int xid, struct cifs_tcon *tcon, idata = in_iov[i].iov_base; idata->reparse.io.iov = *iov; idata->reparse.io.buftype = resp_buftype[i + 1]; - idata->contains_posix_file_info = false; /* BB VERIFY */ rbuf = reparse_buf_ptr(iov); if (IS_ERR(rbuf)) { rc = PTR_ERR(rbuf); @@ -728,7 +730,6 @@ static int smb2_compound_op(const unsigned int xid, struct cifs_tcon *tcon, case SMB2_OP_QUERY_WSL_EA: if (!rc) { idata = in_iov[i].iov_base; - idata->contains_posix_file_info = false; qi_rsp = rsp_iov[i + 1].iov_base; data[0] = (u8 *)qi_rsp + le16_to_cpu(qi_rsp->OutputBufferOffset); size[0] = le32_to_cpu(qi_rsp->OutputBufferLength); diff --git a/fs/smb/client/smb2pdu.c b/fs/smb/client/smb2pdu.c index d207591cc889..dea05aeb53a1 100644 --- a/fs/smb/client/smb2pdu.c +++ b/fs/smb/client/smb2pdu.c @@ -3372,6 +3372,7 @@ SMB2_open(const unsigned int xid, struct cifs_open_parms *oparms, __le16 *path, #endif /* CIFS_DEBUG2 */ if (file_info) { + buf->contains_posix_file_info = false; file_info->CreationTime = rsp->CreationTime; file_info->LastAccessTime = rsp->LastAccessTime; file_info->LastWriteTime = rsp->LastWriteTime; From 43549eb84266c424ee6c004f149574e2ae4b6515 Mon Sep 17 00:00:00 2001 From: Ze Tan Date: Tue, 11 Aug 2026 14:00:47 +0800 Subject: [PATCH 15/33] smb/client: decode reparse metadata using its payload type cifs_open_info_data stores FILE_ALL_INFORMATION and SMB3 POSIX query information in a union. reparse_info_to_fattr() selects a union member from the mount mode, while several directory checks always read fi.Attributes. The metadata can instead come from an SMB2 CREATE response on a POSIX mount, or from a POSIX query while processing a reparse point. In those cases the mount mode and hard-coded fi accesses select the wrong union member. See the procedures below: cifs_nt_open smb2_open_file SMB2_open data->fi = SMB2 CREATE response data->contains_posix_file_info = false cifs_get_inode_info reparse_info_to_fattr if (tcon->posix_extensions) // true smb311_posix_info_to_fattr data->posix_fi // wrong union member smb311_posix_get_fattr smb2_query_path_info smb2_compound_op data->posix_fi = SMB3 POSIX query response data->contains_posix_file_info = true reparse_info_to_fattr data->fi.Attributes // wrong union member Add a common DOS attribute accessor and use contains_posix_file_info both for attribute reads and for the final fattr conversion. Signed-off-by: Ze Tan Signed-off-by: Namjae Jeon Signed-off-by: Paulo Alcantara --- fs/smb/client/inode.c | 9 +++++---- fs/smb/client/reparse.h | 17 ++++++++++------- fs/smb/client/smb2inode.c | 6 ++++-- 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/fs/smb/client/inode.c b/fs/smb/client/inode.c index 3a0263df104b..cb5515a50f74 100644 --- a/fs/smb/client/inode.c +++ b/fs/smb/client/inode.c @@ -1215,7 +1215,7 @@ static int reparse_info_to_fattr(struct cifs_open_info_data *data, break; case IO_REPARSE_TAG_INTERNAL: rc = 0; - if (le32_to_cpu(data->fi.Attributes) & ATTR_DIRECTORY) { + if (cifs_open_data_attrs(data) & ATTR_DIRECTORY) { cifs_create_junction_fattr(fattr, sb); goto out; } @@ -1239,7 +1239,7 @@ static int reparse_info_to_fattr(struct cifs_open_info_data *data, */ if (rc == -EOPNOTSUPP && IS_REPARSE_TAG_NAME_SURROGATE(data->reparse.tag) && - (le32_to_cpu(data->fi.Attributes) & ATTR_DIRECTORY)) { + (cifs_open_data_attrs(data) & ATTR_DIRECTORY)) { rc = 0; cifs_create_junction_fattr(fattr, sb); goto out; @@ -1257,13 +1257,14 @@ static int reparse_info_to_fattr(struct cifs_open_info_data *data, } if (data->reparse.tag == IO_REPARSE_TAG_SYMLINK && !rc) { - bool directory = le32_to_cpu(data->fi.Attributes) & ATTR_DIRECTORY; + bool directory = cifs_open_data_attrs(data) & ATTR_DIRECTORY; + rc = smb2_fix_symlink_target_type(&data->symlink_target, directory, cifs_sb); } break; } - if (tcon->posix_extensions) + if (data->contains_posix_file_info) smb311_posix_info_to_fattr(fattr, data, sb); else cifs_open_info_to_fattr(fattr, data, sb); diff --git a/fs/smb/client/reparse.h b/fs/smb/client/reparse.h index 0164dc47bdfd..49efd85b1e94 100644 --- a/fs/smb/client/reparse.h +++ b/fs/smb/client/reparse.h @@ -98,15 +98,21 @@ static inline bool reparse_inode_match(struct inode *inode, timespec64_equal(&ctime, &fattr->cf_ctime); } +static inline u32 cifs_open_data_attrs(const struct cifs_open_info_data *data) +{ + if (data->contains_posix_file_info) + return le32_to_cpu(data->posix_fi.DosAttributes); + + return le32_to_cpu(data->fi.Attributes); +} + static inline bool cifs_open_data_reparse(struct cifs_open_info_data *data) { - u32 attrs; - bool ret; + u32 attrs = cifs_open_data_attrs(data); if (data->contains_posix_file_info) { struct smb311_posix_qinfo *fi = &data->posix_fi; - attrs = le32_to_cpu(fi->DosAttributes); if (data->reparse_point) { attrs |= ATTR_REPARSE_POINT; fi->DosAttributes = cpu_to_le32(attrs); @@ -115,16 +121,13 @@ static inline bool cifs_open_data_reparse(struct cifs_open_info_data *data) } else { struct smb2_file_all_info *fi = &data->fi; - attrs = le32_to_cpu(fi->Attributes); if (data->reparse_point) { attrs |= ATTR_REPARSE_POINT; fi->Attributes = cpu_to_le32(attrs); } } - ret = attrs & ATTR_REPARSE_POINT; - - return ret; + return attrs & ATTR_REPARSE_POINT; } bool cifs_reparse_point_to_fattr(struct cifs_sb_info *cifs_sb, diff --git a/fs/smb/client/smb2inode.c b/fs/smb/client/smb2inode.c index 058b05f7a3e5..bcaa44814b71 100644 --- a/fs/smb/client/smb2inode.c +++ b/fs/smb/client/smb2inode.c @@ -22,6 +22,7 @@ #include "smb2glob.h" #include "smb2proto.h" #include "cached_dir.h" +#include "reparse.h" #include "../common/smb2status.h" #include "../common/smbfsctl.h" @@ -1002,12 +1003,13 @@ int smb2_query_path_info(const unsigned int xid, /* * If the symlink was already parsed in create response then it is needed to fix * its type now (after the second call with OPEN_REPARSE_POINT which filled the - * data->fi.Attributes). If the symlink was not parsed in create response then + * metadata attributes). If the symlink was not parsed in create response then * the data->symlink_target was not filled yet and then the type will be fixed * later after data->symlink_target is filled. */ if (data->reparse.tag == IO_REPARSE_TAG_SYMLINK && !rc && data->symlink_target) { - bool directory = le32_to_cpu(data->fi.Attributes) & ATTR_DIRECTORY; + bool directory = cifs_open_data_attrs(data) & ATTR_DIRECTORY; + rc = smb2_fix_symlink_target_type(&data->symlink_target, directory, cifs_sb); } break; From 6343c1da561962688f203362d80d6a3bfa39fa1b Mon Sep 17 00:00:00 2001 From: Frank Sorenson Date: Tue, 11 Aug 2026 21:41:26 -0500 Subject: [PATCH 16/33] smb: client: fix OOB read/write from unvalidated DataOffset in coalesce_t2() coalesce_t2() computes data pointers directly from server-supplied DataOffset fields with no validation against buffer bounds: data_area_of_tgt = (char *)&pSMBt->hdr.Protocol + get_unaligned_le16(&pSMBt->t2_rsp.DataOffset); data_area_of_src = (char *)&pSMBs->hdr.Protocol + get_unaligned_le16(&pSMBs->t2_rsp.DataOffset); data_area_of_tgt += total_in_tgt; ... memcpy(data_area_of_tgt, data_area_of_src, total_in_src); A small DataOffset can push a pointer below the actual byte area, overwriting header fields; a large one can push it past the buffer end, causing out-of-bounds heap reads (source) or writes (target). The BCC overflow guard does not prevent this: BCC reflects how much data is present, while DataOffset controls where in the buffer it starts. The "validate target area" comment present since the function was first written in 2005 was a placeholder that was never implemented. Add lower- and upper-bound checks for both data pointers before the memcpy, and before any target header fields are modified. Fixes: e4eb295d38b5 ("[PATCH] cifs: Handle multiple response transact2 part 1 of 2") Cc: stable@vger.kernel.org Reported-by: Shen Yongchao Signed-off-by: Frank Sorenson Signed-off-by: Namjae Jeon Signed-off-by: Paulo Alcantara --- fs/smb/client/smb1transport.c | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/fs/smb/client/smb1transport.c b/fs/smb/client/smb1transport.c index 966f2cf83a51..66daa5a37e4a 100644 --- a/fs/smb/client/smb1transport.c +++ b/fs/smb/client/smb1transport.c @@ -375,12 +375,31 @@ coalesce_t2(char *second_buf, struct smb_hdr *target_hdr, unsigned int *pdu_len) data_area_of_tgt = (char *)&pSMBt->hdr.Protocol + get_unaligned_le16(&pSMBt->t2_rsp.DataOffset); - /* validate target area */ data_area_of_src = (char *)&pSMBs->hdr.Protocol + get_unaligned_le16(&pSMBs->t2_rsp.DataOffset); data_area_of_tgt += total_in_tgt; + /* + * DataOffset fields are server-supplied and not validated against + * buffer bounds; check both data pointers before mutating the + * target header. + */ + if (data_area_of_tgt < (char *)target_hdr + + sizeof(struct smb_t2_rsp) + sizeof(__le16) || + data_area_of_tgt + total_in_src > + (char *)target_hdr + CIFSMaxBufSize + MAX_CIFS_HDR_SIZE) { + cifs_dbg(VFS, "%s: target data area out of bounds\n", __func__); + return -EPROTO; + } + if (data_area_of_src < second_buf + + sizeof(struct smb_t2_rsp) + sizeof(__le16) || + data_area_of_src + total_in_src > + second_buf + smbCalcSize((struct smb_hdr *)second_buf)) { + cifs_dbg(VFS, "%s: secondary data area out of bounds\n", __func__); + return -EPROTO; + } + total_in_tgt += total_in_src; /* is the result too big for the field? */ if (total_in_tgt > USHRT_MAX) { From 730d0bb19507b9e19c2fe5343109ac618e2fbce5 Mon Sep 17 00:00:00 2001 From: Frank Sorenson Date: Wed, 12 Aug 2026 21:39:51 -0500 Subject: [PATCH 17/33] smb: client: fix UAF and buffer leak in cifs_check_trans2() for malformed secondary T2 When a valid primary TRANSACT2 response has been received (mid->resp_buf set, mid->multiRsp true) and a subsequent secondary response causes cifs_check_trans2() to return false -- either because the SMB header is invalid (malformed != 0) or because check2ndT2() rejects the PDU -- handle_mid() overwrites mid->resp_buf with the new buffer (leaking the primary buffer) and, because mid->multiRsp is set, skips the server->smallbuf/bigbuf NULL-out. When the user thread frees mid->resp_buf, server->smallbuf or server->bigbuf is left dangling; the demux thread reuses it for the next packet, resulting in a use-after-free. Combine both early-exit conditions and, when mid->multiRsp is already set, abort the pending transaction inline: set multiEnd, call dequeue_mid() with malformed=true, and return true so handle_mid() exits without touching mid->resp_buf or the server buffer pointers. Fixes: 316cf94a910f ("CIFS: Move trans2 processing to ops struct") Cc: stable@vger.kernel.org # cifs_check_trans2() is in smb1ops.c on kernels < 7.0 Signed-off-by: Frank Sorenson Signed-off-by: Namjae Jeon Signed-off-by: Paulo Alcantara --- fs/smb/client/smb1transport.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/fs/smb/client/smb1transport.c b/fs/smb/client/smb1transport.c index 66daa5a37e4a..42e95cc1bd89 100644 --- a/fs/smb/client/smb1transport.c +++ b/fs/smb/client/smb1transport.c @@ -449,10 +449,18 @@ bool cifs_check_trans2(struct mid_q_entry *mid, struct TCP_Server_Info *server, char *buf, int malformed) { - if (malformed) - return false; - if (check2ndT2(buf) <= 0) + if (malformed || check2ndT2(buf) <= 0) { + /* mid->multiRsp blocks the server buf detach in handle_mid(); + * returning false here would leak resp_buf and leave a dangling + * server->smallbuf/bigbuf after the user thread frees resp_buf. + */ + if (mid->multiRsp) { + mid->multiEnd = true; + dequeue_mid(server, mid, true); + return true; + } return false; + } mid->multiRsp = true; if (mid->resp_buf) { /* merge response - fix up 1st*/ From 3fffaa8a646c5bee02a553014b7f80e6ea2a76dd Mon Sep 17 00:00:00 2001 From: Dmitry Antipov Date: Fri, 14 Aug 2026 07:14:18 +0300 Subject: [PATCH 18/33] smb: client: simplify __build_path_from_dentry_optional_prefix() Use the convenient 'strreplace()' to simplify '__build_path_from_dentry_optional_prefix()'. Signed-off-by: Dmitry Antipov Signed-off-by: Namjae Jeon Signed-off-by: Paulo Alcantara --- fs/smb/client/dir.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/fs/smb/client/dir.c b/fs/smb/client/dir.c index b0ddcaa2d815..c292e3cd623b 100644 --- a/fs/smb/client/dir.c +++ b/fs/smb/client/dir.c @@ -115,11 +115,7 @@ char *__build_path_from_dentry_optional_prefix(struct dentry *direntry, void *pa } if (dirsep != '/') { /* BB test paths to Windows with '/' in the midst of prepath */ - char *p; - - for (p = s; *p; p++) - if (*p == '/') - *p = dirsep; + strreplace(s, '/', dirsep); } if (dfsplen) { s -= dfsplen; From 62656b024efc21c3230eade1a847f25871c3d2bb Mon Sep 17 00:00:00 2001 From: Frank Sorenson Date: Mon, 17 Aug 2026 12:16:51 -0500 Subject: [PATCH 19/33] smb: client: fix ALIGN() overflow in symlink_data() error context loop The check added by commit 7d9a7f1f96cd ("smb/client: fix possible infinite loop and oob read in symlink_data()") compared the post-ALIGN length against the remaining buffer, but ALIGN() itself can overflow: for ErrorDataLength near UINT32_MAX (e.g. 0xFFFFFFF9), ALIGN(x, 8) wraps to 0, so the subsequent bounds check passes, and the loop advances by zero bytes leaving 'p' pointing into stale data. Fix by checking the raw ErrorDataLength against the remaining space before applying ALIGN(), then checking again after. Since raw_len is bounded by the buffer, raw_len + 7 cannot overflow, so the second check is an exact post-alignment bounds guard. Fixes: 76894f3e2f71 ("cifs: improve symlink handling for smb2+") Cc: stable@vger.kernel.org Signed-off-by: Frank Sorenson Signed-off-by: Namjae Jeon Signed-off-by: Paulo Alcantara --- fs/smb/client/smb2file.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/fs/smb/client/smb2file.c b/fs/smb/client/smb2file.c index f35b6488d810..fb2fccbe8667 100644 --- a/fs/smb/client/smb2file.c +++ b/fs/smb/client/smb2file.c @@ -61,7 +61,10 @@ static struct smb2_symlink_err_rsp *symlink_data(const struct kvec *iov) cifs_dbg(FYI, "%s: skipping unhandled error context: 0x%x\n", __func__, le32_to_cpu(p->ErrorId)); - len = ALIGN(le32_to_cpu(p->ErrorDataLength), 8); + len = le32_to_cpu(p->ErrorDataLength); + if (len > end - ((u8 *)p + sizeof(*p))) + return ERR_PTR(-EINVAL); + len = ALIGN(len, 8); if (len > end - ((u8 *)p + sizeof(*p))) return ERR_PTR(-EINVAL); From 05f78e6cf34ea3a285053bd5999e08e8ac298bd5 Mon Sep 17 00:00:00 2001 From: Frank Sorenson Date: Mon, 17 Aug 2026 12:16:57 -0500 Subject: [PATCH 20/33] smb: client: fix use-before-check of ReparseDataLength in reparse_buf_ptr() reparse_buf_ptr() reads buf->ReparseDataLength before checking that count covers the full fixed header: buf = (struct reparse_data_buffer *)((u8 *)io + off); len = sizeof(*buf); /* 8 bytes */ rdlen = le16_to_cpu(buf->ReparseDataLength); /* offset 4, 2 bytes */ if (count < len || count < rdlen + len) /* check comes after */ struct reparse_data_buffer has ReparseDataLength at offset 4. If a server returns OutputCount < 6, the read at offset 4-5 reaches past the end of the received data. The off+count bounds against iov_len were already validated, but that does not protect against count being smaller than sizeof(*buf). Split the check: verify count >= sizeof(*buf) before reading ReparseDataLength, then verify count covers the data region. Fixes: a158bb66b137 ("smb: client: optimise reparse point querying") Cc: stable@vger.kernel.org Signed-off-by: Frank Sorenson Signed-off-by: Namjae Jeon Signed-off-by: Paulo Alcantara --- fs/smb/client/smb2inode.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/fs/smb/client/smb2inode.c b/fs/smb/client/smb2inode.c index bcaa44814b71..98ea5c6c34af 100644 --- a/fs/smb/client/smb2inode.c +++ b/fs/smb/client/smb2inode.c @@ -41,9 +41,11 @@ static struct reparse_data_buffer *reparse_buf_ptr(struct kvec *iov) buf = (struct reparse_data_buffer *)((u8 *)io + off); len = sizeof(*buf); - rdlen = le16_to_cpu(buf->ReparseDataLength); + if (count < len) + return ERR_PTR(smb_EIO2(smb_eio_trace_reparse_rdlen, count, 0)); - if (count < len || count < rdlen + len) + rdlen = le16_to_cpu(buf->ReparseDataLength); + if (count < rdlen + len) return ERR_PTR(smb_EIO2(smb_eio_trace_reparse_rdlen, count, rdlen)); return buf; } From ce31ec06d3c0ee9279dddb848343c62b90322ac3 Mon Sep 17 00:00:00 2001 From: Zizhi Wo Date: Tue, 18 Aug 2026 11:06:34 +0800 Subject: [PATCH 21/33] Revert "cifs: remove all cifs files before kill super" This reverts commit 6d9a4aaaa8b2612b5ef9d581e2f286a458b71ee1. First, directly flushing fileinfo_put_wq in that commit cannot guarantee that all in-flight I/O has run its cleanup_work on system_dfl_wq and subsequently called queue_work(fileinfo_put_wq, ...). Flushing only the latter workqueue may therefore miss puts that have not yet been queued, so the fix is not reliable in the first place. Moreover, this fix flushes inside cifs_umount(), which means the busy-dentry warning can still be triggered when umount_check() is called inside kill_anon_super(), because kill_anon_super() is executed before cifs_umount(). Second, commit 75f5c412fa86 ("smb: client: fix busy dentry warning on unmount after DIO") already drains both serverclose_wq and fileinfo_put_wq in cifs_kill_sb(), before kill_anon_super(). By adding a per-superblock outstanding-rreq counter, it guarantees that all cleanup_work for this sb have run, and thus all relevant cfile puts are queued on fileinfo_put_wq or serverclose_wq. Third, no path between those drains and cifs_umount() can queue new work onto either workqueue. In the "cifs_sb->root == NULL" path there are no file-related workers either, so that case is safe as well. Therefore the busy-dentry and null-ptr-deref problems cannot arise, and the flush added by commit 6d9a4aaaa8b2 ("cifs: remove all cifs files before kill super") is redundant and can be removed. Signed-off-by: Zizhi Wo Signed-off-by: Namjae Jeon Signed-off-by: Paulo Alcantara --- fs/smb/client/connect.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/fs/smb/client/connect.c b/fs/smb/client/connect.c index ba749ec25a59..f9764f65430e 100644 --- a/fs/smb/client/connect.c +++ b/fs/smb/client/connect.c @@ -4003,9 +4003,6 @@ cifs_umount(struct cifs_sb_info *cifs_sb) } spin_unlock(&cifs_sb->tlink_tree_lock); - flush_workqueue(serverclose_wq); - flush_workqueue(fileinfo_put_wq); - kfree(cifs_sb->prepath); call_rcu(&cifs_sb->rcu, delayed_free); } From 3d93986f68f4a58002755794a17411b5fe6d339b Mon Sep 17 00:00:00 2001 From: Thomas Huth Date: Wed, 12 Aug 2026 15:01:48 +0200 Subject: [PATCH 22/33] smb: client: Clear sensitive stack data in smb2transport.c Sensitive data like keys that are stored in stack-local arrays could be leaked via the stack to the calling functions. There is no known vulnerability for this right now, but it's good security style to explicitly zeroize this sensitive material as soon as possible to avoid that it could be exploited together with other bugs later. Signed-off-by: Thomas Huth Signed-off-by: Namjae Jeon Signed-off-by: Paulo Alcantara --- fs/smb/client/smb2transport.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/fs/smb/client/smb2transport.c b/fs/smb/client/smb2transport.c index 1143ee52470a..fdc634d99da0 100644 --- a/fs/smb/client/smb2transport.c +++ b/fs/smb/client/smb2transport.c @@ -249,6 +249,8 @@ smb2_calc_signature(struct smb_rqst *rqst, struct TCP_Server_Info *server) if (!rc) memcpy(shdr->Signature, smb2_signature, SMB2_SIGNATURE_SIZE); + memzero_explicit(key, sizeof(key)); + memzero_explicit(&hmac_ctx, sizeof(hmac_ctx)); return rc; } @@ -283,6 +285,7 @@ static void generate_key(struct cifs_ses *ses, struct kvec label, hmac_sha256_final(&hmac_ctx, prfhash); memcpy(key, prfhash, key_size); + memzero_explicit(prfhash, sizeof(prfhash)); } struct derivation { @@ -482,6 +485,7 @@ smb3_calc_signature(struct smb_rqst *rqst, struct TCP_Server_Info *server) memset(shdr->Signature, 0x0, SMB2_SIGNATURE_SIZE); rc = aes_cmac_preparekey(&cmac_key, key, SMB2_CMACAES_SIZE); + memzero_explicit(key, sizeof(key)); if (rc) { cifs_server_dbg(VFS, "%s: Could not set key for cmac aes\n", __func__); return rc; From 55a1ad8413f5bae504381b9bf90849c8cf283892 Mon Sep 17 00:00:00 2001 From: Thomas Huth Date: Wed, 12 Aug 2026 15:01:49 +0200 Subject: [PATCH 23/33] smb: client: Clear sensitive stack and heap data in smb2ops.c Make sure to not leak key-related data via the heap or the stack by using kfree_sensitive() or memzero_explicit() here. Signed-off-by: Thomas Huth Signed-off-by: Namjae Jeon Signed-off-by: Paulo Alcantara --- fs/smb/client/smb2ops.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c index 0e872d58fae7..7d6738ffcb80 100644 --- a/fs/smb/client/smb2ops.c +++ b/fs/smb/client/smb2ops.c @@ -1569,7 +1569,7 @@ SMB2_request_res_key(const unsigned int xid, struct cifs_tcon *tcon, memcpy(pcchunk->SourceKey, res_key->ResumeKey, COPY_CHUNK_RES_KEY_SIZE); req_res_key_exit: - kfree(res_key); + kfree_sensitive(res_key); return rc; } @@ -4633,7 +4633,7 @@ crypt_message(struct TCP_Server_Info *server, int num_rqst, rc = crypto_aead_setkey(tfm, key, SMB3_GCM256_CRYPTKEY_SIZE); else rc = crypto_aead_setkey(tfm, key, SMB3_GCM128_CRYPTKEY_SIZE); - + memzero_explicit(key, sizeof(key)); if (rc) { cifs_server_dbg(VFS, "%s: Failed to set aead key %d\n", __func__, rc); return rc; From 1a6bd74a27f1007b02c5a30aaa35608286bd0d23 Mon Sep 17 00:00:00 2001 From: Thomas Huth Date: Wed, 12 Aug 2026 15:01:50 +0200 Subject: [PATCH 24/33] smb: client: Clear sensitive stack data in cifsencrypt.c Make sure to not leak hash data via the stack, clear it with memzero_explicit() before leaving the function. Signed-off-by: Thomas Huth Signed-off-by: Namjae Jeon Signed-off-by: Paulo Alcantara --- fs/smb/client/cifsencrypt.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/fs/smb/client/cifsencrypt.c b/fs/smb/client/cifsencrypt.c index 34804e9842a8..71a2a59123f5 100644 --- a/fs/smb/client/cifsencrypt.c +++ b/fs/smb/client/cifsencrypt.c @@ -249,12 +249,13 @@ static int calc_ntlmv2_hash(struct cifs_ses *ses, char *ntlmv2_hash, E_md4hash(ses->password, nt_hash, nls_cp); hmac_md5_init_usingrawkey(&hmac_ctx, nt_hash, CIFS_NTHASH_SIZE); + memzero_explicit(nt_hash, sizeof(nt_hash)); /* convert ses->user_name to unicode */ len = ses->user_name ? strlen(ses->user_name) : 0; user = kmalloc(2 + (len * 2), GFP_KERNEL); if (user == NULL) - return -ENOMEM; + goto out_nomem; if (len) { len = cifs_strtoUTF16(user, ses->user_name, len, nls_cp); @@ -272,7 +273,7 @@ static int calc_ntlmv2_hash(struct cifs_ses *ses, char *ntlmv2_hash, domain = kmalloc(2 + (len * 2), GFP_KERNEL); if (domain == NULL) - return -ENOMEM; + goto out_nomem; len = cifs_strtoUTF16((__le16 *)domain, ses->domainName, len, nls_cp); @@ -284,7 +285,7 @@ static int calc_ntlmv2_hash(struct cifs_ses *ses, char *ntlmv2_hash, server = kmalloc(2 + (len * 2), GFP_KERNEL); if (server == NULL) - return -ENOMEM; + goto out_nomem; len = cifs_strtoUTF16((__le16 *)server, ses->ip_addr, len, nls_cp); hmac_md5_update(&hmac_ctx, (const u8 *)server, 2 * len); @@ -293,6 +294,10 @@ static int calc_ntlmv2_hash(struct cifs_ses *ses, char *ntlmv2_hash, hmac_md5_final(&hmac_ctx, ntlmv2_hash); return 0; + +out_nomem: + memzero_explicit(&hmac_ctx, sizeof(hmac_ctx)); + return -ENOMEM; } static void CalcNTLMv2_response(const struct cifs_ses *ses, char *ntlmv2_hash) @@ -463,6 +468,7 @@ setup_ntlmv2_rsp(struct cifs_ses *ses, const struct nls_table *nls_cp) rc = 0; unlock: cifs_server_unlock(ses->server); + memzero_explicit(ntlmv2_hash, sizeof(ntlmv2_hash)); setup_ntlmv2_rsp_ret: kfree_sensitive(tiblob); From 2f9af06e30b78ccb6f2708d89793180c29e4feef Mon Sep 17 00:00:00 2001 From: Thomas Huth Date: Wed, 12 Aug 2026 15:01:51 +0200 Subject: [PATCH 25/33] smb: client: Clear sensitive stack data in smb1encrypt.c Make sure to not leak signature data via the stack, clear it with memzero_explicit() before leaving the function. To avoid that we have to introduce "goto"-cleanup here, we re-arrange the code a little bit (and drop the commented cifs_dump_mem debug code that looks like a leftover from very early days). Signed-off-by: Thomas Huth Signed-off-by: Namjae Jeon Signed-off-by: Paulo Alcantara --- fs/smb/client/smb1encrypt.c | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/fs/smb/client/smb1encrypt.c b/fs/smb/client/smb1encrypt.c index bf10fdeeedca..c9eb68f04e7b 100644 --- a/fs/smb/client/smb1encrypt.c +++ b/fs/smb/client/smb1encrypt.c @@ -81,6 +81,7 @@ int cifs_sign_rqst(struct smb_rqst *rqst, struct TCP_Server_Info *server, else memcpy(cifs_pdu->Signature.SecuritySignature, smb_signature, 8); + memzero_explicit(smb_signature, sizeof(smb_signature)); return rc; } @@ -126,15 +127,13 @@ int cifs_verify_signature(struct smb_rqst *rqst, rc = cifs_calc_signature(rqst, server, what_we_think_sig_should_be); cifs_server_unlock(server); - if (rc) - return rc; - -/* cifs_dump_mem("what we think it should be: ", - what_we_think_sig_should_be, 16); */ - - if (crypto_memneq(server_response_sig, what_we_think_sig_should_be, 8)) - return -EACCES; - else - return 0; + if (!rc) { + if (crypto_memneq(server_response_sig, + what_we_think_sig_should_be, 8)) + rc = -EACCES; + } + memzero_explicit(what_we_think_sig_should_be, + sizeof(what_we_think_sig_should_be)); + return rc; } From 111a2b8717efbd9729d36828308d5163fdc977c3 Mon Sep 17 00:00:00 2001 From: Thomas Huth Date: Wed, 12 Aug 2026 15:01:52 +0200 Subject: [PATCH 26/33] smb: client: Avoid leaking sensitive data to the heap in connect.c TCP_Server_Info contains a preauth_sha_hash[] and a cryptkey[] array that might contain sensitive data. Thus free its memory with kfree_sensitive() to avoid that we are leaking this information to the heap. Signed-off-by: Thomas Huth Signed-off-by: Namjae Jeon Signed-off-by: Paulo Alcantara --- fs/smb/client/connect.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/smb/client/connect.c b/fs/smb/client/connect.c index f9764f65430e..bcd7f1ae99ba 100644 --- a/fs/smb/client/connect.c +++ b/fs/smb/client/connect.c @@ -1143,7 +1143,7 @@ clean_demultiplex_info(struct TCP_Server_Info *server) put_net(cifs_net_ns(server)); kfree(server->leaf_fullpath); kfree(server->hostname); - kfree(server); + kfree_sensitive(server); length = atomic_dec_return(&tcpSesAllocCount); if (length > 0) From b96db32fed8dfb2478d7c208f89bf383beed1535 Mon Sep 17 00:00:00 2001 From: Frank Sorenson Date: Thu, 6 Aug 2026 21:41:56 -0500 Subject: [PATCH 27/33] cifs: clear tcon after cifsFileInfo_put() in cifs_file_set_size() When the else branch of cifs_file_set_size() finds a writable file handle via find_writable_file(), it borrows tcon and server from the handle's tlink, attempts the handle-based set_file_size() RPC, and then releases the handle with cifsFileInfo_put(). If set_file_size() fails, execution falls through to the path-based fallback, which reuses the borrowed tcon and server under the "if (tcon == NULL)" guard. Since tcon is not NULL at that point, the guard is skipped. If cifsFileInfo_put() dropped the last reference on a tlink that was already removed from the tlink tree (TCON_LINK_IN_TREE cleared, as happens during reconnection or session teardown), cifs_put_tlink() will have freed tcon; the subsequent set_path_size() call is then a use-after-free. Setting tcon = NULL after cifsFileInfo_put() causes the existing guard to take the cifs_sb_tlink() path, which acquires a fresh reference for the path-based operation or fails cleanly if the session is gone. Fixes: 110fee6b9bb5 ("smb: client: fix missing timestamp updates with O_TRUNC") Cc: stable@vger.kernel.org Cc: Paulo Alcantara Signed-off-by: Frank Sorenson Signed-off-by: Paulo Alcantara --- fs/smb/client/inode.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/smb/client/inode.c b/fs/smb/client/inode.c index cb5515a50f74..d459b6f602ca 100644 --- a/fs/smb/client/inode.c +++ b/fs/smb/client/inode.c @@ -3118,6 +3118,7 @@ int cifs_file_set_size(const unsigned int xid, struct dentry *dentry, size, false); cifs_dbg(FYI, "%s: set_file_size: rc = %d\n", __func__, rc); cifsFileInfo_put(open_file); + tcon = NULL; } } From ba22f575de9deeae4ae0859ca4315a7698226237 Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Tue, 28 Jul 2026 13:06:13 -0500 Subject: [PATCH 28/33] smb: client: restore the data_offset bound in is_valid_oplock_break() Commit 83bfbd0bb902 ("cifs: Remove the RFC1002 header from smb_hdr") changed the quantity this bound is measured against. It used to be srv->total_read minus the 4-byte RFC1002 preamble that total_read then included, so it was the SMB message length. The same commit stopped counting the preamble, and the mechanical substitution to srv->total_read - srv->pdu_size left an expression that is identically zero: standard_receive3() reads MID_HEADER_SIZE() bytes and then exactly pdu_length - MID_HEADER_SIZE() more, adding both to total_read. len is therefore 0, the subtraction below it wraps, and no __u32 DataOffset can exceed the result, so the check from commit 097f5863b1a0 ("cifs: read overflow in is_valid_oplock_break()") no longer rejects anything. Use total_read, which is now the message length on its own. Fixes: 83bfbd0bb902 ("cifs: Remove the RFC1002 header from smb_hdr") Cc: stable@kernel.org Signed-off-by: Bryam Vargas Signed-off-by: Paulo Alcantara --- fs/smb/client/smb1misc.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/smb/client/smb1misc.c b/fs/smb/client/smb1misc.c index ba56023010d8..cdfbbff24b72 100644 --- a/fs/smb/client/smb1misc.c +++ b/fs/smb/client/smb1misc.c @@ -80,7 +80,8 @@ is_valid_oplock_break(char *buffer, struct TCP_Server_Info *srv) (struct smb_com_transaction_change_notify_rsp *)buf; struct file_notify_information *pnotify; __u32 data_offset = 0; - size_t len = srv->total_read - srv->pdu_size; + /* total_read excludes the RFC1002 preamble */ + size_t len = srv->total_read; if (get_bcc(buf) > sizeof(struct file_notify_information)) { data_offset = le32_to_cpu(pSMBr->DataOffset); From 019716ca2633998c0e9c57df08b4e34d384d4b58 Mon Sep 17 00:00:00 2001 From: Mohammad Shahid Date: Fri, 3 Jul 2026 17:51:04 +0530 Subject: [PATCH 29/33] smb: client: remove redundant NULL check before kfree() kfree() safely handles NULL pointers, so the explicit NULL check before calling kfree() is unnecessary. This issue was reported by ifnullfree.cocci. Signed-off-by: Mohammad Shahid Signed-off-by: Paulo Alcantara --- fs/smb/client/ioctl.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/fs/smb/client/ioctl.c b/fs/smb/client/ioctl.c index 9fa743be3652..2f152d76be7d 100644 --- a/fs/smb/client/ioctl.c +++ b/fs/smb/client/ioctl.c @@ -133,8 +133,7 @@ static int cifs_set_compression_by_path(unsigned int xid, struct file *filep, close: server->ops->close(xid, tcon, &fid); - if (tmp_cfile) - kfree(tmp_cfile); + kfree(tmp_cfile); cifs_free_open_info(&data); out: free_dentry_path(page); From 5d14030b46af1a958fd104b020fbb93631c98822 Mon Sep 17 00:00:00 2001 From: Frank Sorenson Date: Thu, 20 Aug 2026 16:22:10 -0500 Subject: [PATCH 30/33] smb: client: fix copy-paste error in WSL EA length accounting for $LXDEV The LXDEV block in cifs_query_path_info() uses SMB2_WSL_XATTR_MODE_SIZE (4) instead of SMB2_WSL_XATTR_DEV_SIZE (8), undercounting eas_len by 4 bytes per $LXDEV EA. eas_len is used only as a zero/non-zero presence flag so there is no current functional impact, but the value is incorrect and misleading. Fixes: 97db41604555 ("smb: client: parse uid, gid, mode and dev from WSL reparse points") Cc: stable@vger.kernel.org Cc: Paulo Alcantara Signed-off-by: Frank Sorenson Acked-by: Namjae Jeon Signed-off-by: Paulo Alcantara --- fs/smb/client/smb1ops.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/smb/client/smb1ops.c b/fs/smb/client/smb1ops.c index 7e2b29060f51..3ac4126267f6 100644 --- a/fs/smb/client/smb1ops.c +++ b/fs/smb/client/smb1ops.c @@ -721,7 +721,7 @@ static int cifs_query_path_info(const unsigned int xid, ea->ea_value_length = cpu_to_le16(SMB2_WSL_XATTR_DEV_SIZE); memcpy(&ea->ea_data[0], SMB2_WSL_XATTR_DEV, SMB2_WSL_XATTR_NAME_LEN + 1); data->wsl.eas_len += ALIGN(sizeof(*ea) + SMB2_WSL_XATTR_NAME_LEN + 1 + - SMB2_WSL_XATTR_MODE_SIZE, 4); + SMB2_WSL_XATTR_DEV_SIZE, 4); rc = 0; } else if (rc >= 0) { /* It is an error if EA $LXDEV has wrong size. */ From c510edb9734af1c274d18f4f31a471a166bbc7e8 Mon Sep 17 00:00:00 2001 From: Frank Sorenson Date: Thu, 20 Aug 2026 16:13:10 -0500 Subject: [PATCH 31/33] cifs: call pagecache_isize_extended() in cifs_setsize() when extending cifs_setsize() calls truncate_pagecache() but skips pagecache_isize_extended() on extension. truncate_setsize() shows the correct pattern: i_size_write(inode, newsize); if (newsize > oldsize) pagecache_isize_extended(inode, oldsize, newsize); truncate_pagecache(inode, newsize); pagecache_isize_extended() zeroes the tail of the page straddling old EOF. Without it, dirty bytes in that region can be written back to the server, exposing stale data in the newly extended range. Cc: stable@vger.kernel.org Cc: David Howells Signed-off-by: Frank Sorenson Acked-by: Namjae Jeon Signed-off-by: Paulo Alcantara --- fs/smb/client/inode.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/smb/client/inode.c b/fs/smb/client/inode.c index d459b6f602ca..a5aa1ae23c5f 100644 --- a/fs/smb/client/inode.c +++ b/fs/smb/client/inode.c @@ -3059,6 +3059,8 @@ void cifs_setsize(struct inode *inode, loff_t offset) inode->i_blocks = blocks; spin_unlock(&inode->i_lock); inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode)); + if (offset > old_size) + pagecache_isize_extended(inode, old_size, offset); truncate_pagecache(inode, offset); netfs_wait_for_outstanding_io(inode); } From 65deb18359341141d37dc86fc7853511be3c87a7 Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Fri, 21 Aug 2026 07:36:16 -0500 Subject: [PATCH 32/33] smb: client: reject a tree connect response whose byte count is too small CIFSTCon() bounds its strnlen() over the byte area with the server's ByteCount minus two, which for ByteCount 0 or 1 goes negative as an int and converts to a huge size_t. The later subtraction wraps the __u16 bytes_left, and that is what bounds cifs_strndup_from_utf16(): a bound of up to 65535 against a ~16 KB cifs_req_poolp object runs off the end of the slab object, and the bytes reach userspace through tcon->nativeFileSystem in /proc/fs/cifs/DebugData. Reject a byte area too small for what the parser consumes. Two bytes is the least it can consume, and no conformant response carries fewer. The new trace point is the 129th smb_eio_trace entry, which __mode(byte) cannot represent, so the attribute goes with it. Fixes: cc20c031bb06 ("cifs: convert CIFSTCon to use new unicode helper functions") Cc: stable@vger.kernel.org Signed-off-by: Bryam Vargas Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara --- fs/smb/client/cifssmb.c | 6 ++++++ fs/smb/client/trace.h | 3 ++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/fs/smb/client/cifssmb.c b/fs/smb/client/cifssmb.c index 1f77512252e7..f5aad5f61dce 100644 --- a/fs/smb/client/cifssmb.c +++ b/fs/smb/client/cifssmb.c @@ -615,6 +615,11 @@ CIFSTCon(const unsigned int xid, struct cifs_ses *ses, tcon->tid = smb_buffer_response->Tid; bcc_ptr = pByteArea(smb_buffer_response); bytes_left = get_bcc(smb_buffer_response); + if (bytes_left < 2) { + rc = smb_EIO2(smb_eio_trace_tcon_bcc_too_small, + bytes_left, 2); + goto out; + } length = strnlen(bcc_ptr, bytes_left - 2); if (smb_buffer->Flags2 & SMBFLG2_UNICODE) is_unicode = true; @@ -670,6 +675,7 @@ CIFSTCon(const unsigned int xid, struct cifs_ses *ses, reset_cifs_unix_caps(xid, tcon, NULL, NULL); } } +out: cifs_buf_release(smb_buffer); return rc; } diff --git a/fs/smb/client/trace.h b/fs/smb/client/trace.h index 5b21ad3c15fb..12241abb8e2e 100644 --- a/fs/smb/client/trace.h +++ b/fs/smb/client/trace.h @@ -133,6 +133,7 @@ EM(smb_eio_trace_sym_slash, "sym_slash") \ EM(smb_eio_trace_sym_target_len, "sym_target_len") \ EM(smb_eio_trace_symlink_file_size, "symlink_file_size") \ + EM(smb_eio_trace_tcon_bcc_too_small, "tcon_bcc_too_small") \ EM(smb_eio_trace_tdis_in_reconnect, "tdis_in_reconnect") \ EM(smb_eio_trace_tx_chained_async, "tx_chained_async") \ EM(smb_eio_trace_tx_compress_failed, "tx_compress_failed") \ @@ -213,7 +214,7 @@ #define EM(a, b) a, #define E_(a, b) a -enum smb_eio_trace { smb_eio_traces } __mode(byte); +enum smb_eio_trace { smb_eio_traces }; enum smb3_rw_credits_trace { smb3_rw_credits_traces } __mode(byte); enum smb3_tcon_ref_trace { smb3_tcon_ref_traces } __mode(byte); From 6c322f5cf7476ded7a9a20f7be72462065a03c68 Mon Sep 17 00:00:00 2001 From: Frank Sorenson Date: Sat, 22 Aug 2026 16:55:17 -0500 Subject: [PATCH 33/33] cifs: fix loff_t underflow in cifs_remap_file_range() when len == 0 With len == 0 (clone to EOF), the effective length is computed as: len = src_inode->i_size - off; If off > i_size, this is a negative loff_t, corrupting the ByteCount in the FSCTL_DUPLICATE_EXTENTS_TO_FILE request and inverting the range in filemap_write_and_wait_range(). The existing off >= i_size check fires only after the ioctl has already been sent. Snapshot i_size_read() once for both the bounds check and the length calculation, eliminating the TOCTOU and 32-bit torn-read risk. Reject off > src_size with -EINVAL. Treat off == src_size as a no-op, consistent with __generic_remap_file_range_prep(). Fixes: 04b38d601239 ("vfs: pull btrfs clone API to vfs layer") Cc: stable@vger.kernel.org Signed-off-by: Frank Sorenson Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara --- fs/smb/client/cifsfs.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/fs/smb/client/cifsfs.c b/fs/smb/client/cifsfs.c index 2073b29ff969..7ecd70efdfea 100644 --- a/fs/smb/client/cifsfs.c +++ b/fs/smb/client/cifsfs.c @@ -1413,8 +1413,19 @@ static loff_t cifs_remap_file_range(struct file *src_file, loff_t off, */ lock_two_nondirectories(target_inode, src_inode); - if (len == 0) - len = src_inode->i_size - off; + if (len == 0) { + loff_t src_size = i_size_read(src_inode); + + if (off > src_size) { + rc = -EINVAL; + goto unlock; + } + len = src_size - off; + if (!len) { + rc = 0; + goto unlock; + } + } cifs_dbg(FYI, "clone range\n");