[PATCH v2 OLK-6.6 0/2] CVE-2026-89642
Fix CVE-2026-89642 Frank Sorenson (1): cifs: call pagecache_isize_extended() in cifs_setsize() when extending Zizhi Wo (1): smb: client: clear post-EOF pagecache when extending a file via truncate fs/smb/client/cifsfs.h | 2 +- fs/smb/client/inode.c | 74 +++++++++++++++++++++++++++++++++++++++-- fs/smb/client/smb2ops.c | 14 +++++--- 3 files changed, 83 insertions(+), 7 deletions(-) -- 2.18.0.huawei.25
反馈: 您发送到kernel@openeuler.org的补丁/补丁集,已成功转换为PR! PR链接地址: https://atomgit.com/openeuler/kernel/merge_requests/29827 邮件列表地址:https://mailweb.openeuler.org/archives/list/kernel@openeuler.org/message/LVF... FeedBack: The patch(es) which you have sent to kernel@openeuler.org mailing list has been converted to a pull request successfully! Pull request link: https://atomgit.com/openeuler/kernel/merge_requests/29827 Mailing list address: https://mailweb.openeuler.org/archives/list/kernel@openeuler.org/message/LVF...
From: Frank Sorenson <sorenson@redhat.com> mainline inclusion from mainline-v7.3-rc1 commit c510edb9734af1c274d18f4f31a471a166bbc7e8 category: bugfix bugzilla: https://atomgit.com/src-openeuler/kernel/issues/18925 CVE: CVE-2026-89642 Reference: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?id=... -------------------------------- cifs_setsize() calls truncate_pagecache() but skips pagecache_isize_extended() on extension. truncate_setsize() shows the correct pattern: i_size_write(inode, newsize); if (newsize > oldsize) pagecache_isize_extended(inode, oldsize, newsize); truncate_pagecache(inode, newsize); pagecache_isize_extended() zeroes the tail of the page straddling old EOF. Without it, dirty bytes in that region can be written back to the server, exposing stale data in the newly extended range. Cc: stable@vger.kernel.org Cc: David Howells <dhowells@redhat.com> Signed-off-by: Frank Sorenson <sorenson@redhat.com> Acked-by: Namjae Jeon <linkinjeon@kernel.org> Signed-off-by: Paulo Alcantara <pc@manguebit.org> Conflicts: fs/cifs/inode.c fs/smb/client/smb2ops.c [Commit c510edb9734a touched fs/smb/client/inode.c which in this tree is fs/cifs/inode.c. This tree's cifs_setsize() lacks old_size, i_blocks clamping and inode_set_mtime_to_ts(), so only add a local old_size = i_size_read(inode) and the pagecache_isize_extended() call while keeping the existing cifs_i->time reset. Compiled objects skipped: upstream path fs/smb/client/inode.c does not exist in this tree. Add lock in smb3_simple_falloc] Signed-off-by: Cai Xinchen <caixinchen1@huawei.com> --- fs/smb/client/inode.c | 4 ++++ fs/smb/client/smb2ops.c | 3 +++ 2 files changed, 7 insertions(+) diff --git a/fs/smb/client/inode.c b/fs/smb/client/inode.c index 71154a0f51c4..d964a1f1e3d9 100644 --- a/fs/smb/client/inode.c +++ b/fs/smb/client/inode.c @@ -2907,13 +2907,17 @@ int cifs_truncate_page(struct address_space *mapping, loff_t from) void cifs_setsize(struct inode *inode, 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; + if (offset > old_size) + pagecache_isize_extended(inode, old_size, offset); truncate_pagecache(inode, offset); } diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c index 5e2ae2d95398..296dda29dca3 100644 --- a/fs/smb/client/smb2ops.c +++ b/fs/smb/client/smb2ops.c @@ -3538,6 +3538,8 @@ static long smb3_simple_falloc(struct file *file, struct cifs_tcon *tcon, return rc; } + inode_lock(inode); + /* * Extending the file */ @@ -3628,6 +3630,7 @@ static long smb3_simple_falloc(struct file *file, struct cifs_tcon *tcon, rc = 0; out: + inode_unlock(inode); if (rc) trace_smb3_falloc_err(xid, cfile->fid.persistent_fid, tcon->tid, tcon->ses->Suid, off, len, rc); -- 2.18.0.huawei.25
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
participants (2)
-
Cai Xinchen -
patchwork bot