From: Zizhi Wo <wozizhi@huawei.com> mainline inclusion from mainline-v7.3-rc6 commit 35024595f641e790e24609f3f046e1667e3b280a category: bugfix bugzilla: https://atomgit.com/src-openeuler/kernel/issues/18925 Reference: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?i... -------------------------------- cifs_setsize() relied on pagecache_isize_extended() to zero the tail of the folio straddling the old EOF, but that helper is a no-op on CIFS (i_blkbits is 14), so data dirtied past EOF through an mmap survived and became visible once the file was extended. Use netfs_clear_stale_post_isize() instead, called after i_size is updated since cifs_setsize() callers hold i_rwsem exclusively for the whole resize. truncate_pagecache() is then called as in truncate_setsize(): a no-op on extend, and it drops the pagecache beyond the new EOF on shrink. Also thread old_size as an explicit parameter through cifs_setsize() and cifs_resize_file_locked(), captured by each caller before its own resize RPC. This closes a race where a concurrent stat() could adopt the RPC's already-updated server size via is_size_safe_to_change() before cifs_setsize() reads old_size itself. Closes: https://sashiko.dev/#/patchset/20260921230755.1133425-1-pc%40manguebit.org Fixes: c510edb9734a ("cifs: call pagecache_isize_extended() in cifs_setsize() when extending") Reviewed-by: David Howells <dhowells@redhat.com> Reviewed-by: Namjae Jeon <linkinjeon@kernel.org> Signed-off-by: Paulo Alcantara <pc@manguebit.org> Cc: Christian Brauner <brauner@kernel.org> Cc: Matthew Wilcox <willy@infradead.org> Cc: Ronnie Sahlberg <ronniesahlberg@gmail.com> Cc: Shyam Prasad N <sprasad@microsoft.com> Cc: Tom Talpey <tom@talpey.com> Cc: Bharath SM <bharathsm@microsoft.com> Cc: stable@vger.kernel.org Conflicts: fs/smb/client/file.c fs/smb/client/inode.c fs/smb/client/cifsfs.h fs/smb/client/smb2ops.c [1. 6.6's has no netfs_clear_stale_post_isize(), so mirror the exclusive branch of netfs_clear_stale_isize() as a CIFS-local static helper cifs_clear_stale_post_isize(). 2. cifs_resize_file_locked() does not exist in 6.6, so old_size is threaded directly through cifs_setsize() and captured in each caller before its resize RPC. 3. 6.6's cifs writeback only clamps the write to i_size and does not zero the tail, while cifs_setattr_* always call filemap_write_and_wait() before the resize RPC. So the folio may holding stale bytes. cifs_clear_stale_post_isize() should not use the folio's dirty state as the staleness criterion.] Signed-off-by: Zizhi Wo <wozizhi@huawei.com> Signed-off-by: Zizhi Wo <wozizhi@huawei.com> --- fs/smb/client/cifsfs.h | 2 +- fs/smb/client/inode.c | 76 ++++++++++++++++++++++++++++++++++++++--- fs/smb/client/smb2ops.c | 11 +++--- 3 files changed, 79 insertions(+), 10 deletions(-) diff --git a/fs/smb/client/cifsfs.h b/fs/smb/client/cifsfs.h index 41daebd220ff..f74b741d5726 100644 --- a/fs/smb/client/cifsfs.h +++ b/fs/smb/client/cifsfs.h @@ -140,7 +140,7 @@ extern ssize_t cifs_file_copychunk_range(unsigned int xid, size_t len, unsigned int flags); extern long cifs_ioctl(struct file *filep, unsigned int cmd, unsigned long arg); -extern void cifs_setsize(struct inode *inode, loff_t offset); +extern void cifs_setsize(struct inode *inode, loff_t old_size, loff_t offset); extern int cifs_truncate_page(struct address_space *mapping, loff_t from); struct smb3_fs_context; diff --git a/fs/smb/client/inode.c b/fs/smb/client/inode.c index d964a1f1e3d9..d01500212a21 100644 --- a/fs/smb/client/inode.c +++ b/fs/smb/client/inode.c @@ -9,6 +9,7 @@ #include <linux/stat.h> #include <linux/slab.h> #include <linux/pagemap.h> +#include <linux/rmap.h> #include <linux/freezer.h> #include <linux/sched/signal.h> #include <linux/wait_bit.h> @@ -2904,20 +2905,84 @@ int cifs_truncate_page(struct address_space *mapping, loff_t from) return rc; } -void cifs_setsize(struct inode *inode, loff_t offset) +/* + * Zero any data left in the pagecache within the [@from, @to) hole by a + * write through an mmap so that it isn't exposed as file content once the + * file is extended. Only the uptodate folio straddling @from can hold such + * data as pages wholly beyond the EOF can't be faulted in, so the zeroing + * is limited to that folio. The folio is zeroed rather than dropped so + * that a concurrent extending write can't lose data. + * + * For callers that hold i_rwsem exclusively across the whole resize and + * have already updated i_size to @to, no genuine concurrent buffered writer + * can be racing, a lockless stat() adopting a server-confirmed size mid-resize + * has no data behind it and never dirties the folio, so it can't fool this + * check into skipping the zeroing. + * + * Do not use the folio's dirty state as the staleness criterion here: unlike + * netfs writeback, this tree's cifs writeback does not zero the part of a + * folio beyond i_size, so a folio cleaned by the setattr-side + * filemap_write_and_wait() can still hold stale bytes. + * + * pagecache_isize_extended() can't be reused here: it is keyed on a + * sub-page block size (a no-op when the block size is >= PAGE_SIZE, as on + * network filesystems), runs after i_size is updated, and doesn't wait for + * writeback. Keep the two in sync if either is changed. + */ +static void cifs_clear_stale_post_isize(struct inode *inode, + unsigned long long from, + unsigned long long to) +{ + struct address_space *mapping = inode->i_mapping; + fgf_t fgp = FGP_LOCK; + struct folio *folio; + + if (from >= to) + return; + + folio = __filemap_get_folio(mapping, from >> PAGE_SHIFT, fgp, 0); + if (IS_ERR(folio)) + return; + + folio_wait_writeback(folio); + + if (folio_mkclean(folio)) + folio_mark_dirty(folio); + + if (folio_test_uptodate(folio)) { + unsigned long long fpos = folio_pos(folio); + + if (from < to && from < fpos + folio_size(folio)) { + size_t end = umin(to - fpos, folio_size(folio)); + size_t offset = from - fpos; + + folio_zero_segment(folio, offset, end); + } + } + + folio_unlock(folio); + folio_put(folio); +} + +void cifs_setsize(struct inode *inode, loff_t old_size, loff_t offset) { struct cifsInodeInfo *cifs_i = CIFS_I(inode); - loff_t old_size; spin_lock(&inode->i_lock); - old_size = i_size_read(inode); i_size_write(inode, offset); spin_unlock(&inode->i_lock); /* Cached inode must be refreshed on truncate */ cifs_i->time = 0; + + /* + * Zero the tail of the folio straddling the old EOF so data dirtied + * past EOF through an mmap isn't exposed. truncate_pagecache() then + * drops any pagecache beyond the new EOF, as in truncate_setsize(). + */ if (offset > old_size) - pagecache_isize_extended(inode, old_size, offset); + cifs_clear_stale_post_isize(inode, old_size, offset); + truncate_pagecache(inode, offset); } @@ -2929,6 +2994,7 @@ cifs_set_file_size(struct inode *inode, struct iattr *attrs, struct cifsFileInfo *open_file; struct cifsInodeInfo *cifsInode = CIFS_I(inode); struct cifs_sb_info *cifs_sb = CIFS_SB(inode->i_sb); + loff_t old_size = i_size_read(inode); struct tcon_link *tlink = NULL; struct cifs_tcon *tcon = NULL; struct TCP_Server_Info *server; @@ -2985,7 +3051,7 @@ cifs_set_file_size(struct inode *inode, struct iattr *attrs, set_size_out: if (rc == 0) { cifsInode->server_eof = attrs->ia_size; - cifs_setsize(inode, attrs->ia_size); + cifs_setsize(inode, old_size, attrs->ia_size); /* * i_blocks is not related to (i_size / i_blksize), but instead * 512 byte (2**9) size is required for calculating num blocks. diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c index 296dda29dca3..181107244f90 100644 --- a/fs/smb/client/smb2ops.c +++ b/fs/smb/client/smb2ops.c @@ -2047,6 +2047,7 @@ smb2_duplicate_extents(const unsigned int xid, struct inode *inode; struct duplicate_extents_to_file dup_ext_buf; struct cifs_tcon *tcon = tlink_tcon(trgtfile->tlink); + loff_t old_size; /* server fileays advertise duplicate extent support with this flag */ if ((le32_to_cpu(tcon->fsAttrInfo.Attributes) & @@ -2062,7 +2063,8 @@ smb2_duplicate_extents(const unsigned int xid, src_off, dest_off, len); inode = d_inode(trgtfile->dentry); - if (inode->i_size < dest_off + len) { + old_size = i_size_read(inode); + if (old_size < dest_off + len) { rc = smb2_set_file_size(xid, tcon, trgtfile, dest_off + len, false); if (rc) goto duplicate_extents_out; @@ -2074,7 +2076,7 @@ smb2_duplicate_extents(const unsigned int xid, * size will be queried on next revalidate, but it is important * to make sure that file's cached size is updated immediately */ - cifs_setsize(inode, dest_off + len); + cifs_setsize(inode, old_size, dest_off + len); } rc = SMB2_ioctl(xid, tcon, trgtfile->fid.persistent_fid, trgtfile->fid.volatile_fid, @@ -3520,7 +3522,7 @@ static long smb3_simple_falloc(struct file *file, struct cifs_tcon *tcon, struct cifsFileInfo *cfile = file->private_data; long rc = -EOPNOTSUPP; unsigned int xid; - loff_t new_eof; + loff_t old_eof, new_eof; xid = get_xid(); @@ -3551,12 +3553,13 @@ static long smb3_simple_falloc(struct file *file, struct cifs_tcon *tcon, if (cifsi->cifsAttrs & FILE_ATTRIBUTE_SPARSE_FILE) smb2_set_sparse(xid, tcon, cfile, inode, false); + old_eof = i_size_read(inode); new_eof = off + len; rc = SMB2_set_eof(xid, tcon, cfile->fid.persistent_fid, cfile->fid.volatile_fid, cfile->pid, new_eof); if (rc == 0) { cifsi->server_eof = new_eof; - cifs_setsize(inode, new_eof); + cifs_setsize(inode, old_eof, new_eof); cifs_truncate_page(inode->i_mapping, inode->i_size); truncate_setsize(inode, new_eof); } -- 2.18.0.huawei.25