[PATCH v3] ubifs: zero stale data beyond EOF before file size grows
Nan Lin
lin.nan at h3c.com
Fri Oct 23 00:11:41 PDT 2026
mmap writes can dirty the page cache beyond EOF without changing
i_size. A later extending write or ftruncate turns that region into a
hole, but UBIFS never zeroes the partial page at the old EOF, so reads
expose the stale data.
Zero the tail of the cached old-EOF folio in both size-extending
paths: ubifs_write_iter() for writes starting beyond EOF, and
do_setattr() before truncate_setsize() grows the size. This mirrors
what ext4 does on size extension;
All zeroing takes the old-EOF folio lock without ui->ui_mutex, keeping
the folio_lock -> ui_mutex order of ->writepage(); the reverse order
deadlocks.
UBIFS does not enable large folios, so the EOF folio is always a
single page today; nevertheless, use folio-relative arithmetic
(offset_in_folio(), folio_contains(), folio_size()) throughout so the
code is correct if that ever changes.
Signed-off-by: Nan Lin <lin.nan at h3c.com>
Changes since v2:
- Move the extending-write check from ubifs_write_begin() to
ubifs_write_iter(), mirroring ext4_write_checks() ->
ext4_block_zero_eof(): the hole zeroing runs once per write()
syscall instead of once per ->write_begin() iteration (suggested
by Zhihao Cheng).
- Drop the ubifs_vm_page_mkwrite() zeroing: like ext4
(ext4_block_write_begin() with len = i_size - folio_pos),
->page_mkwrite() does not zero the tail beyond EOF, and mmap
writes past EOF are the user's responsibility (suggested by
Zhihao Cheng).
Changes since v1:
- Both zeroing sites used PAGE_SIZE-relative arithmetic
(offset_in_page()-style) while folio_zero_range() expects
folio-relative offsets. UBIFS does not currently enable large
folios, so PAGE_SIZE == folio_size() and the two coincide in
practice, but switch to offset_in_folio()/folio_size() anyway so
the code is correct if UBIFS ever opts into large folios
(suggested by Sashiko AI review).
---
fs/ubifs/file.c | 63 ++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 62 insertions(+), 1 deletion(-)
diff --git a/fs/ubifs/file.c b/fs/ubifs/file.c
index e73c28b12f97..e94fbcae6d3d 100644
--- a/fs/ubifs/file.c
+++ b/fs/ubifs/file.c
@@ -202,6 +202,53 @@ static void release_existing_page_budget(struct ubifs_info *c)
ubifs_release_budget(c, &req);
}
+/**
+ * ubifs_zero_eof_hole - zero the stale tail of the old-EOF folio.
+ * @inode: inode whose size is about to grow
+ * @new_end: new end of the file, only used to bound the tail
+ *
+ * Zero the cached folio containing the old EOF for [old EOF,
+ * min(folio end, @new_end)) so a size extension does not expose stale
+ * data left beyond EOF by mmap writes.
+ *
+ * Must be called without ui->ui_mutex held: we block on the old-EOF
+ * folio lock, and ->writepage() and ->page_mkwrite() hold that folio
+ * lock while taking ui_mutex; the reverse order deadlocks.
+ */
+static void ubifs_zero_eof_hole(struct inode *inode, loff_t new_end)
+{
+ loff_t old_size = i_size_read(inode);
+ size_t from, to;
+ struct folio *folio;
+
+ if (new_end <= old_size)
+ return;
+
+ folio = filemap_lock_folio(inode->i_mapping, old_size >> PAGE_SHIFT);
+ if (IS_ERR(folio))
+ return;
+
+ /*
+ * Re-read i_size under the folio lock: a concurrent truncate may
+ * have shrunk the file, turning this folio into valid data.
+ */
+ old_size = i_size_read(inode);
+ if (new_end <= old_size)
+ goto out;
+
+ from = offset_in_folio(folio, old_size);
+ if (!from)
+ goto out;
+ to = folio_contains(folio, new_end >> PAGE_SHIFT) ?
+ offset_in_folio(folio, new_end) : folio_size(folio);
+ if (to > from)
+ folio_zero_range(folio, from, to - from);
+
+out:
+ folio_unlock(folio);
+ folio_put(folio);
+}
+
static int write_begin_slow(struct address_space *mapping,
loff_t pos, unsigned len, struct folio **foliop)
{
@@ -1220,6 +1267,10 @@ static int do_setattr(struct ubifs_info *c, struct inode *inode,
if (attr->ia_valid & ATTR_SIZE) {
dbg_gen("size %lld -> %lld", inode->i_size, new_size);
+
+ /* Must run before the size change loses the old boundary. */
+ ubifs_zero_eof_hole(inode, new_size);
+
truncate_setsize(inode, new_size);
}
@@ -1433,7 +1484,17 @@ static int update_mctime(struct inode *inode)
static ssize_t ubifs_write_iter(struct kiocb *iocb, struct iov_iter *from)
{
- int err = update_mctime(file_inode(iocb->ki_filp));
+ struct inode *inode = file_inode(iocb->ki_filp);
+ int err;
+
+ /*
+ * The write may extend beyond EOF: zero the stale tail of the
+ * old-EOF folio first.
+ */
+ if (iocb->ki_pos > i_size_read(inode))
+ ubifs_zero_eof_hole(inode, iocb->ki_pos);
+
+ err = update_mctime(inode);
if (err)
return err;
--
2.33.0
More information about the linux-mtd
mailing list