Re: [PATCH] mm/swapfile: drop a large unmapped swap-in folio in unuse_pte()
From: Kairui Song
Date: Sat Oct 31 2026 - 01:55:13 EST
On Sat, Oct 31, 2026 at 3:12 AM Mani Tofigh via B4 Relay
<devnull+mt4022.columbia.edu@xxxxxxxxxx> wrote:
>
> From: Mani Tofigh <mt4022@xxxxxxxxxxxx>
>
> Since commit f1879e8a0c60 ("mm, swap: never bypass the swap cache even
> for SWP_SYNCHRONOUS_IO"), a fault can read a swapped-out mTHP range into
> one large folio in the swap cache. The folio only becomes anon once
> do_swap_page() maps all of it, so if the faulting PTE changes in the
> meantime the folio stays in the swap cache while not being mapped or
> anon:
>
> Task A (fault) Task B
> do_swap_page()
> /* swaps in large folio F */
> madvise(MADV_DONTNEED)
> /* zaps the faulting PTE */
> /* PTE changed (F not mapped) */
>
> swapoff then maps it, which marks all of its pages as mapped and
> exclusive, and the next fault on one of the other PTEs crashes in
> do_swap_page():
>
> [ 6.424485] kernel BUG at mm/memory.c:5195!
So what's being triggered here is this right?
BUG_ON(folio_test_anon(folio) && PageAnonExclusive(page));
That check is a bit overkill to be a BUG I think, and we have been trying
to remove unnecessary kernel panics. Before f1879e8a0c60 we can occasionally
see large folios got stuck in swap cache and partially mapped (e.g. failed
writeback, see activate_locked: in vmscan.c, much less likely but still
occurs from time to time), but the ones still referenced by swap PTEs don't
have PAE set so we are good.
Now the problem here is unuse_pte() calls folio_add_new_anon_rmap()
while only one sub PTE is mapped. So yeah, that's a problem.
Still it doesn't have to panic the kernel though.
> [ 6.424503] Oops: invalid opcode: 0000 [#1] SMP KASAN NOPTI
> [ 6.427654] CPU: 0 UID: 0 PID: 67 Comm: swapmthp Tainted: G W 7.3.0-17087-gd865f2677c44 #1 PREEMPT
> [ 6.432665] RIP: 0010:do_swap_page+0x24eb/0x2fa0
> [ 6.451035] Call Trace:
> [ 6.451567] <TASK>
> [ 6.453841] __handle_mm_fault+0x13b5/0x3b40
> [ 6.458525] handle_mm_fault+0x16f/0x480
> [ 6.461143] do_user_addr_fault+0x312/0xa30
> [ 6.462044] exc_page_fault+0x66/0xc0
> [ 6.462846] asm_exc_page_fault+0x26/0x30
> [ 6.476708] </TASK>
>
> do_swap_page() already drops such a folio from the swap cache when it
> can't map all of it. Do the same in unuse_pte() so that swapoff swaps
> the entries in again. With the fix, nothing is reported.
>
> This bug was identified and confirmed with the help of a deterministic
> hypervisor currently under development. The C reproducer below however
> hits it within a second.
Great working catching this!
>
> Fixes: f1879e8a0c60 ("mm, swap: never bypass the swap cache even for SWP_SYNCHRONOUS_IO")
> Cc: stable@xxxxxxxxxxxxxxx
> Signed-off-by: Mani Tofigh <mt4022@xxxxxxxxxxxx>
> ---
> This conflicts in context with patch 41/47 of David's v2 series [1],
> which changes the line right below in unuse_pte() for KSM folios. I
> can rebase on top of it once it's applied if needed.
>
> [1] https://lore.kernel.org/all/20261029-fixes_core-v2-41-a64ce7f83abb@xxxxxxxxxx/
>
> Reproducer: https://github.com/manitofigh/files/blob/main/linux-repros/swapmthp.c
> ---
> mm/swapfile.c | 12 ++++++++++++
> 1 file changed, 12 insertions(+)
>
> diff --git a/mm/swapfile.c b/mm/swapfile.c
> index 189f04d8838f..b0b75ff16835 100644
> --- a/mm/swapfile.c
> +++ b/mm/swapfile.c
> @@ -2518,6 +2518,18 @@ static int unuse_pte(struct vm_area_struct *vma, pmd_t *pmd,
> if (!folio_matches_swap_entry(folio, entry))
> return 0;
>
> + /*
> + * A large folio that is not anon yet was swapped in by a page fault
> + * that did not map it (yet). It can only be mapped as a whole, by
> + * do_swap_page(). Drop it from the swap cache and swap in small
> + * folios instead.
> + */
> + if (unlikely(folio_test_large(folio) && !folio_test_anon(folio))) {
> + if (!WARN_ON_ONCE(folio_test_dirty(folio)))
> + swap_cache_del_folio(folio);
> + return 0;
> + }
Force dropping a large folio for any swapoff doesn't seems very
reasonable.. We really should reuse the common large swapin code between
unuse_pte and the ordinary fault processing code, and there has been quite
some patches just for unuse_pte just because its out of sync with
do_swap_page.
But, having this fix first and backport for stable looks good to me, clean
up and improvement can come later and I'm trying to properly split the
folios when the PTE backing it is partially gone during swapin, that seems
much more reasonable.
Introducing a helper to avoid code duplication from this fix is also a
good idea.