From 72f4d48034864b93700d1d23fc418d90fa28d7ae Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Wed, 11 Feb 2026 01:10:07 -0300 Subject: [PATCH 01/14] smb: client: fix regression with mount options parsing After commit 1ef15fbe6771 ("cifs: client: enforce consistent handling of multichannel and max_channels"), invalid mount options started to be ignored, allowing cifs.ko to proceed with the mount instead of baling out. The problem was related to smb3_handle_conflicting_options() being called even when an invalid parameter had been parsed, overwriting the return value of vfs_parse_fs_string() in smb3_fs_context_parse_monolithic(). Fix this by calling smb3_handle_conflicting_options() only when a valid mount option has been passed. Reproducer: $ mount.cifs //srv/share /mnt -o ${opts} $ mount -o remount,foo,${opts} /mnt # must fail Fixes: 1ef15fbe6771 ("cifs: client: enforce consistent handling of multichannel and max_channels") Reported-by: Xiaoli Feng Signed-off-by: Paulo Alcantara (Red Hat) Cc: David Howells Cc: linux-cifs@vger.kernel.org Signed-off-by: Steve French --- fs/smb/client/fs_context.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/fs/smb/client/fs_context.c b/fs/smb/client/fs_context.c index ec84204aee18..412c5b534791 100644 --- a/fs/smb/client/fs_context.c +++ b/fs/smb/client/fs_context.c @@ -825,9 +825,7 @@ static int smb3_fs_context_parse_monolithic(struct fs_context *fc, if (ret < 0) break; } - ret = smb3_handle_conflicting_options(fc); - - return ret; + return ret ?: smb3_handle_conflicting_options(fc); } /* From 66dc58bdbd7c1897d87c06fee6e0f176c85190a0 Mon Sep 17 00:00:00 2001 From: Chen Ni Date: Wed, 11 Feb 2026 12:46:44 +0800 Subject: [PATCH 02/14] cifs: SMB1 split: Remove duplicate include of cifs_debug.h Remove duplicate inclusion of cifs_debug.h in smb1transport.c to clean up redundant code. Signed-off-by: Chen Ni Signed-off-by: Steve French --- fs/smb/client/smb1transport.c | 1 - 1 file changed, 1 deletion(-) diff --git a/fs/smb/client/smb1transport.c b/fs/smb/client/smb1transport.c index 0b8b852cfc0d..93731b00ca5d 100644 --- a/fs/smb/client/smb1transport.c +++ b/fs/smb/client/smb1transport.c @@ -29,7 +29,6 @@ #include "cifs_debug.h" #include "smbdirect.h" #include "compress.h" -#include "cifs_debug.h" /* Max number of iovectors we can use off the stack when sending requests. */ #define CIFS_MAX_IOV_SIZE 8 From ba39063ca3ee885af62bdde2c184b8295bf4bf12 Mon Sep 17 00:00:00 2001 From: ChenXiaoSong Date: Thu, 22 Jan 2026 13:23:56 +0800 Subject: [PATCH 03/14] smb/client: map NT_STATUS_NOTIFY_ENUM_DIR See MS-CIFS 2.2.2.4 STATUS_NOTIFY_ENUM_DIR. Signed-off-by: ChenXiaoSong Signed-off-by: Steve French --- fs/smb/client/nterr.c | 1 + fs/smb/client/nterr.h | 2 +- fs/smb/client/smb1maperror.c | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/fs/smb/client/nterr.c b/fs/smb/client/nterr.c index ab5a2119efd0..806ad6d7c22a 100644 --- a/fs/smb/client/nterr.c +++ b/fs/smb/client/nterr.c @@ -14,6 +14,7 @@ const struct nt_err_code_struct nt_errs[] = { {"NT_STATUS_OK", NT_STATUS_OK}, {"NT_STATUS_PENDING", NT_STATUS_PENDING}, + {"NT_STATUS_NOTIFY_ENUM_DIR", NT_STATUS_NOTIFY_ENUM_DIR}, {"NT_STATUS_MEDIA_CHANGED", NT_STATUS_MEDIA_CHANGED}, {"NT_STATUS_END_OF_MEDIA", NT_STATUS_END_OF_MEDIA}, {"NT_STATUS_MEDIA_CHECK", NT_STATUS_MEDIA_CHECK}, diff --git a/fs/smb/client/nterr.h b/fs/smb/client/nterr.h index b04cd0d786b1..e8306704f5e3 100644 --- a/fs/smb/client/nterr.h +++ b/fs/smb/client/nterr.h @@ -27,7 +27,6 @@ extern const struct nt_err_code_struct nt_errs[]; #define NT_ERROR_INVALID_PARAMETER 0x0057 #define NT_ERROR_INSUFFICIENT_BUFFER 0x007a #define NT_STATUS_1804 0x070c -#define NT_STATUS_NOTIFY_ENUM_DIR 0x010c /* * Win32 Error codes extracted using a loop in smbclient then printing a netmon @@ -37,6 +36,7 @@ extern const struct nt_err_code_struct nt_errs[]; #define NT_STATUS_OK 0x0000 #define NT_STATUS_PENDING 0x0103 #define NT_STATUS_SOME_UNMAPPED 0x0107 +#define NT_STATUS_NOTIFY_ENUM_DIR 0x010c #define NT_STATUS_BUFFER_OVERFLOW 0x80000005 #define NT_STATUS_NO_MORE_ENTRIES 0x8000001a #define NT_STATUS_MEDIA_CHANGED 0x8000001c diff --git a/fs/smb/client/smb1maperror.c b/fs/smb/client/smb1maperror.c index 277ef0865be0..ae13367e3782 100644 --- a/fs/smb/client/smb1maperror.c +++ b/fs/smb/client/smb1maperror.c @@ -112,6 +112,7 @@ static const struct { __u32 ntstatus; } ntstatus_to_dos_map[] = { { + ERRSRV, ERR_NOTIFY_ENUM_DIR, NT_STATUS_NOTIFY_ENUM_DIR}, { ERRDOS, ERRgeneral, NT_STATUS_UNSUCCESSFUL}, { ERRDOS, ERRbadfunc, NT_STATUS_NOT_IMPLEMENTED}, { ERRDOS, ERRbadpipe, NT_STATUS_INVALID_INFO_CLASS}, { From e4424687fc6d866c7e0dca979d9d362ec8cbc71a Mon Sep 17 00:00:00 2001 From: ChenXiaoSong Date: Thu, 22 Jan 2026 13:23:57 +0800 Subject: [PATCH 04/14] smb/client: map NT_STATUS_BUFFER_OVERFLOW See MS-CIFS 2.2.2.4 STATUS_BUFFER_OVERFLOW. Signed-off-by: ChenXiaoSong Signed-off-by: Steve French --- fs/smb/client/smb1maperror.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/smb/client/smb1maperror.c b/fs/smb/client/smb1maperror.c index ae13367e3782..c6853b447dbd 100644 --- a/fs/smb/client/smb1maperror.c +++ b/fs/smb/client/smb1maperror.c @@ -113,6 +113,7 @@ static const struct { } ntstatus_to_dos_map[] = { { ERRSRV, ERR_NOTIFY_ENUM_DIR, NT_STATUS_NOTIFY_ENUM_DIR}, { + ERRDOS, ERRmoredata, NT_STATUS_BUFFER_OVERFLOW}, { ERRDOS, ERRgeneral, NT_STATUS_UNSUCCESSFUL}, { ERRDOS, ERRbadfunc, NT_STATUS_NOT_IMPLEMENTED}, { ERRDOS, ERRbadpipe, NT_STATUS_INVALID_INFO_CLASS}, { From 3e5f083428607baea80bbbd7dfce6769c288efa3 Mon Sep 17 00:00:00 2001 From: ChenXiaoSong Date: Thu, 22 Jan 2026 13:23:58 +0800 Subject: [PATCH 05/14] smb/client: map NT_STATUS_MORE_PROCESSING_REQUIRED See MS-CIFS 2.2.2.4 STATUS_MORE_PROCESSING_REQUIRED. Signed-off-by: ChenXiaoSong Signed-off-by: Steve French --- fs/smb/client/smb1maperror.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/smb/client/smb1maperror.c b/fs/smb/client/smb1maperror.c index c6853b447dbd..2d439b5b25de 100644 --- a/fs/smb/client/smb1maperror.c +++ b/fs/smb/client/smb1maperror.c @@ -114,6 +114,7 @@ static const struct { { ERRSRV, ERR_NOTIFY_ENUM_DIR, NT_STATUS_NOTIFY_ENUM_DIR}, { ERRDOS, ERRmoredata, NT_STATUS_BUFFER_OVERFLOW}, { + ERRDOS, ERRmoredata, NT_STATUS_MORE_PROCESSING_REQUIRED}, { ERRDOS, ERRgeneral, NT_STATUS_UNSUCCESSFUL}, { ERRDOS, ERRbadfunc, NT_STATUS_NOT_IMPLEMENTED}, { ERRDOS, ERRbadpipe, NT_STATUS_INVALID_INFO_CLASS}, { From 4da735c48a27af32b444f34fcdc18441eb3f9b24 Mon Sep 17 00:00:00 2001 From: ChenXiaoSong Date: Thu, 22 Jan 2026 13:23:59 +0800 Subject: [PATCH 06/14] smb/client: map NT_STATUS_PRIVILEGE_NOT_HELD See MS-CIFS 2.2.2.4 STATUS_PRIVILEGE_NOT_HELD. Signed-off-by: ChenXiaoSong Signed-off-by: Steve French --- fs/smb/client/smb1maperror.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/smb/client/smb1maperror.c b/fs/smb/client/smb1maperror.c index 2d439b5b25de..e1964fd2b85e 100644 --- a/fs/smb/client/smb1maperror.c +++ b/fs/smb/client/smb1maperror.c @@ -115,6 +115,7 @@ static const struct { ERRSRV, ERR_NOTIFY_ENUM_DIR, NT_STATUS_NOTIFY_ENUM_DIR}, { ERRDOS, ERRmoredata, NT_STATUS_BUFFER_OVERFLOW}, { ERRDOS, ERRmoredata, NT_STATUS_MORE_PROCESSING_REQUIRED}, { + ERRDOS, ERRnoaccess, NT_STATUS_PRIVILEGE_NOT_HELD}, { ERRDOS, ERRgeneral, NT_STATUS_UNSUCCESSFUL}, { ERRDOS, ERRbadfunc, NT_STATUS_NOT_IMPLEMENTED}, { ERRDOS, ERRbadpipe, NT_STATUS_INVALID_INFO_CLASS}, { From fa34d0a5703367b1979afdf6c08ddd2cb8a4b896 Mon Sep 17 00:00:00 2001 From: ChenXiaoSong Date: Thu, 22 Jan 2026 13:24:00 +0800 Subject: [PATCH 07/14] smb/client: rename to NT_STATUS_SOME_NOT_MAPPED See MS-ERREF 2.3.1 STATUS_SOME_NOT_MAPPED. Signed-off-by: ChenXiaoSong Signed-off-by: Steve French --- fs/smb/client/nterr.c | 2 +- fs/smb/client/nterr.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/smb/client/nterr.c b/fs/smb/client/nterr.c index 806ad6d7c22a..279566cac435 100644 --- a/fs/smb/client/nterr.c +++ b/fs/smb/client/nterr.c @@ -695,7 +695,7 @@ const struct nt_err_code_struct nt_errs[] = { {"NT_STATUS_NETWORK_SESSION_EXPIRED", NT_STATUS_NETWORK_SESSION_EXPIRED}, {"NT_STATUS_NO_MORE_ENTRIES", NT_STATUS_NO_MORE_ENTRIES}, {"NT_STATUS_MORE_ENTRIES", NT_STATUS_MORE_ENTRIES}, - {"NT_STATUS_SOME_UNMAPPED", NT_STATUS_SOME_UNMAPPED}, + {"NT_STATUS_SOME_NOT_MAPPED", NT_STATUS_SOME_NOT_MAPPED}, {"NT_STATUS_NO_SUCH_JOB", NT_STATUS_NO_SUCH_JOB}, {"NT_STATUS_NO_PREAUTH_INTEGRITY_HASH_OVERLAP", NT_STATUS_NO_PREAUTH_INTEGRITY_HASH_OVERLAP}, diff --git a/fs/smb/client/nterr.h b/fs/smb/client/nterr.h index e8306704f5e3..cab16d53e11c 100644 --- a/fs/smb/client/nterr.h +++ b/fs/smb/client/nterr.h @@ -35,7 +35,7 @@ extern const struct nt_err_code_struct nt_errs[]; #define NT_STATUS_OK 0x0000 #define NT_STATUS_PENDING 0x0103 -#define NT_STATUS_SOME_UNMAPPED 0x0107 +#define NT_STATUS_SOME_NOT_MAPPED 0x0107 #define NT_STATUS_NOTIFY_ENUM_DIR 0x010c #define NT_STATUS_BUFFER_OVERFLOW 0x80000005 #define NT_STATUS_NO_MORE_ENTRIES 0x8000001a From 617a5d2473dc043dcd7f2b84b28ce7a9f8a06587 Mon Sep 17 00:00:00 2001 From: ChenXiaoSong Date: Thu, 22 Jan 2026 13:24:01 +0800 Subject: [PATCH 08/14] smb/client: rename to NT_ERROR_INVALID_DATATYPE See MS-ERREF 2.2 ERROR_INVALID_DATATYPE. Signed-off-by: ChenXiaoSong Signed-off-by: Steve French --- fs/smb/client/nterr.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/smb/client/nterr.h b/fs/smb/client/nterr.h index cab16d53e11c..03d120e2ce09 100644 --- a/fs/smb/client/nterr.h +++ b/fs/smb/client/nterr.h @@ -26,7 +26,7 @@ extern const struct nt_err_code_struct nt_errs[]; #define NT_STATUS_MORE_ENTRIES 0x0105 #define NT_ERROR_INVALID_PARAMETER 0x0057 #define NT_ERROR_INSUFFICIENT_BUFFER 0x007a -#define NT_STATUS_1804 0x070c +#define NT_ERROR_INVALID_DATATYPE 0x070c /* * Win32 Error codes extracted using a loop in smbclient then printing a netmon From 3774289f525cba0f33610a390bd6a28bb636b637 Mon Sep 17 00:00:00 2001 From: ChenXiaoSong Date: Thu, 22 Jan 2026 13:24:02 +0800 Subject: [PATCH 09/14] smb/client: move NT_STATUS_MORE_ENTRIES It is an NTSTATUS value, not a Win32 error code. Signed-off-by: ChenXiaoSong Signed-off-by: Steve French --- fs/smb/client/nterr.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fs/smb/client/nterr.h b/fs/smb/client/nterr.h index 03d120e2ce09..9a4a450c6571 100644 --- a/fs/smb/client/nterr.h +++ b/fs/smb/client/nterr.h @@ -22,19 +22,19 @@ struct nt_err_code_struct { extern const struct nt_err_code_struct nt_errs[]; -/* Win32 Status codes. */ -#define NT_STATUS_MORE_ENTRIES 0x0105 +/* Win32 Error Codes. */ #define NT_ERROR_INVALID_PARAMETER 0x0057 #define NT_ERROR_INSUFFICIENT_BUFFER 0x007a #define NT_ERROR_INVALID_DATATYPE 0x070c /* - * Win32 Error codes extracted using a loop in smbclient then printing a netmon + * NTSTATUS Values extracted using a loop in smbclient then printing a netmon * sniff to a file. */ #define NT_STATUS_OK 0x0000 #define NT_STATUS_PENDING 0x0103 +#define NT_STATUS_MORE_ENTRIES 0x0105 #define NT_STATUS_SOME_NOT_MAPPED 0x0107 #define NT_STATUS_NOTIFY_ENUM_DIR 0x010c #define NT_STATUS_BUFFER_OVERFLOW 0x80000005 From 4c91b67f87ac032c77650108ef9841772b9dc364 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Thu, 12 Feb 2026 19:53:07 -0300 Subject: [PATCH 10/14] smb: client: fix data corruption due to racy lease checks Customer reported data corruption in some of their files. It turned out the client would end up calling cacheless IO functions while having RHW lease, bypassing the pagecache and then leaving gaps in the file while writing to it. It was related to concurrent opens changing the lease state while having writes in flight. Lease breaks and re-opens due to reconnect could also cause same issue. Fix this by serialising the lease updates with cifsInodeInfo::open_file_lock. When handling oplock break, make sure to use the downgraded oplock value rather than one in cifsInodeinfo as it could be changed concurrently. Reported-by: Frank Sorenson Signed-off-by: Paulo Alcantara (Red Hat) Reviewed-by: David Howells Cc: linux-cifs@vger.kernel.org Signed-off-by: Steve French --- fs/smb/client/cifsglob.h | 36 ++++++++++++++++++++----- fs/smb/client/file.c | 57 ++++++++++++++++++++++++--------------- fs/smb/client/smb1ops.c | 16 ++++++++--- fs/smb/client/smb2misc.c | 10 +++---- fs/smb/client/smb2ops.c | 44 +++++++++++++++++------------- fs/smb/client/smb2proto.h | 2 +- 6 files changed, 110 insertions(+), 55 deletions(-) diff --git a/fs/smb/client/cifsglob.h b/fs/smb/client/cifsglob.h index 7eb0131963dd..080ea601c209 100644 --- a/fs/smb/client/cifsglob.h +++ b/fs/smb/client/cifsglob.h @@ -515,8 +515,10 @@ struct smb_version_operations { /* check for STATUS_NETWORK_SESSION_EXPIRED */ bool (*is_session_expired)(char *); /* send oplock break response */ - int (*oplock_response)(struct cifs_tcon *tcon, __u64 persistent_fid, __u64 volatile_fid, - __u16 net_fid, struct cifsInodeInfo *cifs_inode); + int (*oplock_response)(struct cifs_tcon *tcon, __u64 persistent_fid, + __u64 volatile_fid, __u16 net_fid, + struct cifsInodeInfo *cifs_inode, + unsigned int oplock); /* query remote filesystem */ int (*queryfs)(const unsigned int, struct cifs_tcon *, const char *, struct cifs_sb_info *, struct kstatfs *); @@ -1531,10 +1533,6 @@ int cifs_file_set_size(const unsigned int xid, struct dentry *dentry, #define CIFS_CACHE_RW_FLG (CIFS_CACHE_READ_FLG | CIFS_CACHE_WRITE_FLG) #define CIFS_CACHE_RHW_FLG (CIFS_CACHE_RW_FLG | CIFS_CACHE_HANDLE_FLG) -#define CIFS_CACHE_READ(cinode) ((cinode->oplock & CIFS_CACHE_READ_FLG) || (CIFS_SB(cinode->netfs.inode.i_sb)->mnt_cifs_flags & CIFS_MOUNT_RO_CACHE)) -#define CIFS_CACHE_HANDLE(cinode) (cinode->oplock & CIFS_CACHE_HANDLE_FLG) -#define CIFS_CACHE_WRITE(cinode) ((cinode->oplock & CIFS_CACHE_WRITE_FLG) || (CIFS_SB(cinode->netfs.inode.i_sb)->mnt_cifs_flags & CIFS_MOUNT_RW_CACHE)) - /* * One of these for each file inode */ @@ -2312,4 +2310,30 @@ static inline void cifs_requeue_server_reconn(struct TCP_Server_Info *server) queue_delayed_work(cifsiod_wq, &server->reconnect, delay * HZ); } +static inline bool __cifs_cache_state_check(struct cifsInodeInfo *cinode, + unsigned int oplock_flags, + unsigned int sb_flags) +{ + struct cifs_sb_info *cifs_sb = CIFS_SB(cinode->netfs.inode.i_sb); + unsigned int oplock = READ_ONCE(cinode->oplock); + unsigned int sflags = cifs_sb->mnt_cifs_flags; + + return (oplock & oplock_flags) || (sflags & sb_flags); +} + +#define CIFS_CACHE_READ(cinode) \ + __cifs_cache_state_check(cinode, CIFS_CACHE_READ_FLG, \ + CIFS_MOUNT_RO_CACHE) +#define CIFS_CACHE_HANDLE(cinode) \ + __cifs_cache_state_check(cinode, CIFS_CACHE_HANDLE_FLG, 0) +#define CIFS_CACHE_WRITE(cinode) \ + __cifs_cache_state_check(cinode, CIFS_CACHE_WRITE_FLG, \ + CIFS_MOUNT_RW_CACHE) + +static inline void cifs_reset_oplock(struct cifsInodeInfo *cinode) +{ + scoped_guard(spinlock, &cinode->open_file_lock) + WRITE_ONCE(cinode->oplock, 0); +} + #endif /* _CIFS_GLOB_H */ diff --git a/fs/smb/client/file.c b/fs/smb/client/file.c index 51360d64b7b2..88273f82812b 100644 --- a/fs/smb/client/file.c +++ b/fs/smb/client/file.c @@ -731,14 +731,14 @@ struct cifsFileInfo *cifs_new_fileinfo(struct cifs_fid *fid, struct file *file, oplock = fid->pending_open->oplock; list_del(&fid->pending_open->olist); - fid->purge_cache = false; - server->ops->set_fid(cfile, fid, oplock); - list_add(&cfile->tlist, &tcon->openFileList); atomic_inc(&tcon->num_local_opens); /* if readable file instance put first in list*/ spin_lock(&cinode->open_file_lock); + fid->purge_cache = false; + server->ops->set_fid(cfile, fid, oplock); + if (file->f_mode & FMODE_READ) list_add(&cfile->flist, &cinode->openFileList); else @@ -1410,7 +1410,8 @@ reopen_success: oplock = 0; } - server->ops->set_fid(cfile, &cfile->fid, oplock); + scoped_guard(spinlock, &cinode->open_file_lock) + server->ops->set_fid(cfile, &cfile->fid, oplock); if (oparms.reconnect) cifs_relock_file(cfile); @@ -1437,11 +1438,11 @@ smb2_can_defer_close(struct inode *inode, struct cifs_deferred_close *dclose) { struct cifs_sb_info *cifs_sb = CIFS_SB(inode->i_sb); struct cifsInodeInfo *cinode = CIFS_I(inode); + unsigned int oplock = READ_ONCE(cinode->oplock); - return (cifs_sb->ctx->closetimeo && cinode->lease_granted && dclose && - (cinode->oplock == CIFS_CACHE_RHW_FLG || - cinode->oplock == CIFS_CACHE_RH_FLG) && - !test_bit(CIFS_INO_CLOSE_ON_LOCK, &cinode->flags)); + return cifs_sb->ctx->closetimeo && cinode->lease_granted && dclose && + (oplock == CIFS_CACHE_RHW_FLG || oplock == CIFS_CACHE_RH_FLG) && + !test_bit(CIFS_INO_CLOSE_ON_LOCK, &cinode->flags); } @@ -2371,7 +2372,7 @@ cifs_setlk(struct file *file, struct file_lock *flock, __u32 type, cifs_zap_mapping(inode); cifs_dbg(FYI, "Set no oplock for inode=%p due to mand locks\n", inode); - CIFS_I(inode)->oplock = 0; + cifs_reset_oplock(CIFS_I(inode)); } rc = server->ops->mand_lock(xid, cfile, flock->fl_start, length, @@ -2930,7 +2931,7 @@ cifs_strict_writev(struct kiocb *iocb, struct iov_iter *from) cifs_zap_mapping(inode); cifs_dbg(FYI, "Set Oplock/Lease to NONE for inode=%p after write\n", inode); - cinode->oplock = 0; + cifs_reset_oplock(cinode); } out: cifs_put_writer(cinode); @@ -2966,7 +2967,7 @@ ssize_t cifs_file_write_iter(struct kiocb *iocb, struct iov_iter *from) cifs_dbg(FYI, "Set no oplock for inode=%p after a write operation\n", inode); - cinode->oplock = 0; + cifs_reset_oplock(cinode); } return written; } @@ -3154,9 +3155,11 @@ void cifs_oplock_break(struct work_struct *work) struct super_block *sb = inode->i_sb; struct cifs_sb_info *cifs_sb = CIFS_SB(sb); struct cifsInodeInfo *cinode = CIFS_I(inode); + bool cache_read, cache_write, cache_handle; struct cifs_tcon *tcon; struct TCP_Server_Info *server; struct tcon_link *tlink; + unsigned int oplock; int rc = 0; bool purge_cache = false, oplock_break_cancelled; __u64 persistent_fid, volatile_fid; @@ -3177,29 +3180,40 @@ void cifs_oplock_break(struct work_struct *work) tcon = tlink_tcon(tlink); server = tcon->ses->server; - server->ops->downgrade_oplock(server, cinode, cfile->oplock_level, - cfile->oplock_epoch, &purge_cache); + scoped_guard(spinlock, &cinode->open_file_lock) { + unsigned int sbflags = cifs_sb->mnt_cifs_flags; - if (!CIFS_CACHE_WRITE(cinode) && CIFS_CACHE_READ(cinode) && - cifs_has_mand_locks(cinode)) { + server->ops->downgrade_oplock(server, cinode, cfile->oplock_level, + cfile->oplock_epoch, &purge_cache); + oplock = READ_ONCE(cinode->oplock); + cache_read = (oplock & CIFS_CACHE_READ_FLG) || + (sbflags & CIFS_MOUNT_RO_CACHE); + cache_write = (oplock & CIFS_CACHE_WRITE_FLG) || + (sbflags & CIFS_MOUNT_RW_CACHE); + cache_handle = oplock & CIFS_CACHE_HANDLE_FLG; + } + + if (!cache_write && cache_read && cifs_has_mand_locks(cinode)) { cifs_dbg(FYI, "Reset oplock to None for inode=%p due to mand locks\n", inode); - cinode->oplock = 0; + cifs_reset_oplock(cinode); + oplock = 0; + cache_read = cache_write = cache_handle = false; } if (S_ISREG(inode->i_mode)) { - if (CIFS_CACHE_READ(cinode)) + if (cache_read) break_lease(inode, O_RDONLY); else break_lease(inode, O_WRONLY); rc = filemap_fdatawrite(inode->i_mapping); - if (!CIFS_CACHE_READ(cinode) || purge_cache) { + if (!cache_read || purge_cache) { rc = filemap_fdatawait(inode->i_mapping); mapping_set_error(inode->i_mapping, rc); cifs_zap_mapping(inode); } cifs_dbg(FYI, "Oplock flush inode %p rc %d\n", inode, rc); - if (CIFS_CACHE_WRITE(cinode)) + if (cache_write) goto oplock_break_ack; } @@ -3214,7 +3228,7 @@ oplock_break_ack: * So, new open will not use cached handle. */ - if (!CIFS_CACHE_HANDLE(cinode) && !list_empty(&cinode->deferred_closes)) + if (!cache_handle && !list_empty(&cinode->deferred_closes)) cifs_close_deferred_file(cinode); persistent_fid = cfile->fid.persistent_fid; @@ -3232,7 +3246,8 @@ oplock_break_ack: if (!oplock_break_cancelled && !list_empty(&cinode->openFileList)) { spin_unlock(&cinode->open_file_lock); rc = server->ops->oplock_response(tcon, persistent_fid, - volatile_fid, net_fid, cinode); + volatile_fid, net_fid, + cinode, oplock); cifs_dbg(FYI, "Oplock release rc = %d\n", rc); } else spin_unlock(&cinode->open_file_lock); diff --git a/fs/smb/client/smb1ops.c b/fs/smb/client/smb1ops.c index 9c3b97d2a20a..970aeffe936e 100644 --- a/fs/smb/client/smb1ops.c +++ b/fs/smb/client/smb1ops.c @@ -395,6 +395,7 @@ cifs_downgrade_oplock(struct TCP_Server_Info *server, struct cifsInodeInfo *cinode, __u32 oplock, __u16 epoch, bool *purge_cache) { + lockdep_assert_held(&cinode->open_file_lock); cifs_set_oplock_level(cinode, oplock); } @@ -894,6 +895,9 @@ static void cifs_set_fid(struct cifsFileInfo *cfile, struct cifs_fid *fid, __u32 oplock) { struct cifsInodeInfo *cinode = CIFS_I(d_inode(cfile->dentry)); + + lockdep_assert_held(&cinode->open_file_lock); + cfile->fid.netfid = fid->netfid; cifs_set_oplock_level(cinode, oplock); cinode->can_cache_brlcks = CIFS_CACHE_WRITE(cinode); @@ -1139,12 +1143,16 @@ cifs_close_dir(const unsigned int xid, struct cifs_tcon *tcon, return CIFSFindClose(xid, tcon, fid->netfid); } -static int -cifs_oplock_response(struct cifs_tcon *tcon, __u64 persistent_fid, - __u64 volatile_fid, __u16 net_fid, struct cifsInodeInfo *cinode) +static int cifs_oplock_response(struct cifs_tcon *tcon, __u64 persistent_fid, + __u64 volatile_fid, __u16 net_fid, + struct cifsInodeInfo *cinode, unsigned int oplock) { + unsigned int sbflags = CIFS_SB(cinode->netfs.inode.i_sb)->mnt_cifs_flags; + __u8 op; + + op = !!((oplock & CIFS_CACHE_READ_FLG) || (sbflags & CIFS_MOUNT_RO_CACHE)); return CIFSSMBLock(0, tcon, net_fid, current->tgid, 0, 0, 0, 0, - LOCKING_ANDX_OPLOCK_RELEASE, false, CIFS_CACHE_READ(cinode) ? 1 : 0); + LOCKING_ANDX_OPLOCK_RELEASE, false, op); } static int diff --git a/fs/smb/client/smb2misc.c b/fs/smb/client/smb2misc.c index 0871b9f1f86a..d1ae839e4863 100644 --- a/fs/smb/client/smb2misc.c +++ b/fs/smb/client/smb2misc.c @@ -484,16 +484,16 @@ cifs_convert_path_to_utf16(const char *from, struct cifs_sb_info *cifs_sb) return to; } -__le32 -smb2_get_lease_state(struct cifsInodeInfo *cinode) +__le32 smb2_get_lease_state(struct cifsInodeInfo *cinode, unsigned int oplock) { + unsigned int sbflags = CIFS_SB(cinode->netfs.inode.i_sb)->mnt_cifs_flags; __le32 lease = 0; - if (CIFS_CACHE_WRITE(cinode)) + if ((oplock & CIFS_CACHE_WRITE_FLG) || (sbflags & CIFS_MOUNT_RW_CACHE)) lease |= SMB2_LEASE_WRITE_CACHING_LE; - if (CIFS_CACHE_HANDLE(cinode)) + if (oplock & CIFS_CACHE_HANDLE_FLG) lease |= SMB2_LEASE_HANDLE_CACHING_LE; - if (CIFS_CACHE_READ(cinode)) + if ((oplock & CIFS_CACHE_READ_FLG) || (sbflags & CIFS_MOUNT_RO_CACHE)) lease |= SMB2_LEASE_READ_CACHING_LE; return lease; } diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c index 262df6d2c2c8..1e13dd0a24c3 100644 --- a/fs/smb/client/smb2ops.c +++ b/fs/smb/client/smb2ops.c @@ -1460,6 +1460,8 @@ smb2_set_fid(struct cifsFileInfo *cfile, struct cifs_fid *fid, __u32 oplock) struct cifsInodeInfo *cinode = CIFS_I(d_inode(cfile->dentry)); struct TCP_Server_Info *server = tlink_tcon(cfile->tlink)->ses->server; + lockdep_assert_held(&cinode->open_file_lock); + cfile->fid.persistent_fid = fid->persistent_fid; cfile->fid.volatile_fid = fid->volatile_fid; cfile->fid.access = fid->access; @@ -2684,16 +2686,19 @@ smb2_is_network_name_deleted(char *buf, struct TCP_Server_Info *server) return false; } -static int -smb2_oplock_response(struct cifs_tcon *tcon, __u64 persistent_fid, - __u64 volatile_fid, __u16 net_fid, struct cifsInodeInfo *cinode) +static int smb2_oplock_response(struct cifs_tcon *tcon, __u64 persistent_fid, + __u64 volatile_fid, __u16 net_fid, + struct cifsInodeInfo *cinode, unsigned int oplock) { + unsigned int sbflags = CIFS_SB(cinode->netfs.inode.i_sb)->mnt_cifs_flags; + __u8 op; + if (tcon->ses->server->capabilities & SMB2_GLOBAL_CAP_LEASING) return SMB2_lease_break(0, tcon, cinode->lease_key, - smb2_get_lease_state(cinode)); + smb2_get_lease_state(cinode, oplock)); - return SMB2_oplock_break(0, tcon, persistent_fid, volatile_fid, - CIFS_CACHE_READ(cinode) ? 1 : 0); + op = !!((oplock & CIFS_CACHE_READ_FLG) || (sbflags & CIFS_MOUNT_RO_CACHE)); + return SMB2_oplock_break(0, tcon, persistent_fid, volatile_fid, op); } void @@ -4053,6 +4058,7 @@ smb2_downgrade_oplock(struct TCP_Server_Info *server, struct cifsInodeInfo *cinode, __u32 oplock, __u16 epoch, bool *purge_cache) { + lockdep_assert_held(&cinode->open_file_lock); server->ops->set_oplock_level(cinode, oplock, 0, NULL); } @@ -4093,19 +4099,19 @@ smb2_set_oplock_level(struct cifsInodeInfo *cinode, __u32 oplock, if (oplock == SMB2_OPLOCK_LEVEL_NOCHANGE) return; if (oplock == SMB2_OPLOCK_LEVEL_BATCH) { - cinode->oplock = CIFS_CACHE_RHW_FLG; + WRITE_ONCE(cinode->oplock, CIFS_CACHE_RHW_FLG); cifs_dbg(FYI, "Batch Oplock granted on inode %p\n", &cinode->netfs.inode); } else if (oplock == SMB2_OPLOCK_LEVEL_EXCLUSIVE) { - cinode->oplock = CIFS_CACHE_RW_FLG; + WRITE_ONCE(cinode->oplock, CIFS_CACHE_RW_FLG); cifs_dbg(FYI, "Exclusive Oplock granted on inode %p\n", &cinode->netfs.inode); } else if (oplock == SMB2_OPLOCK_LEVEL_II) { - cinode->oplock = CIFS_CACHE_READ_FLG; + WRITE_ONCE(cinode->oplock, CIFS_CACHE_READ_FLG); cifs_dbg(FYI, "Level II Oplock granted on inode %p\n", &cinode->netfs.inode); } else - cinode->oplock = 0; + WRITE_ONCE(cinode->oplock, 0); } static void @@ -4140,7 +4146,7 @@ smb21_set_oplock_level(struct cifsInodeInfo *cinode, __u32 oplock, if (!new_oplock) strscpy(message, "None"); - cinode->oplock = new_oplock; + WRITE_ONCE(cinode->oplock, new_oplock); cifs_dbg(FYI, "%s Lease granted on inode %p\n", message, &cinode->netfs.inode); } @@ -4149,30 +4155,32 @@ static void smb3_set_oplock_level(struct cifsInodeInfo *cinode, __u32 oplock, __u16 epoch, bool *purge_cache) { - unsigned int old_oplock = cinode->oplock; + unsigned int old_oplock = READ_ONCE(cinode->oplock); + unsigned int new_oplock; smb21_set_oplock_level(cinode, oplock, epoch, purge_cache); + new_oplock = READ_ONCE(cinode->oplock); if (purge_cache) { *purge_cache = false; if (old_oplock == CIFS_CACHE_READ_FLG) { - if (cinode->oplock == CIFS_CACHE_READ_FLG && + if (new_oplock == CIFS_CACHE_READ_FLG && (epoch - cinode->epoch > 0)) *purge_cache = true; - else if (cinode->oplock == CIFS_CACHE_RH_FLG && + else if (new_oplock == CIFS_CACHE_RH_FLG && (epoch - cinode->epoch > 1)) *purge_cache = true; - else if (cinode->oplock == CIFS_CACHE_RHW_FLG && + else if (new_oplock == CIFS_CACHE_RHW_FLG && (epoch - cinode->epoch > 1)) *purge_cache = true; - else if (cinode->oplock == 0 && + else if (new_oplock == 0 && (epoch - cinode->epoch > 0)) *purge_cache = true; } else if (old_oplock == CIFS_CACHE_RH_FLG) { - if (cinode->oplock == CIFS_CACHE_RH_FLG && + if (new_oplock == CIFS_CACHE_RH_FLG && (epoch - cinode->epoch > 0)) *purge_cache = true; - else if (cinode->oplock == CIFS_CACHE_RHW_FLG && + else if (new_oplock == CIFS_CACHE_RHW_FLG && (epoch - cinode->epoch > 1)) *purge_cache = true; } diff --git a/fs/smb/client/smb2proto.h b/fs/smb/client/smb2proto.h index c7759e37d975..881e42cf66ce 100644 --- a/fs/smb/client/smb2proto.h +++ b/fs/smb/client/smb2proto.h @@ -42,7 +42,7 @@ struct mid_q_entry *smb2_setup_async_request(struct TCP_Server_Info *server, struct smb_rqst *rqst); struct cifs_tcon *smb2_find_smb_tcon(struct TCP_Server_Info *server, __u64 ses_id, __u32 tid); -__le32 smb2_get_lease_state(struct cifsInodeInfo *cinode); +__le32 smb2_get_lease_state(struct cifsInodeInfo *cinode, unsigned int oplock); bool smb2_is_valid_oplock_break(char *buffer, struct TCP_Server_Info *server); int smb3_handle_read_data(struct TCP_Server_Info *server, struct mid_q_entry *mid); From 8e61cfc5f52c7673f12bc8e993f4ebb811136b16 Mon Sep 17 00:00:00 2001 From: Steve French Date: Sat, 13 Dec 2025 12:48:49 -0600 Subject: [PATCH 11/14] cifs: update internal module version number to 2.59 Signed-off-by: Steve French --- fs/smb/client/cifsfs.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/smb/client/cifsfs.h b/fs/smb/client/cifsfs.h index 121821e6c872..e320d39b01f5 100644 --- a/fs/smb/client/cifsfs.h +++ b/fs/smb/client/cifsfs.h @@ -147,6 +147,6 @@ extern const struct export_operations cifs_export_ops; #endif /* CONFIG_CIFS_NFSD_EXPORT */ /* when changing internal version - update following two lines at same time */ -#define SMB3_PRODUCT_BUILD 58 -#define CIFS_VERSION "2.58" +#define SMB3_PRODUCT_BUILD 59 +#define CIFS_VERSION "2.59" #endif /* _CIFSFS_H */ From a5a50f1415692a0fe3bf0ec17f422cac8e158e65 Mon Sep 17 00:00:00 2001 From: Shyam Prasad N Date: Sat, 14 Feb 2026 13:05:50 +0530 Subject: [PATCH 12/14] cifs: remove unnecessary tracing after put tcon This code was recently changed from manually decrementing tcon ref to using cifs_put_tcon. But even before that change this tracing happened after decrementing the ref count, which is wrong. With cifs_put_tcon, tracing already happens inside it. So just removing the extra tracing here. Signed-off-by: Shyam Prasad N Signed-off-by: Steve French --- fs/smb/client/smb2ops.c | 2 -- fs/smb/client/trace.h | 1 - 2 files changed, 3 deletions(-) diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c index 1e13dd0a24c3..61c521712f86 100644 --- a/fs/smb/client/smb2ops.c +++ b/fs/smb/client/smb2ops.c @@ -3181,8 +3181,6 @@ smb2_get_dfs_refer(const unsigned int xid, struct cifs_ses *ses, if (tcon && !tcon->ipc) { /* ipc tcons are not refcounted */ cifs_put_tcon(tcon, netfs_trace_tcon_ref_put_dfs_refer); - trace_smb3_tcon_ref(tcon->debug_id, tcon->tc_count, - netfs_trace_tcon_ref_dec_dfs_refer); } kfree(utf16_path); kfree(dfs_req); diff --git a/fs/smb/client/trace.h b/fs/smb/client/trace.h index 191f02344dcd..9228f95cae2b 100644 --- a/fs/smb/client/trace.h +++ b/fs/smb/client/trace.h @@ -168,7 +168,6 @@ E_(cifs_trace_rw_credits_zero_in_flight, "ZERO-IN-FLT") #define smb3_tcon_ref_traces \ - EM(netfs_trace_tcon_ref_dec_dfs_refer, "DEC DfsRef") \ EM(netfs_trace_tcon_ref_free, "FRE ") \ EM(netfs_trace_tcon_ref_free_fail, "FRE Fail ") \ EM(netfs_trace_tcon_ref_free_ipc, "FRE Ipc ") \ From 14f66f44646333d2bfd7ece36585874fd72f8286 Mon Sep 17 00:00:00 2001 From: Shyam Prasad N Date: Sat, 14 Feb 2026 15:59:13 +0530 Subject: [PATCH 13/14] cifs: some missing initializations on replay In several places in the code, we have a label to signify the start of the code where a request can be replayed if necessary. However, some of these places were missing the necessary reinitializations of certain local variables before replay. This change makes sure that these variables get initialized after the label. Cc: stable@vger.kernel.org Reported-by: Yuchan Nam Tested-by: Yuchan Nam Signed-off-by: Shyam Prasad N Signed-off-by: Steve French --- fs/smb/client/smb2ops.c | 2 ++ fs/smb/client/smb2pdu.c | 1 + 2 files changed, 3 insertions(+) diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c index 61c521712f86..7370d7a18cd0 100644 --- a/fs/smb/client/smb2ops.c +++ b/fs/smb/client/smb2ops.c @@ -1185,6 +1185,7 @@ smb2_set_ea(const unsigned int xid, struct cifs_tcon *tcon, replay_again: /* reinitialize for possible replay */ + used_len = 0; flags = CIFS_CP_CREATE_CLOSE_OP; oplock = SMB2_OPLOCK_LEVEL_NONE; server = cifs_pick_channel(ses); @@ -1588,6 +1589,7 @@ smb2_ioctl_query_info(const unsigned int xid, replay_again: /* reinitialize for possible replay */ + buffer = NULL; flags = CIFS_CP_CREATE_CLOSE_OP; oplock = SMB2_OPLOCK_LEVEL_NONE; server = cifs_pick_channel(ses); diff --git a/fs/smb/client/smb2pdu.c b/fs/smb/client/smb2pdu.c index 4602b4dfe832..7f3edf42b9c3 100644 --- a/fs/smb/client/smb2pdu.c +++ b/fs/smb/client/smb2pdu.c @@ -2908,6 +2908,7 @@ int smb311_posix_mkdir(const unsigned int xid, struct inode *inode, replay_again: /* reinitialize for possible replay */ + pc_buf = NULL; flags = 0; n_iov = 2; server = cifs_pick_channel(ses); From dc96f01d54cc7c785c98ee6e2b53075949ac16ed Mon Sep 17 00:00:00 2001 From: Aaditya Kansal Date: Thu, 5 Feb 2026 06:30:12 +0530 Subject: [PATCH 14/14] smb: client: terminate session upon failed client required signing Currently, when smb signature verification fails, the behaviour is to log the failure without any action to terminate the session. Call cifs_reconnect() when client required signature verification fails. Otherwise, log the error without reconnecting. Signed-off-by: Aaditya Kansal Signed-off-by: Steve French --- fs/smb/client/smb1transport.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/fs/smb/client/smb1transport.c b/fs/smb/client/smb1transport.c index 93731b00ca5d..38d6d5538b96 100644 --- a/fs/smb/client/smb1transport.c +++ b/fs/smb/client/smb1transport.c @@ -169,12 +169,18 @@ cifs_check_receive(struct mid_q_entry *mid, struct TCP_Server_Info *server, iov[0].iov_base = mid->resp_buf; iov[0].iov_len = len; - /* FIXME: add code to kill session */ + rc = cifs_verify_signature(&rqst, server, mid->sequence_number); - if (rc) + if (rc) { cifs_server_dbg(VFS, "SMB signature verification returned error = %d\n", rc); + + if (!(server->sec_mode & SECMODE_SIGN_REQUIRED)) { + cifs_reconnect(server, true); + return rc; + } + } } /* BB special case reconnect tid and uid here? */