Re: [PATCH v2] mm: memory: fix truncation removing mapped folios
From: Pedro Falcato
Date: Tue Oct 13 2026 - 06:32:52 EST
On Sat, Oct 10, 2026 at 03:22:18PM +0800, Baolin Wang wrote:
> Ayush reported an issue that shmem/tmpfs folios are still mapped when
> truncating them on production:
> "
> BUG: Bad page cache in process ... pfn:...
> page dumped because: still mapped when deleted
> "
>
> I reproduced the issue with Ayush's reproducer [1] with the latest mm-unstable
> branch, and I got the following crash info. From the dump message, we can
> see that truncate_inode_folio() is really trying to remove mapped folios,
> which is incorrect.
>
> "
> [ 189.171977] page: refcount:3 mapcount:1 mapping:00000000fe168ee1
> index:0x3880 pfn:0x191647
> [ 189.171995] memcg:ffff0000cc073d40
> [ 189.171997] aops:shmem_aops ino:3c01 dentry name(?):"memfd:runsc-memory"
> [ 189.172006] flags: 0x17fffef0002022d(locked|referenced|uptodate|lru|
> workingset|swapbacked|node=0|zone=2|lastcpupid=0x3ffff)
> .......
> [ 189.172017] page dumped because: VM_BUG_ON_FOLIO(folio_mapped(folio))
> [ 189.172026] ------------[ cut here ]------------
> [ 189.172027] kernel BUG at mm/filemap.c:155!
> [ 189.172057] Internal error: Oops - BUG: 00000000f2000800 [#1] SMP
> .......
> [ 189.177721] CPU: 3 UID: 0 PID: 5521 Comm: repro Kdump: loaded Tainted:
> 7.3.0-rc4+ #277 PREEMPT(lazy)
> .......
> [ 189.183986] Call trace:
> [ 189.184124] filemap_unaccount_folio+0xf0/0x1e8 (P)
> [ 189.184391] __filemap_remove_folio+0x34/0x160
> [ 189.184633] filemap_remove_folio+0x4c/0xb0
> [ 189.184859] truncate_inode_folio+0x34/0x58
> [ 189.185087] shmem_undo_range+0x220/0x658
> [ 189.185308] shmem_fallocate+0x2f0/0x470
> [ 189.185527] vfs_fallocate+0x128/0x328
> ......
> [ 189.187078] el0t_64_sync+0x184/0x188
> "
>
> After analysis, I believe the race exists between truncation and MADV_DONTNEED,
> and shmem's ->map_pages() merely makes the issue easier to reproduce. Since
> MADV_DONTNEED synchronously releases the pagetable page before calling
> tlb_flush_rmaps(), this could cause another thread's truncation to skip
> zap_pte_range() (due to pmd is none) but still observe the folio's mapcount
> as non-zero. A possible race scenario is as follows:
>
> CPU 0 CPU 1
> shmem_fallocate madvise_dontneed_single_vma
> unmap_mapping_range .......
> ......
> (filemap_map_pages() remap folios)
> zap_pte_range
> truncate_inode_folio zap_empty_pte_table(pmd clear)
> unmap_mapping_folio
> ......
> zap_pmd_range (saw pmd none, skip)
>
> filemap_remove_folio
> BUG_ON(folio_mapped)
> tlb_flush_rmaps(remove rmaps)
>
> To fix this issue, we should move the pmd clear operation (via zap_empty_pte_table())
> to after tlb_flush_rmaps() and add smp_wmb()/smp_rmb() memory barriers, so that
> when we find a pmd_none() while unmapping a folio without holding the PTL, the
> folio's mappings are guaranteed to have been removed via tlb_flush_rmaps().
>
> [1] https://lore.kernel.org/all/20260925065013.3682431-1-ayushr@xxxxxxxxx/
> Reported-by: Ayush Ranjan <ayushr@xxxxxxxxx>
> Closes: https://lore.kernel.org/all/20260924061708.1645968-1-ayushr@xxxxxxxxx/
> Fixes: 6375e95f381e ("mm: pgtable: reclaim empty PTE page in madvise(MADV_DONTNEED)")
> Signed-off-by: Baolin Wang <baolin.wang@xxxxxxxxxxxxxxxxx>
Reviewed-by: Pedro Falcato <pfalcato@xxxxxxx>
Mostly LGTM, thanks!
> ---
> Changes from v1:
> - Add an smp_rmb() to pair with the smp_wmb() (Per Lorenzo and Pedro).
> ---
> mm/memory.c | 29 ++++++++++++++++++++---------
> 1 file changed, 20 insertions(+), 9 deletions(-)
>
> diff --git a/mm/memory.c b/mm/memory.c
> index 6a8e7772b8d6..c3a1faf580c8 100644
> --- a/mm/memory.c
> +++ b/mm/memory.c
> @@ -2036,6 +2036,15 @@ static unsigned long zap_pte_range(struct mmu_gather *tlb,
> }
> } while (pte += nr, addr += PAGE_SIZE * nr, addr != end);
>
> + add_mm_rss_vec(mm, rss);
> + lazy_mmu_mode_disable();
> +
> + /* Do the actual TLB flush before dropping ptl */
> + if (force_flush) {
> + tlb_flush_mmu_tlbonly(tlb);
> + tlb_flush_rmaps(tlb, vma);
> + }
> +
> /*
> * Fast path: try to hold the pmd lock and unmap the PTE page.
> *
> @@ -2044,16 +2053,13 @@ static unsigned long zap_pte_range(struct mmu_gather *tlb,
> * to ensure they are still none, thereby preventing the pte entries
> * from being repopulated by another thread.
> */
> - if (can_reclaim_pt && direct_reclaim && addr == end)
> + if (can_reclaim_pt && direct_reclaim && addr == end) {
> + /*
> + * The rmap changes need to be observed before PTEs get zapped.
> + * Pairs with smp_rmb() in zap_pmd_range().
> + */
I'd amend this comment to something like:
/*
* rmap changes need to be observed before page tables get zapped.
* Pairs with smp_rmb() in zap_pmd_range().
*/
> + smp_wmb();
> direct_reclaim = zap_empty_pte_table(mm, pmd, ptl, &pmdval);
> -
> - add_mm_rss_vec(mm, rss);
> - lazy_mmu_mode_disable();
> -
> - /* Do the actual TLB flush before dropping ptl */
> - if (force_flush) {
> - tlb_flush_mmu_tlbonly(tlb);
> - tlb_flush_rmaps(tlb, vma);
> }
> pte_unmap_unlock(start_pte, ptl);
>
> @@ -2108,6 +2114,11 @@ static inline unsigned long zap_pmd_range(struct mmu_gather *tlb,
> sync_with_folio_pmd_zap(tlb->mm, pmd);
> }
> if (pmd_none(*pmd)) {
> + /*
> + * Order this PMD read before the folio's mapcount read.
> + * Pairs with smp_wmb() in zap_pte_range().
> + */
and this one to something like:
/*
* Order this PMD read before any (possible) mapcount/rmap read.
* This isn't a problem in most cases, due to us synchronizing on
* the PTE lock, but such doesn't happen when PTE tables are zapped.
* Pairs with smp_wmb() in zap_pte_range().
*/
> + smp_rmb();
> addr = next;
> continue;
> }
Also, as a note, I would suggest we hold off on this fix until the merge window.
I don't think it's that urgent, and it seems like there is a possibility of a
nicer, cleaner fix. That and we need to audit pmd_none() sites for other
possible smp_rmb()'s...
--
Pedro