From 02696ac0906540715dd4a0bb86b1117a1d00da4e Mon Sep 17 00:00:00 2001 From: Henrique Carvalho Date: Tue, 23 Sep 2025 13:20:48 -0300 Subject: [PATCH 01/16] smb: client: add tcon information to smb2_reconnect() debug messages smb2_reconnect() debug messages lack tcon context, making it hard to identify which tcon is reconnecting in multi-share environments. Change cifs_dbg() to cifs_tcon_dbg() to include tcon information. Closes: https://bugzilla.suse.com/show_bug.cgi?id=1234066 Signed-off-by: Henrique Carvalho Signed-off-by: Steve French --- fs/smb/client/smb2pdu.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/fs/smb/client/smb2pdu.c b/fs/smb/client/smb2pdu.c index 1c63d2c9cc9c..42e2d4ea344d 100644 --- a/fs/smb/client/smb2pdu.c +++ b/fs/smb/client/smb2pdu.c @@ -240,8 +240,8 @@ smb2_reconnect(__le16 smb2_command, struct cifs_tcon *tcon, */ if (smb2_command != SMB2_TREE_DISCONNECT) { spin_unlock(&tcon->tc_lock); - cifs_dbg(FYI, "can not send cmd %d while umounting\n", - smb2_command); + cifs_tcon_dbg(FYI, "can not send cmd %d while umounting\n", + smb2_command); return -ENODEV; } } @@ -296,9 +296,9 @@ smb2_reconnect(__le16 smb2_command, struct cifs_tcon *tcon, return 0; } spin_unlock(&ses->chan_lock); - cifs_dbg(FYI, "sess reconnect mask: 0x%lx, tcon reconnect: %d", - tcon->ses->chans_need_reconnect, - tcon->need_reconnect); + cifs_tcon_dbg(FYI, "sess reconnect mask: 0x%lx, tcon reconnect: %d\n", + tcon->ses->chans_need_reconnect, + tcon->need_reconnect); mutex_lock(&ses->session_mutex); /* @@ -392,11 +392,11 @@ smb2_reconnect(__le16 smb2_command, struct cifs_tcon *tcon, rc = cifs_tree_connect(0, tcon); - cifs_dbg(FYI, "reconnect tcon rc = %d\n", rc); + cifs_tcon_dbg(FYI, "reconnect tcon rc = %d\n", rc); if (rc) { /* If sess reconnected but tcon didn't, something strange ... */ mutex_unlock(&ses->session_mutex); - cifs_dbg(VFS, "reconnect tcon failed rc = %d\n", rc); + cifs_tcon_dbg(VFS, "reconnect tcon failed rc = %d\n", rc); goto out; } @@ -442,8 +442,8 @@ smb2_reconnect(__le16 smb2_command, struct cifs_tcon *tcon, from_reconnect); goto skip_add_channels; } else if (rc) - cifs_dbg(FYI, "%s: failed to query server interfaces: %d\n", - __func__, rc); + cifs_tcon_dbg(FYI, "%s: failed to query server interfaces: %d\n", + __func__, rc); if (ses->chan_max > ses->chan_count && ses->iface_count && From 8e979aab34d766986f6d14f50cebf5f5dd87ab8e Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Sat, 6 Sep 2025 20:20:03 -0700 Subject: [PATCH 02/16] smb: Use arc4 library instead of duplicate arc4 code fs/smb/common/cifs_arc4.c has an implementation of ARC4, but a copy of this same code is also present in lib/crypto/arc4.c to serve the other users of this legacy algorithm in the kernel. Remove the duplicate implementation in fs/smb/, which seems to have been added because of a misunderstanding, and just use the lib/crypto/ one. Signed-off-by: Eric Biggers Reviewed-by: Paulo Alcantara (Red Hat) Signed-off-by: Steve French --- fs/smb/client/Kconfig | 1 + fs/smb/client/cifsencrypt.c | 8 ++-- fs/smb/common/Makefile | 1 - fs/smb/common/arc4.h | 23 ------------ fs/smb/common/cifs_arc4.c | 75 ------------------------------------- fs/smb/server/Kconfig | 1 + fs/smb/server/auth.c | 9 ++--- 7 files changed, 10 insertions(+), 108 deletions(-) delete mode 100644 fs/smb/common/arc4.h delete mode 100644 fs/smb/common/cifs_arc4.c diff --git a/fs/smb/client/Kconfig b/fs/smb/client/Kconfig index 9f05f94e265a..a4c02199fef4 100644 --- a/fs/smb/client/Kconfig +++ b/fs/smb/client/Kconfig @@ -15,6 +15,7 @@ config CIFS select CRYPTO_GCM select CRYPTO_ECB select CRYPTO_AES + select CRYPTO_LIB_ARC4 select KEYS select DNS_RESOLVER select ASN1 diff --git a/fs/smb/client/cifsencrypt.c b/fs/smb/client/cifsencrypt.c index 3cc686246908..7b7c8c38fdd0 100644 --- a/fs/smb/client/cifsencrypt.c +++ b/fs/smb/client/cifsencrypt.c @@ -22,8 +22,8 @@ #include #include #include -#include "../common/arc4.h" #include +#include static size_t cifs_shash_step(void *iter_base, size_t progress, size_t len, void *priv, void *priv2) @@ -725,9 +725,9 @@ calc_seckey(struct cifs_ses *ses) return -ENOMEM; } - cifs_arc4_setkey(ctx_arc4, ses->auth_key.response, CIFS_SESS_KEY_SIZE); - cifs_arc4_crypt(ctx_arc4, ses->ntlmssp->ciphertext, sec_key, - CIFS_CPHTXT_SIZE); + arc4_setkey(ctx_arc4, ses->auth_key.response, CIFS_SESS_KEY_SIZE); + arc4_crypt(ctx_arc4, ses->ntlmssp->ciphertext, sec_key, + CIFS_CPHTXT_SIZE); /* make secondary_key/nonce as session key */ memcpy(ses->auth_key.response, sec_key, CIFS_SESS_KEY_SIZE); diff --git a/fs/smb/common/Makefile b/fs/smb/common/Makefile index c66dbbc1469c..9e0730a385fb 100644 --- a/fs/smb/common/Makefile +++ b/fs/smb/common/Makefile @@ -3,5 +3,4 @@ # Makefile for Linux filesystem routines that are shared by client and server. # -obj-$(CONFIG_SMBFS) += cifs_arc4.o obj-$(CONFIG_SMBFS) += cifs_md4.o diff --git a/fs/smb/common/arc4.h b/fs/smb/common/arc4.h deleted file mode 100644 index 12e71ec033a1..000000000000 --- a/fs/smb/common/arc4.h +++ /dev/null @@ -1,23 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0+ */ -/* - * Common values for ARC4 Cipher Algorithm - */ - -#ifndef _CRYPTO_ARC4_H -#define _CRYPTO_ARC4_H - -#include - -#define ARC4_MIN_KEY_SIZE 1 -#define ARC4_MAX_KEY_SIZE 256 -#define ARC4_BLOCK_SIZE 1 - -struct arc4_ctx { - u32 S[256]; - u32 x, y; -}; - -int cifs_arc4_setkey(struct arc4_ctx *ctx, const u8 *in_key, unsigned int key_len); -void cifs_arc4_crypt(struct arc4_ctx *ctx, u8 *out, const u8 *in, unsigned int len); - -#endif /* _CRYPTO_ARC4_H */ diff --git a/fs/smb/common/cifs_arc4.c b/fs/smb/common/cifs_arc4.c deleted file mode 100644 index df360ca47826..000000000000 --- a/fs/smb/common/cifs_arc4.c +++ /dev/null @@ -1,75 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Cryptographic API - * - * ARC4 Cipher Algorithm - * - * Jon Oberheide - */ - -#include -#include "arc4.h" - -MODULE_DESCRIPTION("ARC4 Cipher Algorithm"); -MODULE_LICENSE("GPL"); - -int cifs_arc4_setkey(struct arc4_ctx *ctx, const u8 *in_key, unsigned int key_len) -{ - int i, j = 0, k = 0; - - ctx->x = 1; - ctx->y = 0; - - for (i = 0; i < 256; i++) - ctx->S[i] = i; - - for (i = 0; i < 256; i++) { - u32 a = ctx->S[i]; - - j = (j + in_key[k] + a) & 0xff; - ctx->S[i] = ctx->S[j]; - ctx->S[j] = a; - if (++k >= key_len) - k = 0; - } - - return 0; -} -EXPORT_SYMBOL_GPL(cifs_arc4_setkey); - -void cifs_arc4_crypt(struct arc4_ctx *ctx, u8 *out, const u8 *in, unsigned int len) -{ - u32 *const S = ctx->S; - u32 x, y, a, b; - u32 ty, ta, tb; - - if (len == 0) - return; - - x = ctx->x; - y = ctx->y; - - a = S[x]; - y = (y + a) & 0xff; - b = S[y]; - - do { - S[y] = a; - a = (a + b) & 0xff; - S[x] = b; - x = (x + 1) & 0xff; - ta = S[x]; - ty = (y + ta) & 0xff; - tb = S[ty]; - *out++ = *in++ ^ S[a]; - if (--len == 0) - break; - y = ty; - a = ta; - b = tb; - } while (true); - - ctx->x = x; - ctx->y = y; -} -EXPORT_SYMBOL_GPL(cifs_arc4_crypt); diff --git a/fs/smb/server/Kconfig b/fs/smb/server/Kconfig index 4a23a5e7e8fe..098cac98d31e 100644 --- a/fs/smb/server/Kconfig +++ b/fs/smb/server/Kconfig @@ -10,6 +10,7 @@ config SMB_SERVER select CRYPTO_MD5 select CRYPTO_HMAC select CRYPTO_ECB + select CRYPTO_LIB_ARC4 select CRYPTO_LIB_DES select CRYPTO_LIB_SHA256 select CRYPTO_SHA256 diff --git a/fs/smb/server/auth.c b/fs/smb/server/auth.c index d99871c21451..b4020bb55a26 100644 --- a/fs/smb/server/auth.c +++ b/fs/smb/server/auth.c @@ -20,6 +20,7 @@ #include "glob.h" #include +#include #include #include "server.h" @@ -29,7 +30,6 @@ #include "mgmt/user_config.h" #include "crypto_ctx.h" #include "transport_ipc.h" -#include "../common/arc4.h" /* * Fixed format data defining GSS header and fixed string @@ -365,10 +365,9 @@ int ksmbd_decode_ntlmssp_auth_blob(struct authenticate_message *authblob, if (!ctx_arc4) return -ENOMEM; - cifs_arc4_setkey(ctx_arc4, sess->sess_key, - SMB2_NTLMV2_SESSKEY_SIZE); - cifs_arc4_crypt(ctx_arc4, sess->sess_key, - (char *)authblob + sess_key_off, sess_key_len); + arc4_setkey(ctx_arc4, sess->sess_key, SMB2_NTLMV2_SESSKEY_SIZE); + arc4_crypt(ctx_arc4, sess->sess_key, + (char *)authblob + sess_key_off, sess_key_len); kfree_sensitive(ctx_arc4); } From 998a67b954680f26f3734040aeeed08642d49721 Mon Sep 17 00:00:00 2001 From: Enzo Matsumiya Date: Thu, 25 Sep 2025 12:10:33 -0300 Subject: [PATCH 03/16] smb: client: fix crypto buffers in non-linear memory The crypto API, through the scatterlist API, expects input buffers to be in linear memory. We handle this with the cifs_sg_set_buf() helper that converts vmalloc'd memory to their corresponding pages. However, when we allocate our aead_request buffer (@creq in smb2ops.c::crypt_message()), we do so with kvzalloc(), which possibly puts aead_request->__ctx in vmalloc area. AEAD algorithm then uses ->__ctx for its private/internal data and operations, and uses sg_set_buf() for such data on a few places. This works fine as long as @creq falls into kmalloc zone (small requests) or vmalloc'd memory is still within linear range. Tasks' stacks are vmalloc'd by default (CONFIG_VMAP_STACK=y), so too many tasks will increment the base stacks' addresses to a point where virt_addr_valid(buf) will fail (BUG() in sg_set_buf()) when that happens. In practice: too many parallel reads and writes on an encrypted mount will trigger this bug. To fix this, always alloc @creq with kmalloc() instead. Also drop the @sensitive_size variable/arguments since kfree_sensitive() doesn't need it. Backtrace: [ 945.272081] ------------[ cut here ]------------ [ 945.272774] kernel BUG at include/linux/scatterlist.h:209! [ 945.273520] Oops: invalid opcode: 0000 [#1] SMP DEBUG_PAGEALLOC NOPTI [ 945.274412] CPU: 7 UID: 0 PID: 56 Comm: kworker/u33:0 Kdump: loaded Not tainted 6.15.0-lku-11779-g8e9d6efccdd7-dirty #1 PREEMPT(voluntary) [ 945.275736] Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS rel-1.16.3-2-gc13ff2cd-prebuilt.qemu.org 04/01/2014 [ 945.276877] Workqueue: writeback wb_workfn (flush-cifs-2) [ 945.277457] RIP: 0010:crypto_gcm_init_common+0x1f9/0x220 [ 945.278018] Code: b0 00 00 00 48 83 c4 08 5b 5d 41 5c 41 5d 41 5e 41 5f c3 cc cc cc cc 48 c7 c0 00 00 00 80 48 2b 05 5c 58 e5 00 e9 58 ff ff ff <0f> 0b 0f 0b 0f 0b 0f 0b 0f 0b 0f 0b 48 c7 04 24 01 00 00 00 48 8b [ 945.279992] RSP: 0018:ffffc90000a27360 EFLAGS: 00010246 [ 945.280578] RAX: 0000000000000000 RBX: ffffc90001d85060 RCX: 0000000000000030 [ 945.281376] RDX: 0000000000080000 RSI: 0000000000000000 RDI: ffffc90081d85070 [ 945.282145] RBP: ffffc90001d85010 R08: ffffc90001d85000 R09: 0000000000000000 [ 945.282898] R10: ffffc90001d85090 R11: 0000000000001000 R12: ffffc90001d85070 [ 945.283656] R13: ffff888113522948 R14: ffffc90001d85060 R15: ffffc90001d85010 [ 945.284407] FS: 0000000000000000(0000) GS:ffff8882e66cf000(0000) knlGS:0000000000000000 [ 945.285262] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [ 945.285884] CR2: 00007fa7ffdd31f4 CR3: 000000010540d000 CR4: 0000000000350ef0 [ 945.286683] Call Trace: [ 945.286952] [ 945.287184] ? crypt_message+0x33f/0xad0 [cifs] [ 945.287719] crypto_gcm_encrypt+0x36/0xe0 [ 945.288152] crypt_message+0x54a/0xad0 [cifs] [ 945.288724] smb3_init_transform_rq+0x277/0x300 [cifs] [ 945.289300] smb_send_rqst+0xa3/0x160 [cifs] [ 945.289944] cifs_call_async+0x178/0x340 [cifs] [ 945.290514] ? __pfx_smb2_writev_callback+0x10/0x10 [cifs] [ 945.291177] smb2_async_writev+0x3e3/0x670 [cifs] [ 945.291759] ? find_held_lock+0x32/0x90 [ 945.292212] ? netfs_advance_write+0xf2/0x310 [ 945.292723] netfs_advance_write+0xf2/0x310 [ 945.293210] netfs_write_folio+0x346/0xcc0 [ 945.293689] ? __pfx__raw_spin_unlock_irq+0x10/0x10 [ 945.294250] netfs_writepages+0x117/0x460 [ 945.294724] do_writepages+0xbe/0x170 [ 945.295152] ? find_held_lock+0x32/0x90 [ 945.295600] ? kvm_sched_clock_read+0x11/0x20 [ 945.296103] __writeback_single_inode+0x56/0x4b0 [ 945.296643] writeback_sb_inodes+0x229/0x550 [ 945.297140] __writeback_inodes_wb+0x4c/0xe0 [ 945.297642] wb_writeback+0x2f1/0x3f0 [ 945.298069] wb_workfn+0x300/0x490 [ 945.298472] process_one_work+0x1fe/0x590 [ 945.298949] worker_thread+0x1ce/0x3c0 [ 945.299397] ? __pfx_worker_thread+0x10/0x10 [ 945.299900] kthread+0x119/0x210 [ 945.300285] ? __pfx_kthread+0x10/0x10 [ 945.300729] ret_from_fork+0x119/0x1b0 [ 945.301163] ? __pfx_kthread+0x10/0x10 [ 945.301601] ret_from_fork_asm+0x1a/0x30 [ 945.302055] Fixes: d08089f649a0 ("cifs: Change the I/O paths to use an iterator rather than a page list") Signed-off-by: Enzo Matsumiya Signed-off-by: Steve French --- fs/smb/client/smb2ops.c | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c index 4711a23c5b38..12792ddc6b0c 100644 --- a/fs/smb/client/smb2ops.c +++ b/fs/smb/client/smb2ops.c @@ -4219,7 +4219,7 @@ fill_transform_hdr(struct smb2_transform_hdr *tr_hdr, unsigned int orig_len, static void *smb2_aead_req_alloc(struct crypto_aead *tfm, const struct smb_rqst *rqst, int num_rqst, const u8 *sig, u8 **iv, struct aead_request **req, struct sg_table *sgt, - unsigned int *num_sgs, size_t *sensitive_size) + unsigned int *num_sgs) { unsigned int req_size = sizeof(**req) + crypto_aead_reqsize(tfm); unsigned int iv_size = crypto_aead_ivsize(tfm); @@ -4236,9 +4236,8 @@ static void *smb2_aead_req_alloc(struct crypto_aead *tfm, const struct smb_rqst len += req_size; len = ALIGN(len, __alignof__(struct scatterlist)); len += array_size(*num_sgs, sizeof(struct scatterlist)); - *sensitive_size = len; - p = kvzalloc(len, GFP_NOFS); + p = kzalloc(len, GFP_NOFS); if (!p) return ERR_PTR(-ENOMEM); @@ -4252,16 +4251,14 @@ static void *smb2_aead_req_alloc(struct crypto_aead *tfm, const struct smb_rqst static void *smb2_get_aead_req(struct crypto_aead *tfm, struct smb_rqst *rqst, int num_rqst, const u8 *sig, u8 **iv, - struct aead_request **req, struct scatterlist **sgl, - size_t *sensitive_size) + struct aead_request **req, struct scatterlist **sgl) { struct sg_table sgtable = {}; unsigned int skip, num_sgs, i, j; ssize_t rc; void *p; - p = smb2_aead_req_alloc(tfm, rqst, num_rqst, sig, iv, req, &sgtable, - &num_sgs, sensitive_size); + p = smb2_aead_req_alloc(tfm, rqst, num_rqst, sig, iv, req, &sgtable, &num_sgs); if (IS_ERR(p)) return ERR_CAST(p); @@ -4350,7 +4347,6 @@ crypt_message(struct TCP_Server_Info *server, int num_rqst, DECLARE_CRYPTO_WAIT(wait); unsigned int crypt_len = le32_to_cpu(tr_hdr->OriginalMessageSize); void *creq; - size_t sensitive_size; rc = smb2_get_enc_key(server, le64_to_cpu(tr_hdr->SessionId), enc, key); if (rc) { @@ -4376,8 +4372,7 @@ crypt_message(struct TCP_Server_Info *server, int num_rqst, return rc; } - creq = smb2_get_aead_req(tfm, rqst, num_rqst, sign, &iv, &req, &sg, - &sensitive_size); + creq = smb2_get_aead_req(tfm, rqst, num_rqst, sign, &iv, &req, &sg); if (IS_ERR(creq)) return PTR_ERR(creq); @@ -4407,7 +4402,7 @@ crypt_message(struct TCP_Server_Info *server, int num_rqst, if (!rc && enc) memcpy(&tr_hdr->Signature, sign, SMB2_SIGNATURE_SIZE); - kvfree_sensitive(creq, sensitive_size); + kfree_sensitive(creq); return rc; } From ac3ad9845b9faf9cf29a736eaac00703bc821ac1 Mon Sep 17 00:00:00 2001 From: Bharath SM Date: Tue, 2 Sep 2025 16:46:03 +0530 Subject: [PATCH 04/16] smb: client: show lease state as R/H/W (or NONE) in open_files Print the lease/oplock caching state for each open file as a compact string of letters: R (read), H (handle), W (write). Signed-off-by: Bharath SM Signed-off-by: Steve French --- fs/smb/client/cifs_debug.c | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/fs/smb/client/cifs_debug.c b/fs/smb/client/cifs_debug.c index 35c4d27d2cc0..75f5f60f0bb8 100644 --- a/fs/smb/client/cifs_debug.c +++ b/fs/smb/client/cifs_debug.c @@ -240,14 +240,18 @@ static int cifs_debug_files_proc_show(struct seq_file *m, void *v) struct cifs_ses *ses; struct cifs_tcon *tcon; struct cifsFileInfo *cfile; + struct inode *inode; + struct cifsInodeInfo *cinode; + char lease[4]; + int n; seq_puts(m, "# Version:1\n"); seq_puts(m, "# Format:\n"); seq_puts(m, "# "); #ifdef CONFIG_CIFS_DEBUG2 - seq_printf(m, " \n"); + seq_puts(m, " \n"); #else - seq_printf(m, " \n"); + seq_puts(m, " \n"); #endif /* CIFS_DEBUG2 */ spin_lock(&cifs_tcp_ses_lock); list_for_each_entry(server, &cifs_tcp_ses_list, tcp_ses_list) { @@ -267,11 +271,30 @@ static int cifs_debug_files_proc_show(struct seq_file *m, void *v) cfile->pid, from_kuid(&init_user_ns, cfile->uid), cfile->dentry); + + /* Append lease/oplock caching state as RHW letters */ + inode = d_inode(cfile->dentry); + n = 0; + if (inode) { + cinode = CIFS_I(inode); + if (CIFS_CACHE_READ(cinode)) + lease[n++] = 'R'; + if (CIFS_CACHE_HANDLE(cinode)) + lease[n++] = 'H'; + if (CIFS_CACHE_WRITE(cinode)) + lease[n++] = 'W'; + } + lease[n] = '\0'; + seq_puts(m, " "); + if (n) + seq_printf(m, "%s", lease); + else + seq_puts(m, "NONE"); + #ifdef CONFIG_CIFS_DEBUG2 - seq_printf(m, " %llu\n", cfile->fid.mid); -#else + seq_printf(m, " %llu", cfile->fid.mid); +#endif /* CONFIG_CIFS_DEBUG2 */ seq_printf(m, "\n"); -#endif /* CIFS_DEBUG2 */ } spin_unlock(&tcon->open_file_lock); } From dde6667fa3c8f52ec4b8afd487749e47d032d833 Mon Sep 17 00:00:00 2001 From: Bharath SM Date: Tue, 2 Sep 2025 01:16:00 +0530 Subject: [PATCH 05/16] smb: client: add drop_dir_cache module parameter to invalidate cached dirents Add write-only /sys/module/cifs/parameters/drop_dir_cache. Writing a non-zero value iterates all tcons and calls invalidate_all_cached_dirs() to drop cached directory entries. This is useful to force a dirent cache drop across mounts for debugging and testing purpose. Signed-off-by: Bharath SM Signed-off-by: Steve French --- fs/smb/client/cifsfs.c | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/fs/smb/client/cifsfs.c b/fs/smb/client/cifsfs.c index dcb39d1b5958..9a83f528d39d 100644 --- a/fs/smb/client/cifsfs.c +++ b/fs/smb/client/cifsfs.c @@ -121,6 +121,44 @@ unsigned int dir_cache_timeout = 30; module_param(dir_cache_timeout, uint, 0644); MODULE_PARM_DESC(dir_cache_timeout, "Number of seconds to cache directory contents for which we have a lease. Default: 30 " "Range: 1 to 65000 seconds, 0 to disable caching dir contents"); + +/* + * Write-only module parameter to drop all cached directory entries across + * all CIFS mounts. Echo a non-zero value to trigger. + */ +static void cifs_drop_all_dir_caches(void) +{ + struct TCP_Server_Info *server; + struct cifs_ses *ses; + struct cifs_tcon *tcon; + + spin_lock(&cifs_tcp_ses_lock); + list_for_each_entry(server, &cifs_tcp_ses_list, tcp_ses_list) { + list_for_each_entry(ses, &server->smb_ses_list, smb_ses_list) { + if (cifs_ses_exiting(ses)) + continue; + list_for_each_entry(tcon, &ses->tcon_list, tcon_list) + invalidate_all_cached_dirs(tcon); + } + } + spin_unlock(&cifs_tcp_ses_lock); +} + +static int cifs_param_set_drop_dir_cache(const char *val, const struct kernel_param *kp) +{ + bool bv; + int rc = kstrtobool(val, &bv); + + if (rc) + return rc; + if (bv) + cifs_drop_all_dir_caches(); + return 0; +} + +module_param_call(drop_dir_cache, cifs_param_set_drop_dir_cache, NULL, NULL, 0200); +MODULE_PARM_DESC(drop_dir_cache, "Write 1 to drop all cached directory entries across all CIFS mounts"); + #ifdef CONFIG_CIFS_STATS2 unsigned int slow_rsp_threshold = 1; module_param(slow_rsp_threshold, uint, 0644); From 63eb8bd6c81d84a23fdc18fffd604e3ea38bb96c Mon Sep 17 00:00:00 2001 From: Bharath SM Date: Mon, 29 Sep 2025 15:57:46 -0500 Subject: [PATCH 06/16] smb: client: account smb directory cache usage and per-tcon totals Add lightweight accounting for directory lease cache usage to aid debugging and limiting cache size in future. Track per-directory entry/byte counts and maintain per-tcon aggregates. Also expose the totals in /proc/fs/cifs/open_dirs. Signed-off-by: Bharath SM Signed-off-by: Steve French --- fs/smb/client/cached_dir.c | 18 +++++++++++++++++ fs/smb/client/cached_dir.h | 9 +++++++++ fs/smb/client/cifs_debug.c | 8 +++++++- fs/smb/client/cifsfs.c | 2 ++ fs/smb/client/readdir.c | 40 +++++++++++++++++++++++++------------- 5 files changed, 63 insertions(+), 14 deletions(-) diff --git a/fs/smb/client/cached_dir.c b/fs/smb/client/cached_dir.c index b69daeb1301b..b6f538a1d5af 100644 --- a/fs/smb/client/cached_dir.c +++ b/fs/smb/client/cached_dir.c @@ -697,6 +697,21 @@ static void free_cached_dir(struct cached_fid *cfid) kfree(dirent); } + /* adjust tcon-level counters and reset per-dir accounting */ + if (cfid->cfids) { + if (cfid->dirents.entries_count) + atomic_long_sub((long)cfid->dirents.entries_count, + &cfid->cfids->total_dirents_entries); + if (cfid->dirents.bytes_used) { + atomic64_sub((long long)cfid->dirents.bytes_used, + &cfid->cfids->total_dirents_bytes); + atomic64_sub((long long)cfid->dirents.bytes_used, + &cifs_dircache_bytes_used); + } + } + cfid->dirents.entries_count = 0; + cfid->dirents.bytes_used = 0; + kfree(cfid->path); cfid->path = NULL; kfree(cfid); @@ -792,6 +807,9 @@ struct cached_fids *init_cached_dirs(void) queue_delayed_work(cfid_put_wq, &cfids->laundromat_work, dir_cache_timeout * HZ); + atomic_long_set(&cfids->total_dirents_entries, 0); + atomic64_set(&cfids->total_dirents_bytes, 0); + return cfids; } diff --git a/fs/smb/client/cached_dir.h b/fs/smb/client/cached_dir.h index 46b5a2fdf15b..c98f02943311 100644 --- a/fs/smb/client/cached_dir.h +++ b/fs/smb/client/cached_dir.h @@ -27,6 +27,9 @@ struct cached_dirents { struct mutex de_mutex; loff_t pos; /* Expected ctx->pos */ struct list_head entries; + /* accounting for cached entries in this directory */ + unsigned long entries_count; + unsigned long bytes_used; }; struct cached_fid { @@ -62,8 +65,14 @@ struct cached_fids { struct list_head dying; struct work_struct invalidation_work; struct delayed_work laundromat_work; + /* aggregate accounting for all cached dirents under this tcon */ + atomic_long_t total_dirents_entries; + atomic64_t total_dirents_bytes; }; +/* Module-wide directory cache accounting (defined in cifsfs.c) */ +extern atomic64_t cifs_dircache_bytes_used; /* bytes across all mounts */ + extern struct cached_fids *init_cached_dirs(void); extern void free_cached_dirs(struct cached_fids *cfids); extern int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, diff --git a/fs/smb/client/cifs_debug.c b/fs/smb/client/cifs_debug.c index 75f5f60f0bb8..1fb71d2d31b5 100644 --- a/fs/smb/client/cifs_debug.c +++ b/fs/smb/client/cifs_debug.c @@ -331,7 +331,10 @@ static int cifs_debug_dirs_proc_show(struct seq_file *m, void *v) if (!cfids) continue; spin_lock(&cfids->cfid_list_lock); /* check lock ordering */ - seq_printf(m, "Num entries: %d\n", cfids->num_entries); + seq_printf(m, "Num entries: %d, cached_dirents: %lu entries, %llu bytes\n", + cfids->num_entries, + (unsigned long)atomic_long_read(&cfids->total_dirents_entries), + (unsigned long long)atomic64_read(&cfids->total_dirents_bytes)); list_for_each_entry(cfid, &cfids->entries, entry) { seq_printf(m, "0x%x 0x%llx 0x%llx %s", tcon->tid, @@ -342,6 +345,9 @@ static int cifs_debug_dirs_proc_show(struct seq_file *m, void *v) seq_printf(m, "\tvalid file info"); if (cfid->dirents.is_valid) seq_printf(m, ", valid dirents"); + if (!list_empty(&cfid->dirents.entries)) + seq_printf(m, ", dirents: %lu entries, %lu bytes", + cfid->dirents.entries_count, cfid->dirents.bytes_used); seq_printf(m, "\n"); } spin_unlock(&cfids->cfid_list_lock); diff --git a/fs/smb/client/cifsfs.c b/fs/smb/client/cifsfs.c index 9a83f528d39d..1775c2b7528f 100644 --- a/fs/smb/client/cifsfs.c +++ b/fs/smb/client/cifsfs.c @@ -121,6 +121,8 @@ unsigned int dir_cache_timeout = 30; module_param(dir_cache_timeout, uint, 0644); MODULE_PARM_DESC(dir_cache_timeout, "Number of seconds to cache directory contents for which we have a lease. Default: 30 " "Range: 1 to 65000 seconds, 0 to disable caching dir contents"); +/* Module-wide total cached dirents (in bytes) across all tcons */ +atomic64_t cifs_dircache_bytes_used = ATOMIC64_INIT(0); /* * Write-only module parameter to drop all cached directory entries across diff --git a/fs/smb/client/readdir.c b/fs/smb/client/readdir.c index 4e5460206397..f0ce26622a14 100644 --- a/fs/smb/client/readdir.c +++ b/fs/smb/client/readdir.c @@ -874,39 +874,42 @@ static void finished_cached_dirents_count(struct cached_dirents *cde, cde->is_valid = 1; } -static void add_cached_dirent(struct cached_dirents *cde, - struct dir_context *ctx, - const char *name, int namelen, - struct cifs_fattr *fattr, - struct file *file) +static bool add_cached_dirent(struct cached_dirents *cde, + struct dir_context *ctx, const char *name, + int namelen, struct cifs_fattr *fattr, + struct file *file) { struct cached_dirent *de; if (cde->file != file) - return; + return false; if (cde->is_valid || cde->is_failed) - return; + return false; if (ctx->pos != cde->pos) { cde->is_failed = 1; - return; + return false; } de = kzalloc(sizeof(*de), GFP_ATOMIC); if (de == NULL) { cde->is_failed = 1; - return; + return false; } de->namelen = namelen; de->name = kstrndup(name, namelen, GFP_ATOMIC); if (de->name == NULL) { kfree(de); cde->is_failed = 1; - return; + return false; } de->pos = ctx->pos; memcpy(&de->fattr, fattr, sizeof(struct cifs_fattr)); list_add_tail(&de->entry, &cde->entries); + /* update accounting */ + cde->entries_count++; + cde->bytes_used += sizeof(*de) + (size_t)namelen + 1; + return true; } static bool cifs_dir_emit(struct dir_context *ctx, @@ -915,7 +918,8 @@ static bool cifs_dir_emit(struct dir_context *ctx, struct cached_fid *cfid, struct file *file) { - bool rc; + size_t delta_bytes = 0; + bool rc, added = false; ino_t ino = cifs_uniqueid_to_ino_t(fattr->cf_uniqueid); rc = dir_emit(ctx, name, namelen, ino, fattr->cf_dtype); @@ -923,10 +927,20 @@ static bool cifs_dir_emit(struct dir_context *ctx, return rc; if (cfid) { + /* Cost of this entry */ + delta_bytes = sizeof(struct cached_dirent) + (size_t)namelen + 1; + mutex_lock(&cfid->dirents.de_mutex); - add_cached_dirent(&cfid->dirents, ctx, name, namelen, - fattr, file); + added = add_cached_dirent(&cfid->dirents, ctx, name, namelen, + fattr, file); mutex_unlock(&cfid->dirents.de_mutex); + + if (added) { + /* per-tcon then global for consistency with free path */ + atomic64_add((long long)delta_bytes, &cfid->cfids->total_dirents_bytes); + atomic_long_inc(&cfid->cfids->total_dirents_entries); + atomic64_add((long long)delta_bytes, &cifs_dircache_bytes_used); + } } return rc; From a365f2c049b3846640234bc25e4f8c46abea6c98 Mon Sep 17 00:00:00 2001 From: Steve French Date: Wed, 1 Oct 2025 21:49:59 -0500 Subject: [PATCH 07/16] smb: client: ensure open_cached_dir_by_dentry() only returns valid cfid open_cached_dir_by_dentry() was exposing an invalid cached directory to callers. The validity check outside the function was exclusively based on cfid->time. Add validity check before returning success and introduce is_valid_cached_dir() helper for consistent checks across the code. Signed-off-by: Henrique Carvalho Reviwed-by: Enzo Matsumiya Signed-off-by: Steve French --- fs/smb/client/cached_dir.c | 9 +++++---- fs/smb/client/cached_dir.h | 6 ++++++ fs/smb/client/dir.c | 2 +- fs/smb/client/inode.c | 2 +- 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/fs/smb/client/cached_dir.c b/fs/smb/client/cached_dir.c index b6f538a1d5af..d714b7ba99ec 100644 --- a/fs/smb/client/cached_dir.c +++ b/fs/smb/client/cached_dir.c @@ -36,9 +36,8 @@ static struct cached_fid *find_or_create_cached_dir(struct cached_fids *cfids, * fully cached or it may be in the process of * being deleted due to a lease break. */ - if (!cfid->time || !cfid->has_lease) { + if (!is_valid_cached_dir(cfid)) return NULL; - } kref_get(&cfid->refcount); return cfid; } @@ -194,7 +193,7 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, * Otherwise, it is either a new entry or laundromat worker removed it * from @cfids->entries. Caller will put last reference if the latter. */ - if (cfid->has_lease && cfid->time) { + if (is_valid_cached_dir(cfid)) { cfid->last_access_time = jiffies; spin_unlock(&cfids->cfid_list_lock); *ret_cfid = cfid; @@ -233,7 +232,7 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, list_for_each_entry(parent_cfid, &cfids->entries, entry) { if (parent_cfid->dentry == dentry->d_parent) { cifs_dbg(FYI, "found a parent cached file handle\n"); - if (parent_cfid->has_lease && parent_cfid->time) { + if (is_valid_cached_dir(parent_cfid)) { lease_flags |= SMB2_LEASE_FLAG_PARENT_LEASE_KEY_SET_LE; memcpy(pfid->parent_lease_key, @@ -420,6 +419,8 @@ int open_cached_dir_by_dentry(struct cifs_tcon *tcon, spin_lock(&cfids->cfid_list_lock); list_for_each_entry(cfid, &cfids->entries, entry) { if (dentry && cfid->dentry == dentry) { + if (!is_valid_cached_dir(cfid)) + break; cifs_dbg(FYI, "found a cached file handle by dentry\n"); kref_get(&cfid->refcount); *ret_cfid = cfid; diff --git a/fs/smb/client/cached_dir.h b/fs/smb/client/cached_dir.h index c98f02943311..9210caf80164 100644 --- a/fs/smb/client/cached_dir.h +++ b/fs/smb/client/cached_dir.h @@ -73,6 +73,12 @@ struct cached_fids { /* Module-wide directory cache accounting (defined in cifsfs.c) */ extern atomic64_t cifs_dircache_bytes_used; /* bytes across all mounts */ +static inline bool +is_valid_cached_dir(struct cached_fid *cfid) +{ + return cfid->time && cfid->has_lease; +} + extern struct cached_fids *init_cached_dirs(void); extern void free_cached_dirs(struct cached_fids *cfids); extern int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, diff --git a/fs/smb/client/dir.c b/fs/smb/client/dir.c index 5223edf6d11a..56c59b67ecc2 100644 --- a/fs/smb/client/dir.c +++ b/fs/smb/client/dir.c @@ -322,7 +322,7 @@ static int cifs_do_create(struct inode *inode, struct dentry *direntry, unsigned list_for_each_entry(parent_cfid, &tcon->cfids->entries, entry) { if (parent_cfid->dentry == direntry->d_parent) { cifs_dbg(FYI, "found a parent cached file handle\n"); - if (parent_cfid->has_lease && parent_cfid->time) { + if (is_valid_cached_dir(parent_cfid)) { lease_flags |= SMB2_LEASE_FLAG_PARENT_LEASE_KEY_SET_LE; memcpy(fid->parent_lease_key, diff --git a/fs/smb/client/inode.c b/fs/smb/client/inode.c index 7e9784080501..8bb544be401e 100644 --- a/fs/smb/client/inode.c +++ b/fs/smb/client/inode.c @@ -2704,7 +2704,7 @@ cifs_dentry_needs_reval(struct dentry *dentry) return true; if (!open_cached_dir_by_dentry(tcon, dentry->d_parent, &cfid)) { - if (cfid->time && cifs_i->time > cfid->time) { + if (cifs_i->time > cfid->time) { close_cached_dir(cfid); return false; } From 5676398315b73f21d6a4e2d36606ce94e8afc79e Mon Sep 17 00:00:00 2001 From: Henrique Carvalho Date: Thu, 18 Sep 2025 22:41:58 -0300 Subject: [PATCH 08/16] smb: client: update cfid->last_access_time in open_cached_dir_by_dentry() open_cached_dir_by_dentry() was missing an update of cfid->last_access_time to jiffies, similar to what open_cached_dir() has. Add it to the function. Signed-off-by: Henrique Carvalho Reviewed-by: Enzo Matsumiya Signed-off-by: Steve French --- fs/smb/client/cached_dir.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/smb/client/cached_dir.c b/fs/smb/client/cached_dir.c index d714b7ba99ec..200a52220f2d 100644 --- a/fs/smb/client/cached_dir.c +++ b/fs/smb/client/cached_dir.c @@ -424,6 +424,7 @@ int open_cached_dir_by_dentry(struct cifs_tcon *tcon, cifs_dbg(FYI, "found a cached file handle by dentry\n"); kref_get(&cfid->refcount); *ret_cfid = cfid; + cfid->last_access_time = jiffies; spin_unlock(&cfids->cfid_list_lock); return 0; } From 17ef15fa80cf3b60b6f82ea1d88fa499d5495994 Mon Sep 17 00:00:00 2001 From: Henrique Carvalho Date: Mon, 2 Jun 2025 17:45:17 -0300 Subject: [PATCH 09/16] smb: client: remove unused fid_lock The fid_lock in struct cached_fid does not currently provide any real synchronization. Previously, it had the intention to prevent a double release of the dentry, but every change to cfid->dentry is already protected either by cfid_list_lock (while the entry is in the list) or happens after the cfid has been removed (so no other thread should find it). Since there is no scenario in which fid_lock prevents any race, it is vestigial and can be removed along with its associated spin_lock()/spin_unlock() calls. Signed-off-by: Henrique Carvalho Reviewed-by: Enzo Matsumiya Signed-off-by: Steve French --- fs/smb/client/cached_dir.c | 17 +++-------------- fs/smb/client/cached_dir.h | 1 - 2 files changed, 3 insertions(+), 15 deletions(-) diff --git a/fs/smb/client/cached_dir.c b/fs/smb/client/cached_dir.c index 200a52220f2d..03bfb919ad47 100644 --- a/fs/smb/client/cached_dir.c +++ b/fs/smb/client/cached_dir.c @@ -524,10 +524,9 @@ void close_all_cached_dirs(struct cifs_sb_info *cifs_sb) spin_unlock(&cifs_sb->tlink_tree_lock); goto done; } - spin_lock(&cfid->fid_lock); + tmp_list->dentry = cfid->dentry; cfid->dentry = NULL; - spin_unlock(&cfid->fid_lock); list_add_tail(&tmp_list->entry, &entry); } @@ -610,14 +609,9 @@ static void cached_dir_put_work(struct work_struct *work) { struct cached_fid *cfid = container_of(work, struct cached_fid, put_work); - struct dentry *dentry; - - spin_lock(&cfid->fid_lock); - dentry = cfid->dentry; + dput(cfid->dentry); cfid->dentry = NULL; - spin_unlock(&cfid->fid_lock); - dput(dentry); queue_work(serverclose_wq, &cfid->close_work); } @@ -675,7 +669,6 @@ static struct cached_fid *init_cached_dir(const char *path) INIT_LIST_HEAD(&cfid->entry); INIT_LIST_HEAD(&cfid->dirents.entries); mutex_init(&cfid->dirents.de_mutex); - spin_lock_init(&cfid->fid_lock); kref_init(&cfid->refcount); return cfid; } @@ -742,7 +735,6 @@ static void cfids_laundromat_worker(struct work_struct *work) { struct cached_fids *cfids; struct cached_fid *cfid, *q; - struct dentry *dentry; LIST_HEAD(entry); cfids = container_of(work, struct cached_fids, laundromat_work.work); @@ -769,12 +761,9 @@ static void cfids_laundromat_worker(struct work_struct *work) list_for_each_entry_safe(cfid, q, &entry, entry) { list_del(&cfid->entry); - spin_lock(&cfid->fid_lock); - dentry = cfid->dentry; + dput(cfid->dentry); cfid->dentry = NULL; - spin_unlock(&cfid->fid_lock); - dput(dentry); if (cfid->is_open) { spin_lock(&cifs_tcp_ses_lock); ++cfid->tcon->tc_count; diff --git a/fs/smb/client/cached_dir.h b/fs/smb/client/cached_dir.h index 9210caf80164..31339dc32719 100644 --- a/fs/smb/client/cached_dir.h +++ b/fs/smb/client/cached_dir.h @@ -44,7 +44,6 @@ struct cached_fid { unsigned long last_access_time; /* jiffies of when last accessed */ struct kref refcount; struct cifs_fid fid; - spinlock_t fid_lock; struct cifs_tcon *tcon; struct dentry *dentry; struct work_struct put_work; From 00be6f26a2a7c671f1402d74c4d3c30a5844660a Mon Sep 17 00:00:00 2001 From: Fiona Ebner Date: Mon, 15 Sep 2025 17:19:39 +0200 Subject: [PATCH 10/16] smb: client: transport: avoid reconnects triggered by pending task work When io_uring is used in the same task as CIFS, there might be unnecessary reconnects, causing issues in user-space applications like QEMU with a log like: > CIFS: VFS: \\10.10.100.81 Error -512 sending data on socket to server Certain io_uring completions might be added to task_work with notify_method being TWA_SIGNAL and thus TIF_NOTIFY_SIGNAL is set for the task. In __smb_send_rqst(), signals are masked before calling smb_send_kvec(), but the masking does not apply to TIF_NOTIFY_SIGNAL. If sk_stream_wait_memory() is reached via sock_sendmsg() while TIF_NOTIFY_SIGNAL is set, signal_pending(current) will evaluate to true there, and -EINTR will be propagated all the way from sk_stream_wait_memory() to sock_sendmsg() in smb_send_kvec(). Afterwards, __smb_send_rqst() will see that not everything was written and reconnect. Signed-off-by: Fiona Ebner Signed-off-by: Steve French --- fs/smb/client/transport.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/fs/smb/client/transport.c b/fs/smb/client/transport.c index a61ba7f3fb86..940e90107134 100644 --- a/fs/smb/client/transport.c +++ b/fs/smb/client/transport.c @@ -22,6 +22,7 @@ #include #include #include +#include #include "cifspdu.h" #include "cifsglob.h" #include "cifsproto.h" @@ -173,9 +174,16 @@ smb_send_kvec(struct TCP_Server_Info *server, struct msghdr *smb_msg, * send a packet. In most cases if we fail to send * after the retries we will kill the socket and * reconnect which may clear the network problem. + * + * Even if regular signals are masked, EINTR might be + * propagated from sk_stream_wait_memory() to here when + * TIF_NOTIFY_SIGNAL is used for task work. For example, + * certain io_uring completions will use that. Treat + * having EINTR with pending task work the same as EAGAIN + * to avoid unnecessary reconnects. */ rc = sock_sendmsg(ssocket, smb_msg); - if (rc == -EAGAIN) { + if (rc == -EAGAIN || unlikely(rc == -EINTR && task_work_pending(current))) { retries++; if (retries >= 14 || (!server->noblocksnd && (retries > 2))) { From 6c7fd184234336a3b998fdf8f8db51e970dd6071 Mon Sep 17 00:00:00 2001 From: Fiona Ebner Date: Mon, 15 Sep 2025 17:19:40 +0200 Subject: [PATCH 11/16] smb: client: transport: minor indentation style fix Signed-off-by: Fiona Ebner Signed-off-by: Steve French --- fs/smb/client/transport.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/fs/smb/client/transport.c b/fs/smb/client/transport.c index 940e90107134..051cd9dbba13 100644 --- a/fs/smb/client/transport.c +++ b/fs/smb/client/transport.c @@ -331,8 +331,7 @@ int __smb_send_rqst(struct TCP_Server_Info *server, int num_rqst, break; total_len += sent; } - -} + } unmask: sigprocmask(SIG_SETMASK, &oldmask, NULL); From 2f6a4af028dbb392d55b261cafcb922dd7b7ffea Mon Sep 17 00:00:00 2001 From: Henrique Carvalho Date: Thu, 18 Sep 2025 22:44:34 -0300 Subject: [PATCH 12/16] smb: client: remove pointless cfid->has_lease check open_cached_dir() will only return a valid cfid, which has both has_lease = true and time != 0. Remove the pointless check of cfid->has_lease right after open_cached_dir() returns no error. Signed-off-by: Henrique Carvalho Reviewed-by: Enzo Matsumiya Signed-off-by: Steve French --- fs/smb/client/smb2ops.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c index 12792ddc6b0c..058050f744c0 100644 --- a/fs/smb/client/smb2ops.c +++ b/fs/smb/client/smb2ops.c @@ -954,11 +954,8 @@ smb2_is_path_accessible(const unsigned int xid, struct cifs_tcon *tcon, rc = open_cached_dir(xid, tcon, full_path, cifs_sb, true, &cfid); if (!rc) { - if (cfid->has_lease) { - close_cached_dir(cfid); - return 0; - } close_cached_dir(cfid); + return 0; } utf16_path = cifs_convert_path_to_utf16(full_path, cifs_sb); From 55580ad027a6764b7b1ee75f537d67811a06307f Mon Sep 17 00:00:00 2001 From: Henrique Carvalho Date: Thu, 18 Sep 2025 18:39:51 -0300 Subject: [PATCH 13/16] smb: client: short-circuit in open_cached_dir_by_dentry() if !dentry When dentry is NULL, the current code acquires the spinlock and traverses the entire list, but the condition (dentry && cfid->dentry == dentry) ensures no match will ever be found. Return -ENOENT early in this case, avoiding unnecessary lock acquisition and list traversal. Signed-off-by: Henrique Carvalho Signed-off-by: Steve French --- fs/smb/client/cached_dir.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/fs/smb/client/cached_dir.c b/fs/smb/client/cached_dir.c index 03bfb919ad47..b36f9f9340f0 100644 --- a/fs/smb/client/cached_dir.c +++ b/fs/smb/client/cached_dir.c @@ -416,9 +416,12 @@ int open_cached_dir_by_dentry(struct cifs_tcon *tcon, if (cfids == NULL) return -EOPNOTSUPP; + if (!dentry) + return -ENOENT; + spin_lock(&cfids->cfid_list_lock); list_for_each_entry(cfid, &cfids->entries, entry) { - if (dentry && cfid->dentry == dentry) { + if (cfid->dentry == dentry) { if (!is_valid_cached_dir(cfid)) break; cifs_dbg(FYI, "found a cached file handle by dentry\n"); From 316025335a2d41dc71c47abf6eb9a41987e94c0a Mon Sep 17 00:00:00 2001 From: Henrique Carvalho Date: Mon, 8 Sep 2025 22:04:23 -0300 Subject: [PATCH 14/16] smb: client: short-circuit negative lookups when parent dir is fully cached When the parent directory has a valid and complete cached enumeration we can assume that negative dentries are not present in the directory, thus we can return without issuing a request. This reduces traffic for common ENOENT when the directory entries are cached. Signed-off-by: Henrique Carvalho Signed-off-by: Steve French --- fs/smb/client/dir.c | 43 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/fs/smb/client/dir.c b/fs/smb/client/dir.c index 56c59b67ecc2..bc145436eba4 100644 --- a/fs/smb/client/dir.c +++ b/fs/smb/client/dir.c @@ -683,6 +683,7 @@ cifs_lookup(struct inode *parent_dir_inode, struct dentry *direntry, const char *full_path; void *page; int retry_count = 0; + struct cached_fid *cfid = NULL; xid = get_xid(); @@ -722,6 +723,28 @@ cifs_lookup(struct inode *parent_dir_inode, struct dentry *direntry, cifs_dbg(FYI, "non-NULL inode in lookup\n"); } else { cifs_dbg(FYI, "NULL inode in lookup\n"); + + /* + * We can only rely on negative dentries having the same + * spelling as the cached dirent if case insensitivity is + * forced on mount. + * + * XXX: if servers correctly announce Case Sensitivity Search + * on GetInfo of FileFSAttributeInformation, then we can take + * correct action even if case insensitive is not forced on + * mount. + */ + if (pTcon->nocase && !open_cached_dir_by_dentry(pTcon, direntry->d_parent, &cfid)) { + /* + * dentry is negative and parent is fully cached: + * we can assume file does not exist + */ + if (cfid->dirents.is_valid) { + close_cached_dir(cfid); + goto out; + } + close_cached_dir(cfid); + } } cifs_dbg(FYI, "Full path: %s inode = 0x%p\n", full_path, d_inode(direntry)); @@ -755,6 +778,8 @@ cifs_lookup(struct inode *parent_dir_inode, struct dentry *direntry, } newInode = ERR_PTR(rc); } + +out: free_dentry_path(page); cifs_put_tlink(tlink); free_xid(xid); @@ -765,7 +790,8 @@ static int cifs_d_revalidate(struct inode *dir, const struct qstr *name, struct dentry *direntry, unsigned int flags) { - struct inode *inode; + struct inode *inode = NULL; + struct cached_fid *cfid; int rc; if (flags & LOOKUP_RCU) @@ -812,6 +838,21 @@ cifs_d_revalidate(struct inode *dir, const struct qstr *name, return 1; } + } else { + struct cifs_sb_info *cifs_sb = CIFS_SB(dir->i_sb); + struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb); + + if (!open_cached_dir_by_dentry(tcon, direntry->d_parent, &cfid)) { + /* + * dentry is negative and parent is fully cached: + * we can assume file does not exist + */ + if (cfid->dirents.is_valid) { + close_cached_dir(cfid); + return 1; + } + close_cached_dir(cfid); + } } /* From aa12118dbcfe659697567c9daa0eac2c71e3fd37 Mon Sep 17 00:00:00 2001 From: Bharath SM Date: Fri, 26 Sep 2025 10:13:50 -0500 Subject: [PATCH 15/16] smb client: fix bug with newly created file in cached dir Test generic/637 spotted a problem with create of a new file in a cached directory (by the same client) could cause cases where the new file does not show up properly in ls on that client until the lease times out. Fixes: 037e1bae588e ("smb: client: use ParentLeaseKey in cifs_do_create") Cc: stable@vger.kernel.org Signed-off-by: Bharath SM Signed-off-by: Steve French --- fs/smb/client/dir.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/smb/client/dir.c b/fs/smb/client/dir.c index bc145436eba4..a233a5fe377b 100644 --- a/fs/smb/client/dir.c +++ b/fs/smb/client/dir.c @@ -329,6 +329,7 @@ static int cifs_do_create(struct inode *inode, struct dentry *direntry, unsigned parent_cfid->fid.lease_key, SMB2_LEASE_KEY_SIZE); parent_cfid->dirents.is_valid = false; + parent_cfid->dirents.is_failed = true; } break; } From 37e263e68c5e27d5f1fbc1377f64f9373dc5bf15 Mon Sep 17 00:00:00 2001 From: Rajasi Mandal Date: Mon, 22 Sep 2025 08:24:16 +0000 Subject: [PATCH 16/16] cifs: client: force multichannel=off when max_channels=1 Previously, specifying both multichannel and max_channels=1 as mount options would leave multichannel enabled, even though it is not meaningful when only one channel is allowed. This led to confusion and inconsistent behavior, as the client would advertise multichannel capability but never establish secondary channels. Fix this by forcing multichannel to false whenever max_channels=1, ensuring the mount configuration is consistent and matches user intent. This prevents the client from advertising or attempting multichannel support when it is not possible. Signed-off-by: Rajasi Mandal Signed-off-by: Steve French --- fs/smb/client/fs_context.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/fs/smb/client/fs_context.c b/fs/smb/client/fs_context.c index 072383899e81..43552b44f613 100644 --- a/fs/smb/client/fs_context.c +++ b/fs/smb/client/fs_context.c @@ -1820,6 +1820,13 @@ static int smb3_fs_context_parse_param(struct fs_context *fc, goto cifs_parse_mount_err; } + /* + * Multichannel is not meaningful if max_channels is 1. + * Force multichannel to false to ensure consistent configuration. + */ + if (ctx->multichannel && ctx->max_channels == 1) + ctx->multichannel = false; + return 0; cifs_parse_mount_err: