From: Frank Sorenson <sorenson@redhat.com>
To: linux-cifs@vger.kernel.org, pc@manguebit.org, stfrench@microsoft.com
Subject: [PATCH v2 1/2] cifs: serialize readdir with directory cache invalidation from lease breaks
Date: Mon, 20 Jul 2026 11:35:40 -0500 [thread overview]
Message-ID: <20260720163541.1428872-2-sorenson@redhat.com> (raw)
In-Reply-To: <20260720163541.1428872-1-sorenson@redhat.com>
When SMB2 directory lease breaks occur concurrently with readdir
(getdents) operations, the lease break handler can invalidate the
directory cache while readdir is still traversing it, corrupting the
dcache and causing subsequent stat() calls to return wrong file sizes
or EIO errors.
The race:
1. rename() completes successfully, returns to userspace
2. Userspace calls getdents64 on the directory
3. cifs_readdir() begins traversing directory cache entries
4. Server sends a lease break notification (directory was modified)
5. cifs_oplock_break() -> cifs_revalidate_mapping() -> cifs_zap_mapping()
acquires CIFS_INO_LOCK and invalidates the page cache
6. RACE: cache invalidation runs concurrently with readdir traversal
7. Subsequent stat() calls return wrong file sizes from the corrupted cache
This bug has existed since directory-level lease support was added.
The fix uses the existing CIFS_INO_LOCK bit to serialize cifs_readdir()
with cifs_revalidate_mapping(), which the lease break handler calls.
Since cifs_revalidate_mapping() already acquires CIFS_INO_LOCK before
invalidating, having cifs_readdir() hold it makes the two operations
mutually exclusive.
cifs_wait_bit_killable() is made non-static so readdir.c can use it
as the wait function for wait_on_bit_lock_action(). On lock acquisition
failure (signal), the already-allocated dentry path page is freed before
returning.
Reproducer: concurrent renames + readdir with 2 or more threads against
a Windows Server share (directory leases required; does not reproduce
against Samba or with actimeo=0).
Signed-off-by: Frank Sorenson <sorenson@redhat.com>
---
fs/smb/client/cifsproto.h | 1 +
fs/smb/client/inode.c | 2 +-
fs/smb/client/readdir.c | 12 ++++++++++++
3 files changed, 14 insertions(+), 1 deletion(-)
diff --git a/fs/smb/client/cifsproto.h b/fs/smb/client/cifsproto.h
index 00168839c123..e1f8304d323c 100644
--- a/fs/smb/client/cifsproto.h
+++ b/fs/smb/client/cifsproto.h
@@ -183,6 +183,7 @@ void cifs_dir_info_to_fattr(struct cifs_fattr *fattr,
int cifs_fattr_to_inode(struct inode *inode, struct cifs_fattr *fattr,
bool from_readdir);
struct inode *cifs_iget(struct super_block *sb, struct cifs_fattr *fattr);
+int cifs_wait_bit_killable(struct wait_bit_key *key, int mode);
int cifs_get_inode_info(struct inode **inode, const char *full_path,
struct cifs_open_info_data *data,
diff --git a/fs/smb/client/inode.c b/fs/smb/client/inode.c
index deed04dd9b91..e75138f5f6bc 100644
--- a/fs/smb/client/inode.c
+++ b/fs/smb/client/inode.c
@@ -2772,7 +2772,7 @@ cifs_dentry_needs_reval(struct dentry *dentry)
* @key: currently unused
* @mode: the task state to sleep in
*/
-static int
+int
cifs_wait_bit_killable(struct wait_bit_key *key, int mode)
{
schedule();
diff --git a/fs/smb/client/readdir.c b/fs/smb/client/readdir.c
index ee5996e6d7d8..862ccc1a7e13 100644
--- a/fs/smb/client/readdir.c
+++ b/fs/smb/client/readdir.c
@@ -1064,6 +1064,17 @@ int cifs_readdir(struct file *file, struct dir_context *ctx)
void *page = alloc_dentry_path();
struct cached_fid *cfid = NULL;
struct cifs_sb_info *cifs_sb = CIFS_SB(file);
+ struct inode *inode = file_inode(file);
+ struct cifsInodeInfo *cinode = CIFS_I(inode);
+ int lock_rc;
+
+ lock_rc = wait_on_bit_lock_action(&cinode->flags, CIFS_INO_LOCK,
+ cifs_wait_bit_killable,
+ TASK_KILLABLE|TASK_FREEZABLE_UNSAFE);
+ if (lock_rc) {
+ free_dentry_path(page);
+ return lock_rc;
+ }
xid = get_xid();
@@ -1226,5 +1237,6 @@ int cifs_readdir(struct file *file, struct dir_context *ctx)
close_cached_dir(cfid);
free_dentry_path(page);
free_xid(xid);
+ clear_and_wake_up_bit(CIFS_INO_LOCK, &cinode->flags);
return rc;
}
--
2.55.0
next prev parent reply other threads:[~2026-07-20 16:35 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-20 16:35 [PATCH v2 0/2] cifs: fix attribute cache corruption from concurrent directory operations Frank Sorenson
2026-07-20 16:35 ` Frank Sorenson [this message]
2026-07-20 16:35 ` [PATCH v2 2/2] cifs: prevent readdir from changing file size due to stale directory metadata Frank Sorenson
2026-07-20 17:19 ` [PATCH v2 0/2] cifs: fix attribute cache corruption from concurrent directory operations Frank Sorenson
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260720163541.1428872-2-sorenson@redhat.com \
--to=sorenson@redhat.com \
--cc=linux-cifs@vger.kernel.org \
--cc=pc@manguebit.org \
--cc=stfrench@microsoft.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox