[PATCH v3] ubifs: zero stale data beyond EOF before file size grows

sashiko-bot at kernel.org sashiko-bot at kernel.org
Fri Oct 23 00:35:02 PDT 2026


Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Critical] ubifs: unlocked ubifs_zero_eof_hole() in ubifs_write_iter() corrupts data
--

commit f10b956c6f33e7d0c8aba1ed8f979c3d9e1197bf
Author: Nan Lin <lin.nan at h3c.com>

ubifs: zero stale data beyond EOF before file size grows

This commit adds ubifs_zero_eof_hole() and calls it from do_setattr() and
ubifs_write_iter() to zero stale post-EOF data in the cached old-EOF folio
before extending a file's size.

> diff --git a/fs/ubifs/file.c b/fs/ubifs/file.c
> index e73c28b12f97f..e94fbcae6d3d5 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);
> +}

[ ... ]

> @@ -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;

[Severity: Critical]
Unlike do_setattr() (called with inode_lock(inode) held via notify_change())
and ext4_buffered_write_iter() -> ext4_write_checks() ->
ext4_block_zero_eof(), ubifs_write_iter() calls ubifs_zero_eof_hole() before
generic_file_write_iter() acquires inode_lock(inode) and runs
generic_write_checks(iocb, from):

mm/filemap.c:generic_file_write_iter() {
	inode_lock(inode);
	ret = generic_write_checks(iocb, from);
	if (ret > 0)
		ret = __generic_file_write_iter(iocb, from);
	inode_unlock(inode);
}

Can calling ubifs_zero_eof_hole() without holding inode_lock(inode) and
before generic_write_checks() cause data corruption and other regressions?

1. If ubifs_zero_eof_hole() reads an initial old_size in folio 0 (for
example, old_size = 100) and calls filemap_lock_folio() on index 0, a
concurrent writer or extending ftruncate() holding inode_lock(inode) can
extend inode->i_size into a later folio (for example, old_size becomes 5100
in folio 1, while new_end is 10000 in folio 2) before folio 0 is locked.
At that point, all of folio 0 is valid committed file data.

When ubifs_zero_eof_hole() acquires the lock on folio 0 and re-reads
old_size = i_size_read(inode), new_end <= old_size is still false, and there
is no check that folio_contains(folio, old_size >> PAGE_SHIFT). Computing
from = offset_in_folio(folio, old_size) masks the new old_size into folio 0
and calls folio_zero_range(folio, 1004, 3092) on folio 0, zeroing committed
in-bounds file data while leaving the actual old-EOF folio (folio 1)
unzeroed.

A concurrent shrinking truncate that moves i_size into an earlier folio
before truncate_pagecache() evicts the locked folio will similarly zero the
wrong folio. And if a concurrent extending ftruncate() updates i_size in
truncate_setsize() right after ubifs_zero_eof_hole() re-reads old_size,
userspace mmap writes to the newly in-bounds region can be overwritten by
folio_zero_range().

2. Because inode_lock(inode) is not held and folio_lock is dropped before
update_mctime() (which can sleep in ubifs_budget_space()) and before
generic_file_write_iter() locks the inode, a concurrent truncate or write can
change i_size or an mmap write can re-dirty the post-EOF tail in the window
between ubifs_zero_eof_hole() and __generic_file_write_iter(), leaving stale
data beyond EOF unzeroed.

3. Because generic_write_checks() has not run yet:
- For O_APPEND or RWF_APPEND writes where ki_pos was positioned past EOF,
  generic_write_checks_count() later resets iocb->ki_pos to i_size_read(inode)
  so no hole is created, but ubifs_zero_eof_hole() has already modified the
  page cache.
- For zero-length writes (iov_iter_count(from) == 0) with ki_pos > i_size,
  generic_write_checks() returns 0 without extending the file, after
  ubifs_zero_eof_hole() has already zeroed the folio tail.
- For RWF_NOWAIT writes (which generic_write_checks_count() rejects with
  -EINVAL since FOP_BUFFER_WASYNC is not set) or writes failing
  generic_write_check_limits() (-EFBIG), ubifs_zero_eof_hole() can block on
  filemap_lock_folio() and mutate the page cache before the write is
  rejected.

Should ubifs_write_iter() acquire inode_lock(inode) and run
generic_write_checks() before calling update_mctime(),
ubifs_zero_eof_hole(), and __generic_file_write_iter(), or otherwise ensure
that ubifs_zero_eof_hole() runs under inode_lock(inode) after write checks?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261023071141.3507024-1-lin.nan@h3c.com?part=1



More information about the linux-mtd mailing list