mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH] mm/swapfile: drop a large unmapped swap-in folio in unuse_pte()
@ 2026-10-30 19:11 Mani Tofigh via B4 Relay
  2026-10-30 22:15 ` David Hildenbrand (Arm)
  2026-10-30 22:54 ` Andrew Morton
  0 siblings, 2 replies; 3+ messages in thread
From: Mani Tofigh via B4 Relay @ 2026-10-30 19:11 UTC (permalink / raw)
  To: Andrew Morton, Chris Li, Kairui Song
  Cc: Kemeng Shi, Nhat Pham, Baoquan He, Barry Song, Youngjun Park,
	David Hildenbrand, linux-mm, linux-kernel, tz2294, stable,
	Mani Tofigh

From: Mani Tofigh <mt4022@columbia.edu>

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!
  [    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.

Fixes: f1879e8a0c60 ("mm, swap: never bypass the swap cache even for SWP_SYNCHRONOUS_IO")
Cc: stable@vger.kernel.org
Signed-off-by: Mani Tofigh <mt4022@columbia.edu>
---
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@kernel.org/

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;
+	}
+
 	swapcache = folio;
 	folio = ksm_might_need_to_copy(folio, vma, addr);
 	if (unlikely(!folio))

---
base-commit: 5e6c45aaaf93343c7f8a1cde9797080238af5c1b
change-id: 20261030-swapin-large-unmapped-6c8f6e691864

Best regards,
--  
Mani Tofigh <mt4022@columbia.edu>



^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: [PATCH] mm/swapfile: drop a large unmapped swap-in folio in unuse_pte()
  2026-10-30 19:11 [PATCH] mm/swapfile: drop a large unmapped swap-in folio in unuse_pte() Mani Tofigh via B4 Relay
@ 2026-10-30 22:15 ` David Hildenbrand (Arm)
  2026-10-30 22:54 ` Andrew Morton
  1 sibling, 0 replies; 3+ messages in thread
From: David Hildenbrand (Arm) @ 2026-10-30 22:15 UTC (permalink / raw)
  To: mt4022, Andrew Morton, Chris Li, Kairui Song
  Cc: Kemeng Shi, Nhat Pham, Baoquan He, Barry Song, Youngjun Park,
	linux-mm, linux-kernel, tz2294, stable

On 10/30/26 20:11, Mani Tofigh via B4 Relay wrote:
> From: Mani Tofigh <mt4022@columbia.edu>
> 

Thanks for the report!

> 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():

Ugh.

Why is this special to large folios? I guess because a single PTE keeps the
large folio alive, right?

The problem really is that unuse_pte() open-codes whet do_swap_page() would
usually do. And it doesn't handle large folios in this scenario correctly.

What a mess.

I guess we could just teach unuse_pte() to only map a single PTE.

folio_add_new_anon_rmap() assumes the folios is fully mapped. We could add
up to twp folio_remove_rmap_ptes() calls with everything except the page we want
to map.

But reading below ...

> 
>   [    6.424485] kernel BUG at mm/memory.c:5195!
>   [    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.

We indeed have this

if (!folio_test_anon(folio) && folio_test_large(folio) &&
    nr_pages != folio_nr_pages(folio)) {
	...
}

check.

The nr_pages check makes sense as in your case below it's simply nr_pages == 1.

> 
> 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.

Rings a bell :)

> 
> Fixes: f1879e8a0c60 ("mm, swap: never bypass the swap cache even for SWP_SYNCHRONOUS_IO")
> Cc: stable@vger.kernel.org
> Signed-off-by: Mani Tofigh <mt4022@columbia.edu>
> ---
> 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.

It will likely get applied on top of

	git git://git.kernel.org/pub/scm/linux/kernel/git/mm/core.git for-next

Tomorrow and then quickly find its way into next. So if a resend is required,
best to send against that.

> 
> [1] https://lore.kernel.org/all/20261029-fixes_core-v2-41-a64ce7f83abb@kernel.org/
> 
> 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;
> +	}

I don't enjoy the additional code duplication, including the comment.

Can you pack the existing handling in do_swap_page() into a helper, pass
nr_pages, and reuse it here?

I guess finding a good name is the main challenge ...


static bool swapcache_folio_mappable(struct folio *folio,
		unsigned int nr_pages)
{
	if (!folio_test_anon(folio) || !folio_test_large(folio) ||
	    nr_pages == folio_nr_pages(folio))
		return true;

	if (!WARN_ON_ONCE(folio_test_dirty(folio)))
		swap_cache_del_folio(folio);

	return false;
}


Just a thought.

But it does look like the right fix.

-- 
Cheers,

David

^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: [PATCH] mm/swapfile: drop a large unmapped swap-in folio in unuse_pte()
  2026-10-30 19:11 [PATCH] mm/swapfile: drop a large unmapped swap-in folio in unuse_pte() Mani Tofigh via B4 Relay
  2026-10-30 22:15 ` David Hildenbrand (Arm)
@ 2026-10-30 22:54 ` Andrew Morton
  1 sibling, 0 replies; 3+ messages in thread
From: Andrew Morton @ 2026-10-30 22:54 UTC (permalink / raw)
  To: Mani Tofigh
  Cc: Chris Li, Kairui Song, Kemeng Shi, Nhat Pham, Baoquan He,
	Barry Song, Youngjun Park, David Hildenbrand, linux-mm,
	linux-kernel, tz2294, stable

On Fri, 30 Oct 2026 15:11:56 -0400 Mani Tofigh <mt4022@columbia.edu> wrote:

> 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!
> 
> ...
>
> 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.
> 
> --- 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;
> +	}

Let's not WARN here.  If it hits and someone reports it, what do we
tell them?  "uh, that's working as designed, no action needed".

Also, won't this change cause try_to_unuse() to keep spinning away until
someone kills it?  Might be better to return -EBUSY and fail the
swapoff?


^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2026-10-30 22:54 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-30 19:11 [PATCH] mm/swapfile: drop a large unmapped swap-in folio in unuse_pte() Mani Tofigh via B4 Relay
2026-10-30 22:15 ` David Hildenbrand (Arm)
2026-10-30 22:54 ` Andrew Morton

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®