[PATCH v4 01/18] f2fs: read i_size once in f2fs_vm_page_mkwrite
From: Nanzhe Zhao
Date: Tue Oct 13 2026 - 08:10:21 EST
From: Nanzhe Zhao <zhaonanzhe@xxxxxxxxxx>
f2fs_vm_page_mkwrite() reads i_size twice: once to check whether the
folio is partially beyond EOF, and once to calculate the offset for
folio_zero_segment(). But a concurrent buffered write can extend isize
and cause the following race condition to corrupt data:
buffered write writes [0,3500)
CPU0 (write fault) CPU1 (buffered write)
---- ----
f2fs_vm_page_mkwrite()
folio_lock(folio)
if (((loff_t)(folio->index + 1) << PAGE_SHIFT) >
i_size_read(inode))
/* i_size_read() = 3500 */
write(fd, buf, 200, 4096)
/* writes into page1, does not
wait for folio lock */
i_size_write(inode, 4296)
offset = i_size_read(inode) & ~PAGE_MASK
/* i_size_read() = 4296, offset = 200 */
folio_zero_segment(folio, offset, folio_size(folio))
/* valid data [200, 3500) is zeroed */
Fix this by reading i_size once and using the same value for both the
EOF check and the zeroing offset.
Reported-by: Sashiko <sashiko-bot@xxxxxxxxxx>
Closes: https://sashiko.dev/#/patchset/20261012030315.978375-1-zhaonanzhe%40xiaomi.com
Fixes: fbfa2cc58d53 ("f2fs: add file operations")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Nanzhe Zhao <zhaonanzhe@xxxxxxxxxx>
---
fs/f2fs/file.c | 14 ++++++++++----
1 file changed, 10 insertions(+), 4 deletions(-)
diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c
index ed1b944f8..fc959cdb9 100644
--- a/fs/f2fs/file.c
+++ b/fs/f2fs/file.c
@@ -125,6 +125,7 @@ static vm_fault_t f2fs_vm_page_mkwrite(struct vm_fault *vmf)
struct f2fs_sb_info *sbi = F2FS_I_SB(inode);
struct dnode_of_data dn;
bool need_alloc = !f2fs_is_pinned_file(inode);
+ loff_t isize;
int err = 0;
vm_fault_t ret;
@@ -188,8 +189,14 @@ static vm_fault_t f2fs_vm_page_mkwrite(struct vm_fault *vmf)
filemap_invalidate_lock_shared(inode->i_mapping);
folio_lock(folio);
+ /*
+ * A concurrent buffered write can extend isize, so we store isize in a
+ * local variable to let the zeroing offset match the EOF check below.
+ */
+ isize = i_size_read(inode);
+
if (unlikely(folio->mapping != inode->i_mapping ||
- folio_pos(folio) > i_size_read(inode) ||
+ folio_pos(folio) > isize ||
!folio_test_uptodate(folio))) {
folio_unlock(folio);
err = -EFAULT;
@@ -225,11 +232,10 @@ static vm_fault_t f2fs_vm_page_mkwrite(struct vm_fault *vmf)
goto out_sem;
/* page is wholly or partially inside EOF */
- if (((loff_t)(folio->index + 1) << PAGE_SHIFT) >
- i_size_read(inode)) {
+ if (((loff_t)(folio->index + 1) << PAGE_SHIFT) > isize) {
loff_t offset;
- offset = i_size_read(inode) & ~PAGE_MASK;
+ offset = isize & ~PAGE_MASK;
folio_zero_segment(folio, offset, folio_size(folio));
}
folio_mark_dirty(folio);
--
2.43.0