Linux-mm Archive on lore.kernel.org
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Usama Arif" <usama.arif@linux.dev>,
	"Andrew Morton" <akpm@linux-foundation.org>,
	david@kernel.org, chrisl@kernel.org, kasong@tencent.com,
	ljs@kernel.org, ziy@nvidia.com, linux-mm@kvack.org
Cc: chenhuacai@kernel.org, ying.huang@linux.alibaba.com,
	"Baoquan He" <baoquan.he@linux.dev>,
	willy@infradead.org, youngjun.park@lge.com, hannes@cmpxchg.org,
	riel@surriel.com, shakeel.butt@linux.dev, alex@ghiti.fr,
	kas@kernel.org, baohua@kernel.org, dev.jain@arm.com,
	baolin.wang@linux.alibaba.com,
	"Nico Pache" <nico.pache@linux.dev>,
	"Liam R. Howlett" <liam@infradead.org>,
	ryan.roberts@arm.com, "Vlastimil Babka" <vbabka@kernel.org>,
	lance.yang@linux.dev, nphamcs@gmail.com,
	shikemeng@huaweicloud.com, yosry@kernel.org, qi.zheng@linux.dev,
	luizcap@redhat.com, kernel-team@meta.com
Subject: Re: [PATCH v9 30/32] mm: handle PMD swap entry faults on swap-in
Date: Fri, 06 Nov 2026 18:43:11 +0000	[thread overview]
Message-ID: <sashiko-outbox-202085@kernel.org> (raw)
In-Reply-To: <20261105183940.2236717-31-usama.arif@linux.dev>

Thank you for your contribution! Sashiko AI review found 3 potential
issues to consider:
- [Critical] mm: use-after-free in do_huge_pmd_swap_page() from
  unlocking PTL first
- [Critical] mm: unmapped PMD swap-cache folio in
  do_huge_pmd_swap_page() corrupts rmap
- [Medium] mm: missing PTE fault fallback after PMD split in
  do_huge_pmd_swap_page()
--

commit 651cc7048660407a18f57e406abe979d3a9ccbfe
Author: Usama Arif <usama.arif@linux.dev>

mm: handle PMD swap entry faults on swap-in

This commit adds do_huge_pmd_swap_page() and hooks it into
__handle_mm_fault() to fault in PMD swap entries at PMD granularity when a
PMD-sized folio is available, or split the PMD swap entry and fall back to
PTE swap-in otherwise.

