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 28/32] mm: handle PMD swap entries in UFFDIO_MOVE
Date: Fri, 06 Nov 2026 18:43:09 +0000 [thread overview]
Message-ID: <sashiko-outbox-202083@kernel.org> (raw)
In-Reply-To: <20261105183940.2236717-29-usama.arif@linux.dev>
Thank you for your contribution! Sashiko AI review found 3 potential
issues to consider:
- [Medium] mm: move_swap_pmd() sets anon rmap on unmapped swap-cache
folios
- [Medium] mm: move_pages_huge_pmd() drops src_ptl before swap lookup
and split
- [Medium] mm: move_pages() splits PMD swap entries before checking
movability
--
commit 0e469173a6b536c3bd6b664124f92ddaf042afb0
Author: Usama Arif <usama.arif@linux.dev>
mm: handle PMD swap entries in UFFDIO_MOVE
This commit adds support for moving PMD-level swap entries in UFFDIO_MOVE via
a new move_swap_pmd() helper in move_pages_huge_pmd(), while rejecting PMD
swap entries at the destination in move_pages() with -EEXIST.
> diff --git a/mm/huge_memory.c b/mm/huge_memory.c
> index 65922d9e4bec1..e02a885ea1d0b 100644
> --- a/mm/huge_memory.c
> +++ b/mm/huge_memory.c
> @@ -2938,6 +2938,79 @@ int change_huge_pud(struct mmu_gather *tlb, struct vm_area_struct *vma,
> #endif
>
> #ifdef CONFIG_USERFAULTFD
> +#ifdef CONFIG_THP_SWAP
> +/*
> + * Move a PMD-level swap entry from src_pmd to dst_pmd. Both PMD locks are
> + * acquired here; src_folio (if present) must already be locked. The deposited
> + * page table backing the source THP is moved across with the entry.
> + */
> +static int move_swap_pmd(struct mm_struct *mm, struct vm_area_struct *dst_vma,
> + unsigned long dst_addr, unsigned long src_addr,
> + pmd_t *dst_pmd, pmd_t *src_pmd,
> + pmd_t orig_dst_pmd, pmd_t orig_src_pmd,
> + spinlock_t *dst_ptl, spinlock_t *src_ptl,
> + struct folio *src_folio, swp_entry_t entry)
> +{
[ ... ]
> + if (src_folio) {
> + folio_move_anon_rmap(src_folio, dst_vma);
> + src_folio->index = linear_anon_page_index(dst_vma, dst_addr);
[Severity: Medium]
Can src_folio be a newly allocated swap-cache folio that has not yet been
marked anonymous with folio_add_new_anon_rmap()?
If a swapped-out THP had its folio evicted from the swap cache and a fresh
PMD-order folio was subsequently allocated into the swap cache (for example,
via MADV_WILLNEED in swapin_walk_pmd_entry() -> swapin_pmd_swap_entry() ->
swapin_sync() -> __swap_cache_alloc_folio(), or a racing swap-in fault before
the folio is mapped), src_folio->mapping is still NULL
(!folio_test_anon(src_folio)).
Calling folio_move_anon_rmap(src_folio, dst_vma) unconditionally in
move_swap_pmd() writes dst_vma->anon_vma + FOLIO_MAPPING_ANON into
src_folio->mapping without going through folio_add_new_anon_rmap():
mm/rmap.c:folio_add_new_anon_rmap() {
...
__folio_mod_stat(folio, nr, nr_pmdmapped);
mod_mthp_stat(folio_order(folio), MTHP_STAT_NR_ANON, 1);
}
When the folio is later freed, free_pages_prepare() sees
folio_test_anon(folio) and decrements MTHP_STAT_NR_ANON without a matching
increment:
mm/page_alloc.c:free_pages_prepare() {
...
if (folio_test_anon(folio)) {
mod_mthp_stat(order, MTHP_STAT_NR_ANON, -1);
folio->mapping = NULL;
}
}
Stamping FOLIO_MAPPING_ANON early also defeats the !folio_test_anon(folio)
checks in unuse_pmd_entry(), do_huge_pmd_swap_page(), and do_swap_page() that
delete newly allocated swap-cache folios on PTE fallback or read failure:
mm/swapfile.c:unuse_pmd_entry() {
...
if (ret && folio_matches_swap_entry(folio, entry) &&
(!folio_test_uptodate(folio) || !folio_test_anon(folio)))
swap_cache_del_folio(folio);
}
Should move_swap_pmd() check folio_test_anon(src_folio) before calling
folio_move_anon_rmap() and updating src_folio->index?
[ ... ]
> @@ -2972,11 +3045,95 @@ int move_pages_huge_pmd(struct mm_struct *mm, pmd_t *dst_pmd, pmd_t *src_pmd, pm
> }
>
> if (!pmd_trans_huge(src_pmdval)) {
> - spin_unlock(src_ptl);
> if (pmd_is_migration_entry(src_pmdval)) {
> + spin_unlock(src_ptl);
> pmd_migration_entry_wait(mm, src_pmd);
> return -EAGAIN;
> }
> +#ifdef CONFIG_THP_SWAP
> + if (pmd_is_swap_entry(src_pmdval)) {
> + swp_entry_t entry;
> + struct swap_info_struct *si;
> + enum swap_pmd_cache cache_state;
> +
> + /*
> + * UFFDIO_MOVE on anon mappings requires single-owner
> + * semantics; refuse to move a shared swap entry.
> + */
> + if (!pmd_swp_exclusive(src_pmdval)) {
> + spin_unlock(src_ptl);
> + return -EBUSY;
> + }
> +
> + entry = softleaf_from_pmd(src_pmdval);
> + spin_unlock(src_ptl);
> +
> + /*
> + * Pin the swap device against a racing swapoff. NULL
> + * means swapoff is in progress, which resolves on its
> + * own, so ask the caller to retry. An error pointer
> + * means the entry names no swap device at all: that
> + * never resolves, so report it instead of spinning in
> + * the caller's -EAGAIN loop.
> + */
> + si = get_swap_device(entry);
> + if (!si)
> + return -EAGAIN;
> + if (IS_ERR(si))
> + return PTR_ERR(si);
> +
> + src_folio = NULL;
> + cache_state = swap_pmd_cache_lookup(entry, &src_folio);
> + if (cache_state == SWAP_PMD_CACHE_SPLIT) {
> + put_swap_device(si);
> + __split_huge_pmd(src_vma, src_pmd, src_addr);
> + return -EAGAIN;
> + }
[Severity: Medium]
What happens if src_pmd changes after spin_unlock(src_ptl) before
get_swap_device(), swap_pmd_cache_lookup(), and __split_huge_pmd() run in
move_pages_huge_pmd()?
Because src_ptl is released before calling get_swap_device(entry) and
swap_pmd_cache_lookup(entry, &src_folio), and __split_huge_pmd() is called
without verifying that src_pmd still matches src_pmdval:
1. A racing swap-in fault, MADV_WILLNEED, or swapoff can swap the THP back in
(making src_pmd a present THP) or populate a PMD-sized swap-cache folio
while those swap slots are reused or partially populated as order-0 folios.
If swap_pmd_cache_lookup() then sees SWAP_PMD_CACHE_SPLIT,
move_pages_huge_pmd() unconditionally calls __split_huge_pmd(src_vma,
src_pmd, src_addr), shattering a present THP (whose folio will then be
split in move_pages_ptes() on retry) or splitting a PMD swap entry backed
by a PMD-sized swap-cache folio so the retry in move_pages_ptes() fails
with -EBUSY.
2. A racing swapoff followed by swapon of a smaller swap device on the same
swap type can complete after spin_unlock(src_ptl) and before
get_swap_device(entry), causing get_swap_device() to log a false "Bad swap
offset" error and return ERR_PTR(-EIO), failing UFFDIO_MOVE with -EIO
instead of retrying.
By comparison, swapin_pmd_swap_entry() in mm/madvise.c performs
swap_pmd_cache_lookup() and get_swap_device() while still holding the PMD
lock, and swapin_walk_pmd_entry() rechecks pmd_same() before calling
__split_huge_pmd():
mm/madvise.c:swapin_pmd_swap_entry() {
...
cache_state = swap_pmd_cache_lookup(entry, &folio);
...
si = get_swap_device(entry);
spin_unlock(ptl);
}
Could move_pages_huge_pmd() keep src_ptl held across get_swap_device() and
swap_pmd_cache_lookup(), and split under src_ptl via split_huge_pmd_locked()
(or recheck pmd_same() before splitting)?
[ ... ]
> diff --git a/mm/userfaultfd.c b/mm/userfaultfd.c
> index 8cff0f820ca51..38b63e9cff62d 100644
> --- a/mm/userfaultfd.c
> +++ b/mm/userfaultfd.c
> @@ -2072,6 +2072,20 @@ static ssize_t move_pages(struct userfaultfd_ctx *ctx, unsigned long dst_start,
> break;
> }
>
> + /*
> + * A PMD swap entry at dst is a swapped-out THP, not a hole,
> + * and unlike a PMD migration entry it will not resolve on its
> + * own. Nothing below faults it back in: pte_alloc() skips a
> + * !pmd_none PMD, pte_offset_map_rw_nolock() then fails on the
> + * non-present PMD, and the -EAGAIN that produces would be
> + * retried forever by the loop below. Be strict, exactly as for
> + * a present THP.
> + */
> + if (unlikely(pmd_is_swap_entry(dst_pmdval))) {
> + err = -EEXIST;
> + break;
> + }
> +
> ptl = pmd_trans_huge_lock(src_pmd, src_vma);
> if (ptl) {
> /* Check if we can move the pmd without splitting it. */
[Severity: Medium]
When move_splits_huge_pmd() or !pmd_none(dst_pmdval) is true in move_pages(),
doesn't the fallback path unconditionally call split_huge_pmd() on a source
PMD swap entry before checking whether the resulting PTE swap entries can be
moved?
In mm/userfaultfd.c:move_pages(), the split branch only checks exclusivity
when pmd_present(*src_pmd) is true:
mm/userfaultfd.c:move_pages() {
...
if (move_splits_huge_pmd(dst_addr, src_addr, src_start + len) ||
!pmd_none(dst_pmdval)) {
/* Can be a migration entry */
if (pmd_present(*src_pmd)) {
struct folio *folio = pmd_folio(*src_pmd);
if (!is_huge_zero_folio(folio) &&
!PageAnonExclusive(&folio->page)) {
spin_unlock(ptl);
err = -EBUSY;
break;
}
}
spin_unlock(ptl);
split_huge_pmd(src_vma, src_pmd, src_addr);
/* The folio will be split by move_pages_pte() */
continue;
}
}
If *src_pmd is a PMD swap entry and UFFDIO_MOVE is called with a sub-PMD
length, an unaligned address, or a destination PMD that already has an empty
PTE page table (!pmd_none(dst_pmdval)):
1. If !pmd_swp_exclusive(*src_pmd), split_huge_pmd() shatters the shared PMD
swap entry into PTE swap entries before move_pages_ptes() rejects them
with -EBUSY.
2. If *src_pmd is exclusive and backed by a PMD-sized folio in the swap cache
(SWAP_PMD_CACHE_HUGE), split_huge_pmd() shatters the PMD swap entry into
PTE swap entries that still point to the large swap-cache folio. On the
next iteration, move_pages_ptes() immediately fails with -EBUSY because
folio_test_large(folio) is true:
mm/userfaultfd.c:move_pages_ptes() {
...
if (folio) {
if (folio_test_large(folio)) {
ret = -EBUSY;
folio_put(folio);
goto out;
}
}
That leaves src_pmd permanently split into PTE swap entries that even a
subsequent PMD-aligned UFFDIO_MOVE cannot move while the large folio remains
in the swap cache.
Should move_pages() check pmd_is_swap_entry(*src_pmd) before splitting, and
refuse with -EBUSY if !pmd_swp_exclusive(*src_pmd) or if the swap cache holds
a large folio?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261105183940.2236717-1-usama.arif@linux.dev?part=28
next prev parent 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 [this message]
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
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-202083@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