Re: [PATCH] mm/shmem: use invalidate_lock to fix hole-punch race

From: Pedro Falcato

Date: Thu Mar 26 2026 - 13:13:45 EST


On Thu, Mar 26, 2026 at 11:26:11AM -0500, Gregory Price wrote:
> Inflating a VM's balloon while vhost-user-net fork+exec's a helper
> triggers "still mapped when deleted" on the memfd backing guest RAM:
>
> BUG: Bad page cache in process __balloon pfn:6520704
> page dumped because: still mapped when deleted
> ...
> shmem_undo_range+0x3fa/0x570
> shmem_fallocate+0x366/0x4d0
> vfs_fallocate+0x13c/0x310
>
> This BUG also resulted in guests seeing stale mappings backed by a
> zeroed page, causing guest kernel panics. I was unable to trace that
> specific interaction, but it appears to be related to THP splitting.
>
> Two races allow PTEs to be re-installed for a folio that fallocate
> is about to remove from page cache:

Hmm, I don't see how your patch fixes anything.

>
> Race 1 — fault-around (filemap_map_pages):
>
> fallocate fault-around fork
> -------- ------------ ----
> set i_private
> unmap_mapping_range()
> # zaps PTEs
> filemap_map_pages()
> # re-maps folio!
> dup_mmap()
> # child VMA
> # in tree
> shmem_undo_range()
> lock folio
> unmap_mapping_folio()
spin_lock(ptl);
> # child VMA:
> # no PTE, skip
spin_unlock(ptl);
> copy_page_range()
spin_lock(dst_ptl);
spin_lock(src_ptl);
/* does not copy PTE. either
* we find a zapped PTE, or unmap_mapping_folio()
* finds two mappings instead of one. */
> # copies PTE
> # parent VMA:
> # zaps PTE
> filemap_remove_folio()
> # mapcount=1, BUG!
>
> filemap_map_pages() is called directly as .map_pages, bypassing
> shmem_fault()'s i_private synchronization.
>
> Race 2 — shmem_fault TOCTOU:
>
> fallocate shmem_fault
> -------- -----------
> check i_private → NULL
> set i_private
> unmap_mapping_range()
> # zaps PTEs
> shmem_get_folio_gfp()
> # finds folio in cache
> finish_fault()
> # installs PTE
> shmem_undo_range()
> truncate_inode_folio()
truncate_inode_folio() zaps the PTEs, thus mapcount = 0.
shmem folio is locked by both truncate and shmem_fault().
> # mapcount=1, BUG!
>
> Fix both races with invalidate_lock.
>

I don't see what you're seeing? Note that both map_pages and fault()
take the folio lock (map_pages does a trylock) to exclude against truncate
as well.

--
Pedro