> diff --git a/mm/huge_memory.c b/mm/huge_memory.c
> index e02a885ea1d0b..b8d61cdf8cbfb 100644
> --- a/mm/huge_memory.c
> +++ b/mm/huge_memory.c
[ ... ]
> @@ -2425,6 +2426,266 @@ vm_fault_t do_huge_pmd_numa_page(struct vm_fault *vmf)
>  	return 0;
>  }
>  
> +#ifdef CONFIG_THP_SWAP
> +/**
> + * do_huge_pmd_swap_page() - Handle a fault on a PMD-level swap entry.
> + * @vmf: Fault context. vmf->orig_pmd contains the swap PMD.
> + *
> + * A PMD swap entry is a compact encoding for HPAGE_PMD_NR consecutive swap
> + * slots. If the swap cache still has one PMD-sized folio covering the range,
> + * map it directly at PMD level. If the range has been split into per-page
> + * cache state, or zswap may have per-page state for it, split the PMD swap
> + * entry and retry at PTE granularity.
> + *
> + * Return: VM_FAULT_* flags.
> + */
> +vm_fault_t do_huge_pmd_swap_page(struct vm_fault *vmf)
> +{
[ ... ]
> +	if (!thp_vma_allowable_order(vma, vma->vm_flags, TVA_PAGEFAULT,
> +				     HPAGE_PMD_ORDER)) {
> +		__split_huge_pmd(vma, vmf->pmd, haddr);
> +		return 0;
> +	}
> +
> +	swp_entry = entry;
> +
> +	/* Prevent swapoff from happening to us. */
> +	si = get_swap_device(swp_entry);
> +	if (IS_ERR_OR_NULL(si)) {
> +		if (IS_ERR(si))
> +			return VM_FAULT_SIGBUS;
> +		return 0;
> +	}
> +
> +	cache_state = swap_pmd_cache_lookup(swp_entry, &folio);
> +	if (cache_state == SWAP_PMD_CACHE_SPLIT)
> +		goto split_fallback;
> +	if (!folio) {
> +		/*
> +		 * PMD swap entries encode ordinary per-page swap slots. If any
> +		 * slot is in zswap, split and let the PTE swap path load the
> +		 * range per page. Otherwise the range is all on disk and can be
> +		 * read back as one PMD-sized folio.
> +		 */
> +		if (zswap_is_present(swp_entry, HPAGE_PMD_NR))
> +			goto split_fallback;
> +
> +		folio = swapin_sync(swp_entry, GFP_HIGHUSER_MOVABLE,
> +				    BIT(HPAGE_PMD_ORDER), vmf, NULL, 0);
> +		if (IS_ERR_OR_NULL(folio))
> +			goto split_fallback;
> +
> +		/* Had to read from swap area: Major fault */
> +		ret = VM_FAULT_MAJOR;
> +		count_vm_event(PGMAJFAULT);
> +		count_memcg_event_mm(mm, PGMAJFAULT);
> +	}
> +
> +	ret |= folio_lock_or_retry(folio, vmf);
> +	if (ret & VM_FAULT_RETRY)
> +		goto out_release;
> +
> +	/* Verify the folio is still in swap cache and matches our entry */
> +	if (unlikely(!folio_matches_swap_entry(folio, swp_entry)))
> +		goto out_page;
[ ... ]
> +	/* Lock the PMD and verify it hasn't changed */
> +	vmf->ptl = pmd_lock(mm, vmf->pmd);
> +	if (unlikely(!pmd_same(vmf->orig_pmd, pmdp_get(vmf->pmd)))) {
> +		spin_unlock(vmf->ptl);
> +		goto out_page;
> +	}
[ ... ]
> +	set_pmd_at(mm, haddr, vmf->pmd, pmd);
> +	update_mmu_cache_pmd(vma, haddr, vmf->pmd);
> +
> +	/* Update orig_pmd for any follow-up wp_huge_pmd() below. */
> +	vmf->orig_pmd = pmd;
> +
> +	/*
> +	 * Conditionally try to free up the swap cache. Do it after mapping,
> +	 * so raced page faults will likely see the folio in swap cache and
> +	 * wait on the folio lock.
> +	 */
> +	if (should_try_to_free_swap(si, folio, vma, exclusive, vmf->flags))
> +		folio_free_swap(folio);
> +
> +	spin_unlock(vmf->ptl);
> +
> +	folio_unlock(folio);
> +	put_swap_device(si);

[Severity: Critical]
Can releasing vmf->ptl before calling folio_unlock(folio) in
do_huge_pmd_swap_page() lead to a use-after-free on folio?

do_huge_pmd_swap_page() holds a single reference to folio from
swap_pmd_cache_lookup() or swapin_sync(), and set_pmd_at() transfers that
reference to the PMD mapping. When should_try_to_free_swap() is true (such
as on SWP_SYNCHRONOUS_IO swap devices, exclusive write faults, or VM_LOCKED
VMAs), folio_free_swap(folio) drops the swap-cache references as well.

At that point the faulting thread no longer holds its own reference on
folio; only vmf->ptl prevents the PMD mapping reference from going away. In
mm/memory.c:do_swap_page(), folio_unlock(folio) is called before
pte_unmap_unlock(vmf->pte, vmf->ptl):

	if (should_try_to_free_swap(si, folio, vma, exclusive, vmf->flags))
		folio_free_swap(folio);

	folio_unlock(folio);
	...
	pte_unmap_unlock(vmf->pte, vmf->ptl);

With spin_unlock(vmf->ptl) happening first here, a racing MADV_DONTNEED or
munmap() in zap_huge_pmd() (or move_folios_to_lru() dropping a transient LRU
isolation reference) can unmap and free the folio while PG_locked is still
set before folio_unlock(folio) runs on this CPU. Should folio_unlock(folio)
be called before spin_unlock(vmf->ptl)?

> +
> +	/*
> +	 * If the write fault wasn't satisfied above (folio is shared without
> +	 * exclusivity), call wp_huge_pmd() to handle COW or
> +	 * userfaultfd-wp without forcing a second fault.
> +	 *
> +	 * wp_huge_pmd() may return VM_FAULT_FALLBACK if it had to split the
> +	 * PMD; that's a normal outcome, and the natural PTE-level refault will
> +	 * complete the COW. Mask it so callers (and the arch fault handler)
> +	 * don't see VM_FAULT_FALLBACK as a fatal VM_FAULT_ERROR.
> +	 */
> +	if (write && !pmd_write(pmd) && !rwp_restore) {
> +		vm_fault_t wp_ret = wp_huge_pmd(vmf);
> +
> +		wp_ret &= ~VM_FAULT_FALLBACK;
> +		ret |= wp_ret;
> +		if (ret & VM_FAULT_ERROR)
> +			ret &= VM_FAULT_ERROR;
> +	}
> +
> +	return ret;
> +
> +out_page:
> +	folio_unlock(folio);
> +out_release:
> +	folio_put(folio);
> +	put_swap_device(si);
> +	return ret;
> +
> +unlock_split_fallback:
> +	/*
> +	 * PTE fallback cannot add a single-page rmap to a PMD-sized folio that
> +	 * has never been mapped: do_swap_page() would hand the whole folio to
> +	 * folio_add_new_anon_rmap() while installing one PTE. Nor can it do
> +	 * anything useful with a folio that failed to read. Remove either from
> +	 * the swap cache so each slot is read back into its own order-0 folio.
> +	 * An uptodate anon swap-cache folio can be mapped one PTE at a time and
> +	 * must stay cached, so that any poisoned subpage stays visible to
> +	 * do_swap_page(). This mirrors unuse_pmd_entry().
> +	 */
> +	if (folio_matches_swap_entry(folio, swp_entry) &&
> +	    (!folio_test_uptodate(folio) || !folio_test_anon(folio)))
> +		swap_cache_del_folio(folio);
> +	folio_unlock(folio);
> +	folio_put(folio);

[Severity: Critical]
What happens if a newly allocated PMD-sized swap-cache folio
(!folio_test_anon(folio) or !folio_test_uptodate(folio)) exits via out_page
or out_release instead of unlock_split_fallback?

There appear to be a few paths where an unmapped PMD-sized folio remains in
the swap cache after the PMD swap entry is split or zapped:

1. If the PMD changed while swapin_sync() or folio_lock_or_retry() was
   in progress (for example, a racing thread split the PMD at
   split_fallback, or mprotect/madvise split the PMD), the !pmd_same()
   check jumps to out_page without deleting the !folio_test_anon(folio)
   (or !folio_test_uptodate(folio)) folio from the swap cache.

2. If the PMD changed because a racing zap_huge_pmd() or
   madvise_free_huge_pmd() zapped the swap entry while this thread held the
   folio lock, swap_put_entries_direct() -> __try_to_reclaim_swap() fails
   folio_trylock(folio). Unlike mm/memory.c:do_swap_page(), which calls
   folio_free_swap(folio) under out_page before unlocking the folio,
   out_page here does not call folio_free_swap(folio), leaving the folio
   and swap slots stranded in the swap cache.

3. On asynchronous swap devices, swapin_sync() inserts a locked
   !folio_test_anon(folio) PMD-sized folio into the swap cache, and
   folio_lock_or_retry() drops the fault lock and jumps to out_release with
   VM_FAULT_RETRY (and MADV_WILLNEED's swapin_pmd_swap_entry() similarly
   populates an unmapped PMD-sized folio). If the PMD is subsequently split
   before the folio is mapped at PMD level (including via the
   !thp_vma_allowable_order() check at the top of do_huge_pmd_swap_page(),
   which splits the PMD without checking the swap cache), the unmapped
   PMD-sized folio stays in the swap cache.

When a subsequent PTE fault enters mm/memory.c:do_swap_page() on the
HPAGE_PMD_NR contiguous split PTE swap entries, the large-folio PTE batching
logic batches all nr_pages = HPAGE_PMD_NR PTEs. Because nr_pages equals
folio_nr_pages(folio), the check in do_swap_page():

	if (!folio_test_anon(folio) && folio_test_large(folio) &&
	    nr_pages != folio_nr_pages(folio)) {
		if (!WARN_ON_ONCE(folio_test_dirty(folio)))
			swap_cache_del_folio(folio);
		goto out_nomap;
	}

does not trigger, and do_swap_page() passes the PMD-mappable folio to
folio_add_new_anon_rmap() while installing PTEs. Since
folio_add_new_anon_rmap() initializes any folio_test_pmd_mappable(folio)
for a PMD mapping (_entire_mapcount = 0, _large_mapcount = 1,
_nr_pages_mapped = ENTIRELY_MAPPED, and PageAnonExclusive only on subpage
0), wouldn't PTE-mapping it corrupt the rmap/mapcounts and PageAnonExclusive
state across subpages?

> diff --git a/mm/memory.c b/mm/memory.c
> index 47bba4695d855..e71c590113e07 100644
> --- a/mm/memory.c
> +++ b/mm/memory.c
[ ... ]
> @@ -6778,6 +6747,9 @@ static vm_fault_t __handle_mm_fault(struct vm_area_struct *vma,
>  
>  		if (pmd_is_migration_entry(vmf.orig_pmd))
>  			pmd_migration_entry_wait(mm, vmf.pmd);
> +		else if (IS_ENABLED(CONFIG_THP_SWAP) &&
> +			 pmd_is_swap_entry(vmf.orig_pmd))
> +			return do_huge_pmd_swap_page(&vmf);
>  		return 0;
>  	}
>  	if (pmd_trans_huge(vmf.orig_pmd)) {

[Severity: Medium]
When do_huge_pmd_swap_page() splits the PMD (either at the
!thp_vma_allowable_order() check, at split_fallback, or inside wp_huge_pmd()
when a non-exclusive swapped-in THP must be split into PTEs for COW), should
__handle_mm_fault() fall through to the fallback label and call
handle_pte_fault(&vmf) rather than returning 0 (or masking out
VM_FAULT_FALLBACK)?

Other PMD handlers in __handle_mm_fault() such as create_huge_pmd() and
wp_huge_pmd() return VM_FAULT_FALLBACK so __handle_mm_fault() jumps to
fallback: and resolves the PTE fault in the same handle_mm_fault() call.

By returning 0 after splitting the PMD swap entry (or clearing
VM_FAULT_FALLBACK when wp_huge_pmd() splits the read-only PMD into read-only
PTEs without copying), callers of handle_mm_fault() that do not loop on 0 -
such as fixup_user_fault(), fault_in_safe_writeable(), and
user_event_mm_fault_in() - see a successful return while the target PTE is
still a non-present swap entry or read-only PTE, causing subsequent
pagefault-disabled user accesses to fail with -EFAULT.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261105183940.2236717-1-usama.arif@linux.dev?part=30


  reply	other threads:[~2026-11-06 18:43 UTC|newest]

Thread overview: 43+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-05 18:38 [PATCH v9 00/32] mm: PMD-level swap entries for anonymous THPs Usama Arif
2026-11-05 18:38 ` [PATCH v9 01/32] mm: rename pmd_to_softleaf_folio() to pmd_softleaf_to_folio() Usama Arif
2026-11-05 18:38 ` [PATCH v9 02/32] arm64: mm: add PMD swap-exclusive helpers Usama Arif
2026-11-05 18:38 ` [PATCH v9 03/32] loongarch: " Usama Arif
2026-11-05 18:38 ` [PATCH v9 04/32] powerpc: " Usama Arif
2026-11-05 18:38 ` [PATCH v9 05/32] riscv: " Usama Arif
2026-11-05 18:38 ` [PATCH v9 06/32] s390: " Usama Arif
2026-11-05 18:38 ` [PATCH v9 07/32] x86: " Usama Arif
2026-11-05 18:38 ` [PATCH v9 08/32] mm: recognize PMD swap entries in the softleaf layer Usama Arif
2026-11-05 18:38 ` [PATCH v9 09/32] mm/debug_vm_pgtable: test PMD swap-exclusive helpers Usama Arif
2026-11-05 18:38 ` [PATCH v9 10/32] mm/migrate_device: avoid leaking folio references during THP splitting Usama Arif
2026-11-05 18:38 ` [PATCH v9 11/32] mm: make PMD migration-entry splitting explicit Usama Arif
2026-11-05 18:38 ` [PATCH v9 12/32] mm: split PMD swap entries into PTE swap entries Usama Arif
2026-11-05 18:38 ` [PATCH v9 13/32] mm/swap: allow duplicating a range of " Usama Arif
2026-11-05 18:38 ` [PATCH v9 14/32] mm: handle PMD swap entries in fork path Usama Arif
2026-11-05 18:38 ` [PATCH v9 15/32] mm: swap: share swap-cache allocation error cleanup Usama Arif
2026-11-06 21:10   ` Yosry Ahmed
2026-11-06 21:39   ` Nhat Pham
2026-11-05 18:39 ` [PATCH v9 16/32] mm: zswap: reject high-order swap cache allocations backed by zswap Usama Arif
2026-11-06 16:26   ` Nhat Pham
2026-11-06 21:16   ` Yosry Ahmed
2026-11-05 18:39 ` [PATCH v9 17/32] mm: swap in PMD swap entries as whole THPs during swapoff Usama Arif
2026-11-06 18:43   ` sashiko-bot
2026-11-05 18:39 ` [PATCH v9 18/32] fs/proc: account PMD swap entries in smaps Usama Arif
2026-11-05 18:39 ` [PATCH v9 19/32] mm: handle soft-dirty and uffd-wp on PMD swap entries Usama Arif
2026-11-05 18:39 ` [PATCH v9 20/32] mm/hmm: fault PMD swap entries on demand Usama Arif
2026-11-05 18:39 ` [PATCH v9 21/32] mm: free PMD swap entries in zap_huge_pmd() Usama Arif
2026-11-05 18:39 ` [PATCH v9 22/32] mm/madvise: free PMD swap entries with MADV_FREE Usama Arif
2026-11-05 18:39 ` [PATCH v9 23/32] mm/madvise: skip PMD swap entries for MADV_COLD and MADV_PAGEOUT Usama Arif
2026-11-05 18:39 ` [PATCH v9 24/32] mm/madvise: keep PMD swap entries whole for MADV_GUARD_INSTALL/REMOVE Usama Arif
2026-11-05 18:39 ` [PATCH v9 25/32] mm/mincore: report PMD swap-cache residency Usama Arif
2026-11-05 18:39 ` [PATCH v9 26/32] mm/khugepaged: treat PMD swap entries as mapped THPs Usama Arif
2026-11-05 18:39 ` [PATCH v9 27/32] mm: handle PMD swap entries in MADV_WILLNEED Usama Arif
2026-11-06 18:43   ` sashiko-bot
2026-11-05 18:39 ` [PATCH v9 28/32] mm: handle PMD swap entries in UFFDIO_MOVE Usama Arif
2026-11-06 18:43   ` sashiko-bot
2026-11-05 18:39 ` [PATCH v9 29/32] mm: don't PTE-batch a swap-in over a hardware-poisoned subpage Usama Arif
2026-11-05 18:39 ` [PATCH v9 30/32] mm: handle PMD swap entry faults on swap-in Usama Arif
2026-11-06 18:43   ` sashiko-bot [this message]
2026-11-05 18:39 ` [PATCH v9 31/32] mm: install PMD swap entries on swap-out Usama Arif
2026-11-06 18:43   ` sashiko-bot
2026-11-05 18:39 ` [PATCH v9 32/32] selftests/mm: add PMD swap entry tests Usama Arif
2026-11-06 18:43   ` sashiko-bot

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=sashiko-outbox-202085@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=akpm@linux-foundation.org \
    --cc=alex@ghiti.fr \
    --cc=baohua@kernel.org \
    --cc=baolin.wang@linux.alibaba.com \
    --cc=baoquan.he@linux.dev \
    --cc=chenhuacai@kernel.org \
    --cc=chrisl@kernel.org \
    --cc=david@kernel.org \
    --cc=dev.jain@arm.com \
    --cc=hannes@cmpxchg.org \
    --cc=kas@kernel.org \
    --cc=kasong@tencent.com \
    --cc=kernel-team@meta.com \
    --cc=lance.yang@linux.dev \
    --cc=liam@infradead.org \
    --cc=linux-mm@kvack.org \
    --cc=ljs@kernel.org \
    --cc=luizcap@redhat.com \
    --cc=nico.pache@linux.dev \
    --cc=nphamcs@gmail.com \
    --cc=qi.zheng@linux.dev \
    --cc=riel@surriel.com \
    --cc=ryan.roberts@arm.com \
    --cc=sashiko-reviews@lists.linux.dev \
    --cc=shakeel.butt@linux.dev \
    --cc=shikemeng@huaweicloud.com \
    --cc=usama.arif@linux.dev \
    --cc=vbabka@kernel.org \
    --cc=willy@infradead.org \
    --cc=ying.huang@linux.alibaba.com \
    --cc=yosry@kernel.org \
    --cc=youngjun.park@lge.com \
    --cc=ziy@nvidia.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox