[PATCH v2] ubifs: zero stale data beyond EOF before file size grows
Zhihao Cheng
chengzhihao1 at huawei.com
Mon Oct 19 05:38:17 PDT 2026
在 2026/10/19 11:25, Nan Lin 写道:
Hi Nan Lin,
> 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 old-EOF folio in all three size-extending paths:
> ubifs_write_begin() for writes starting beyond EOF, do_setattr()
> before truncate_setsize() grows the size, and ubifs_vm_page_mkwrite()
> so mmap writes cannot leave such a tail behind. This mirrors what
> block_page_mkwrite() does for buffer_head filesystems and 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() and
> ->page_mkwrite(); 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 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 | 73 +++++++++++++++++++++++++++++++++++++++++++++++++
> 1 file changed, 73 insertions(+)
>
> diff --git a/fs/ubifs/file.c b/fs/ubifs/file.c
> index e73c28b12f97..1726714161a1 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;
The filemap_lock_folio gets folio with FGP_LOCK, I think we should use
'FGP_WRITEBEGIN | FGP_ACCESSED' just like ext4_load_tail_bh() does,
because the folio may not exist in ram when calling do_setattr().
> +
> + /*
> + * 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;
Why skipping 'from == 0'? What if a user writes begin from
offset-X(0<X<4096) to a new file(isize = 0)? Ext4 zero the range by
ext4_block_write_begin->folio_zero_segments(folio, to, block_end,
**block_start**, **from**), but ubifs not handle it.
> + 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)
> {
> @@ -423,6 +470,14 @@ static int ubifs_write_begin(const struct kiocb *iocb,
> if (unlikely(c->ro_error))
> return -EROFS;
>
> + /*
> + * The write starts beyond EOF: zero the stale tail of the old-EOF
> + * folio before budgeting takes ui->ui_mutex. do_readpage() only
> + * sanitizes the write folio, not the old-EOF folio.
> + */
> + if (pos > inode->i_size)
> + ubifs_zero_eof_hole(inode, pos);
> +
The ext4 protection check is ext4_write_checks->ext4_block_zero_eof, I
think we can add the zero hole check in ubifs_write_iter().
> /* Try out the fast-path part first */
> folio = __filemap_get_folio(mapping, index, FGP_WRITEBEGIN,
> mapping_gfp_mask(mapping));
> @@ -1220,6 +1275,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);
> }
>
> @@ -1494,6 +1553,7 @@ static vm_fault_t ubifs_vm_page_mkwrite(struct vm_fault *vmf)
> struct timespec64 now = current_time(inode);
> struct ubifs_budget_req req = { .new_page = 1 };
> int err, update_time;
> + loff_t i_size;
>
> dbg_gen("ino %llu, pg %lu, i_size %lld", inode->i_ino, folio->index,
> i_size_read(inode));
> @@ -1553,6 +1613,19 @@ static vm_fault_t ubifs_vm_page_mkwrite(struct vm_fault *vmf)
> filemap_dirty_folio(folio->mapping, folio);
> }
>
> + /*
> + * Zero the part of the EOF folio beyond i_size so mmap writes
> + * cannot leave a stale tail behind (see ubifs_zero_eof_hole()).
> + * For a folio-aligned i_size the mkwrite entry check has already
> + * returned SIGBUS, so @from is always non-zero here.
> + */
> + i_size = i_size_read(inode);
> + if (folio_contains(folio, i_size >> PAGE_SHIFT)) {
> + size_t from = offset_in_folio(folio, i_size);
> +
If the program enters into this branch, the ubifs file's isize is not
zero, ubifs_read_folio has already zerod tail, do we still need this
folio_zero_range?
> + folio_zero_range(folio, from, folio_size(folio) - from);
> + }
> +
> if (update_time) {
> int release;
> struct ubifs_inode *ui = ubifs_inode(inode);
>
More information about the linux-mtd
mailing list