All of 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: 46+ 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   ` Usama Arif
2026-11-05 18:38 ` [PATCH v9 06/32] s390: " Usama Arif
2026-11-06 18:43   ` sashiko-bot
2026-11-10 17:17   ` Gerald Schaefer
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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.