]> git.ipfire.org Git - thirdparty/kernel/linux.git/commit
cifs: prevent readdir from changing file size due to stale directory metadata
authorFrank Sorenson <sorenson@redhat.com>
Tue, 21 Jul 2026 23:55:51 +0000 (18:55 -0500)
committerSteve French <stfrench@microsoft.com>
Wed, 22 Jul 2026 01:19:03 +0000 (20:19 -0500)
commite8a8d54c2d508891c142a928fc7d298c4c8bd0dd
treeccef485932aea5821a5cd6b5cb4e8804b5c5d784
parent2eb74eef4b7eda8df593d22fb48e94ef959ec8a5
cifs: prevent readdir from changing file size due to stale directory metadata

Windows Server's directory enumeration metadata lags behind the actual
file size after a write+close or rename.  A concurrent readdir() in the
window between close() returning to userspace and stat() being called
overwrites the correct cached i_size with the stale server value, causing
stat() to return the wrong size.

Once _cifsFileInfo_put() removes the last writable handle from
openFileList, is_size_safe_to_change() permits readdir to overwrite
i_size.  smb2_close_getattr() then stamps cifs_i->time = jiffies, making
the corrupt cached value appear fresh to the next stat().

The existing check (see Fixes:) only blocked stale size updates while
an active RW lease was held, not after the last writable handle closes.

Add cifsInodeInfo->time_last_write, written via smp_store_release() at
writable close and on setattr/truncate.  is_size_safe_to_change() checks
is_inode_writable() first (acquiring open_file_lock), then rejects a
readdir size update if time_last_write falls within acregmax jiffies.
The spinlock release in _cifsFileInfo_put() forms a store-release barrier
that pairs with the spin_lock() (load-acquire) in is_inode_writable(),
ensuring the subsequent smp_load_acquire() on time_last_write observes
any update from a concurrent close().  When a size update is rejected and
the server value differs from the cached one, cifs_i->time is cleared to
force a fresh QUERY_INFO on the next stat().

readdir is also blocked from changing i_size while writable handles are
open or an RW lease is held, even on direct-IO mounts.

For deferred close (closetimeo > 0), time_last_write is refreshed at the
actual server close in smb2_deferred_work_close() and in the
cifs_close_deferred_file*() drain paths invoked by lease/oplock breaks
and tcon teardown, anchoring the protection window to the real close time
rather than the earlier userspace close.

time_last_write == 0 skips the time_before() check to avoid false
positives near boot on 32-bit systems where jiffies starts close to
INITIAL_JIFFIES.

Does not reproduce against Samba or with actimeo=0.

Fixes: e4b61f3b1c67 ("cifs: prevent updating file size from server if we have a read/write lease")
Signed-off-by: Frank Sorenson <sorenson@redhat.com>
Signed-off-by: Steve French <stfrench@microsoft.com>
fs/smb/client/cifsfs.c
fs/smb/client/cifsglob.h
fs/smb/client/file.c
fs/smb/client/inode.c
fs/smb/client/misc.c