* [PATCH 00/12] userfaultfd: assorted fixes (part 1)
@ 2026-11-11 17:55 Tal Zussman
2026-11-11 17:55 ` [PATCH 01/12] userfaultfd: use anonymous memory for UFFDIO_ZEROPAGE on private file mappings Tal Zussman
2026-11-11 17:55 ` [PATCH 02/12] userfaultfd: refuse UFFDIO_MOVE into a non-present huge destination PMD Tal Zussman
0 siblings, 2 replies; 5+ messages in thread
From: Tal Zussman @ 2026-11-11 17:55 UTC (permalink / raw)
To: Andrew Morton, Mike Rapoport, Peter Xu, James Houghton,
Andrea Arcangeli, Suren Baghdasaryan, Balbir Singh,
Liam R. Howlett, Lorenzo Stoakes, Vlastimil Babka, Jann Horn,
Pedro Falcato, Michał Mirosław, Andrei Vagin,
Stephen Rothwell, Arnd Bergmann, Muhammad Usama Anjum,
David Hildenbrand, Zi Yan, Matthew Brost, Joshua Hahn,
Byungchul Park, Gregory Price, Ying Huang, Alistair Popple,
Baolin Wang, Nico Pache, Ryan Roberts, Dev Jain, Barry Song,
Lance Yang, Usama Arif, Kiryl Shutsemau, Chris Down, Muchun Song,
Oscar Salvador, Mikołaj Lenczewski, Michal Hocko,
Mike Kravetz
Cc: linux-mm, linux-kernel, linux-fsdevel, stable, Tal Zussman
This is a first batch of userfaultfd fixes, with more to come.
These were found by a mix of regular old LLM review and by having an LLM
write a TLA+ model of uffd interactions with PTE bits and other state
and seeing what issues fell out.
Most of these fix places where mm code loses uffd-wp or RWP protection
while moving, migrating or zapping an entry. Userspace then either
misses writes and accesses, or gets pages reported that nobody touched.
There are also two hangs (UFFDIO_MOVE and MADV_GUARD_INSTALL), and
UFFDIO_ZEROPAGE modifying a file through a private mapping.
Patches 1-2: UFFDIO_ZEROPAGE and UFFDIO_MOVE fixes, cc stable
Patches 3-5: PAGEMAP_SCAN write-protect vs. migration and
device-private entries
Patch 6: UFFDIO_MOVE of the huge zero page from an RWP source
Patch 7: mremap() losing uffd state when a move fails halfway
Patch 8: migrate_vma replacing a uffd-protected zero page
Patches 9-11: guard regions vs. uffd-wp
Patch 12: UFFDIO_UNREGISTER with a misaligned hugetlb end
A few of the fixes change what userspace sees:
- UFFDIO_ZEROPAGE on a private file mapping no longer fails with
-EEXIST where the file already has a page, on an mm that cannot use
the zero page (patch 1)
- UFFDIO_MOVE returns -EEXIST for any huge destination PMD, not only
for a present THP (patch 2)
- MADV_GUARD_INSTALL sends UFFD_EVENT_REMOVE, and can return -ENOMEM
or -EINVAL if the VMA goes away or changes while the lock is dropped
(patch 11)
- UFFDIO_UNREGISTER with a misaligned hugetlb end fails before
unregistering anything (patch 12)
Patch 6 has little user impact on its own. It is included because,
together with patch 7, it allows removing clear_uffd_wp_pmd().
Patch 7 adds spurious minor faults on shmem and hugetlb after mremap():
when dropping uffd protection there, change_protection() makes writable
pages that were never protected read-only. UFFDIO_UNREGISTER and
UFFDIO_WRITEPROTECT already behave this way and lead to the same
spurious faults. This will be fixed in change_protection() in a separate
series.
Based on mm-new.
---
Tal Zussman (12):
userfaultfd: use anonymous memory for UFFDIO_ZEROPAGE on private file mappings
userfaultfd: refuse UFFDIO_MOVE into a non-present huge destination PMD
fs/proc/task_mmu: downgrade writable migration entries in PAGEMAP_SCAN write-protect
mm/migrate: keep the uffd bit when restoring a writable migration entry
fs/proc/task_mmu: write-protect device-private THPs in PAGEMAP_SCAN
mm/huge_memory: build the moved huge zero PMD from the destination VMA
mm/mremap: resolve uffd state when the context is dropped, not during the move
mm/migrate_device: do not migrate a uffd-protected zero page
mm/madvise: let a guard marker carry the uffd-wp bit
mm: set the uffd-wp bit on a guard marker when write-protecting
mm/madvise: send UFFD_EVENT_REMOVE when installing guard regions
mm/userfaultfd: check hugetlb end alignment in UFFDIO_UNREGISTER pre-scan
fs/proc/task_mmu.c | 64 +++++++++++++++++++++++++++++++++++++++++---
include/linux/leafops.h | 18 +++++++++++++
mm/huge_memory.c | 31 +++-------------------
mm/hugetlb.c | 27 +------------------
mm/madvise.c | 62 +++++++++++++++++++++++++++++++++++--------
mm/migrate.c | 2 +-
mm/migrate_device.c | 23 ++++++++++++++--
mm/mprotect.c | 26 ++++++++++++++++--
mm/mremap.c | 46 +-------------------------------
mm/userfaultfd.c | 70 ++++++++++++++++++++++++-------------------------
10 files changed, 215 insertions(+), 154 deletions(-)
---
base-commit: 2dc0136b1fdac153434ab02778fcdb32db5d0dae
change-id: 20261106-uffd-state-fixes-d2111e2abd48
Best regards,
--
Tal Zussman <tz2294@columbia.edu>
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH 01/12] userfaultfd: use anonymous memory for UFFDIO_ZEROPAGE on private file mappings
2026-11-11 17:55 [PATCH 00/12] userfaultfd: assorted fixes (part 1) Tal Zussman
@ 2026-11-11 17:55 ` Tal Zussman
2026-11-12 18:05 ` Lorenzo Stoakes (ARM)
2026-11-11 17:55 ` [PATCH 02/12] userfaultfd: refuse UFFDIO_MOVE into a non-present huge destination PMD Tal Zussman
1 sibling, 1 reply; 5+ messages in thread
From: Tal Zussman @ 2026-11-11 17:55 UTC (permalink / raw)
To: Andrew Morton, Mike Rapoport, Peter Xu, James Houghton,
Andrea Arcangeli, Suren Baghdasaryan, Balbir Singh,
Liam R. Howlett, Lorenzo Stoakes, Vlastimil Babka, Jann Horn,
Pedro Falcato, Michał Mirosław, Andrei Vagin,
Stephen Rothwell, Arnd Bergmann, Muhammad Usama Anjum,
David Hildenbrand, Zi Yan, Matthew Brost, Joshua Hahn,
Byungchul Park, Gregory Price, Ying Huang, Alistair Popple,
Baolin Wang, Nico Pache, Ryan Roberts, Dev Jain, Barry Song,
Lance Yang, Usama Arif, Kiryl Shutsemau, Chris Down, Muchun Song,
Oscar Salvador, Mikołaj Lenczewski, Michal Hocko,
Mike Kravetz
Cc: linux-mm, linux-kernel, linux-fsdevel, stable, Tal Zussman
If the mm cannot map the shared zero page, UFFDIO_ZEROPAGE falls back to
mfill_atomic_pte_zeroed_folio(), which allocates a zeroed folio and
installs it through vma_uffd_ops(), no matter what kind of mapping it
is. An s390 KVM guest with storage keys or protected virtualization is
such an mm.
For a MAP_PRIVATE shmem mapping, that adds the zeroed folio to the page
cache of the file. That is wrong: a private mapping must never modify
the file it maps, and the file might even be mapped from a read-only fd.
The ioctl can also fail where it should succeed. A missing fault is only
delivered for a hole in the file, but UFFDIO_ZEROPAGE can be called on
an address that did not fault, or can race with a write to the file. If
the file has a page at that offset by then, inserting the zeroed folio
fails and the ioctl returns -EEXIST although nothing is mapped at the
destination address.
What we want is what UFFDIO_ZEROPAGE does on a private mapping when the
shared zero page can be used, and what UFFDIO_COPY does: only check that
nothing is mapped at the destination address, map zeroed memory there,
and leave the page cache alone. The zeroed folio path did that before it
was moved to vm_uffd_ops.
To fix it, pick the ops in the common folio path, as
mfill_atomic_pte_copy() did for UFFDIO_COPY: anon_uffd_ops for a private
mapping, the ops of the VMA otherwise. That makes
mfill_atomic_pte_copy() and mfill_atomic_pte_zeroed_folio() redundant,
so remove them and rename __mfill_atomic_pte() to
mfill_atomic_pte_folio().
UFFDIO_ZEROPAGE no longer modifies the file, and only fails with -EEXIST
if something is already mapped at the destination address.
Fixes: f74991b4e383 ("shmem, userfaultfd: implement shmem uffd operations using vm_uffd_ops")
Cc: stable@vger.kernel.org
Signed-off-by: Tal Zussman <tz2294@columbia.edu>
---
mm/userfaultfd.c | 46 ++++++++++++++++------------------------------
1 file changed, 16 insertions(+), 30 deletions(-)
diff --git a/mm/userfaultfd.c b/mm/userfaultfd.c
index d65184d9a480..61a712e1f9f5 100644
--- a/mm/userfaultfd.c
+++ b/mm/userfaultfd.c
@@ -531,15 +531,27 @@ static int mfill_copy_folio_retry(struct mfill_state *mfill_state,
return 0;
}
-static int __mfill_atomic_pte(struct mfill_state *state,
- const struct vm_uffd_ops *ops)
+static int mfill_atomic_pte_folio(struct mfill_state *state)
{
+ const struct vm_uffd_ops *ops = vma_uffd_ops(state->vma);
unsigned long dst_addr = state->dst_addr;
unsigned long src_addr = state->src_addr;
uffd_flags_t flags = state->flags;
struct folio *folio;
int ret;
+ /*
+ * The normal page fault path for a MAP_PRIVATE mapping in a
+ * file-backed VMA will invoke the fault, fill the hole in the file and
+ * COW it right away. The result generates plain anonymous memory.
+ * So when we are asked to fill a hole in a MAP_PRIVATE mapping, we'll
+ * generate anonymous memory directly without actually filling the
+ * hole. For the MAP_PRIVATE case the robustness check only happens in
+ * the pagetable (to verify it's still none) and not in the page cache.
+ */
+ if (!(state->vma->vm_flags & VM_SHARED))
+ ops = &anon_uffd_ops;
+
if (!ops) {
VM_WARN_ONCE(1, "UFFDIO_COPY for unsupported VMA");
return -EOPNOTSUPP;
@@ -597,32 +609,6 @@ static int __mfill_atomic_pte(struct mfill_state *state,
return ret;
}
-static int mfill_atomic_pte_copy(struct mfill_state *state)
-{
- const struct vm_uffd_ops *ops = vma_uffd_ops(state->vma);
-
- /*
- * The normal page fault path for a MAP_PRIVATE mapping in a
- * file-backed VMA will invoke the fault, fill the hole in the file and
- * COW it right away. The result generates plain anonymous memory.
- * So when we are asked to fill a hole in a MAP_PRIVATE mapping, we'll
- * generate anonymous memory directly without actually filling the
- * hole. For the MAP_PRIVATE case the robustness check only happens in
- * the pagetable (to verify it's still none) and not in the page cache.
- */
- if (!(state->vma->vm_flags & VM_SHARED))
- ops = &anon_uffd_ops;
-
- return __mfill_atomic_pte(state, ops);
-}
-
-static int mfill_atomic_pte_zeroed_folio(struct mfill_state *state)
-{
- const struct vm_uffd_ops *ops = vma_uffd_ops(state->vma);
-
- return __mfill_atomic_pte(state, ops);
-}
-
static int mfill_atomic_pte_zeropage(struct mfill_state *state)
{
struct vm_area_struct *dst_vma = state->vma;
@@ -634,7 +620,7 @@ static int mfill_atomic_pte_zeropage(struct mfill_state *state)
if (mm_forbids_zeropage(dst_vma->vm_mm) ||
(dst_vma->vm_flags & VM_SHARED))
- return mfill_atomic_pte_zeroed_folio(state);
+ return mfill_atomic_pte_folio(state);
_dst_pte = pte_mkspecial(pfn_pte(zero_pfn(dst_addr),
dst_vma->vm_page_prot));
@@ -927,7 +913,7 @@ static __always_inline ssize_t mfill_atomic_pte(struct mfill_state *state)
if (uffd_flags_mode_is(flags, MFILL_ATOMIC_POISON))
return mfill_atomic_pte_poison(state);
if (uffd_flags_mode_is(flags, MFILL_ATOMIC_COPY))
- return mfill_atomic_pte_copy(state);
+ return mfill_atomic_pte_folio(state);
if (uffd_flags_mode_is(flags, MFILL_ATOMIC_ZEROPAGE))
return mfill_atomic_pte_zeropage(state);
--
2.39.5
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH 02/12] userfaultfd: refuse UFFDIO_MOVE into a non-present huge destination PMD
2026-11-11 17:55 [PATCH 00/12] userfaultfd: assorted fixes (part 1) Tal Zussman
2026-11-11 17:55 ` [PATCH 01/12] userfaultfd: use anonymous memory for UFFDIO_ZEROPAGE on private file mappings Tal Zussman
@ 2026-11-11 17:55 ` Tal Zussman
2026-11-12 18:07 ` Lorenzo Stoakes (ARM)
1 sibling, 1 reply; 5+ messages in thread
From: Tal Zussman @ 2026-11-11 17:55 UTC (permalink / raw)
To: Andrew Morton, Mike Rapoport, Peter Xu, James Houghton,
Andrea Arcangeli, Suren Baghdasaryan, Balbir Singh,
Liam R. Howlett, Lorenzo Stoakes, Vlastimil Babka, Jann Horn,
Pedro Falcato, Michał Mirosław, Andrei Vagin,
Stephen Rothwell, Arnd Bergmann, Muhammad Usama Anjum,
David Hildenbrand, Zi Yan, Matthew Brost, Joshua Hahn,
Byungchul Park, Gregory Price, Ying Huang, Alistair Popple,
Baolin Wang, Nico Pache, Ryan Roberts, Dev Jain, Barry Song,
Lance Yang, Usama Arif, Kiryl Shutsemau, Chris Down, Muchun Song,
Oscar Salvador, Mikołaj Lenczewski, Michal Hocko,
Mike Kravetz
Cc: linux-mm, linux-kernel, linux-fsdevel, stable, Tal Zussman
move_pages() refuses to move into a destination PMD that already maps a
THP, but checks for that with pmd_trans_huge(), which is only true for a
present THP. A device-private THP or a THP under migration is a
non-present huge PMD leaf and is not refused.
The destination PMD is then treated as if it pointed to a PTE table. It
is not empty, so pte_alloc() leaves it alone, and move_pages_ptes()
fails to map a PTE table from it and returns -EAGAIN. move_pages()
retries on -EAGAIN, with both VMA read locks held. If the source is a
THP, it is split first for nothing.
For a THP under migration, the loop ends once the migration finishes.
A device-private THP can remain in device memory indefinitely, so the
caller of UFFDIO_MOVE hangs until it receives a fatal signal, and
anything in the mm that needs to write-lock one of the VMAs blocks along
with it. This requires a driver that migrates THPs to device memory.
To fix it, check the destination with pmd_is_huge(), which is true for
any huge leaf, present or not. UFFDIO_MOVE now fails with -EEXIST in
these cases, as it already does for a present THP.
Fixes: adef440691ba ("userfaultfd: UFFDIO_MOVE uABI")
Fixes: a30b48bf1b24 ("mm/migrate_device: implement THP migration of zone device pages")
Cc: stable@vger.kernel.org
Signed-off-by: Tal Zussman <tz2294@columbia.edu>
---
mm/userfaultfd.c | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/mm/userfaultfd.c b/mm/userfaultfd.c
index 61a712e1f9f5..ad9d243dc64b 100644
--- a/mm/userfaultfd.c
+++ b/mm/userfaultfd.c
@@ -2021,12 +2021,13 @@ static ssize_t move_pages(struct userfaultfd_ctx *ctx, unsigned long dst_start,
dst_pmdval = pmdp_get_lockless(dst_pmd);
/*
- * If the dst_pmd is mapped as THP don't override it and just
- * be strict. If dst_pmd changes into TPH after this check, the
- * move_pages_huge_pmd() will detect the change and retry
- * while move_pages_pte() will detect the change and fail.
+ * If the dst_pmd is a huge leaf, present or not, don't
+ * override it and just be strict. If dst_pmd changes into a
+ * leaf after this check, move_pages_huge_pmd() will detect the
+ * change and retry while move_pages_ptes() will fail to map
+ * the PTE table and the next iteration will see the leaf.
*/
- if (unlikely(pmd_trans_huge(dst_pmdval))) {
+ if (unlikely(pmd_is_huge(dst_pmdval))) {
err = -EEXIST;
break;
}
--
2.39.5
^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH 01/12] userfaultfd: use anonymous memory for UFFDIO_ZEROPAGE on private file mappings
2026-11-11 17:55 ` [PATCH 01/12] userfaultfd: use anonymous memory for UFFDIO_ZEROPAGE on private file mappings Tal Zussman
@ 2026-11-12 18:05 ` Lorenzo Stoakes (ARM)
0 siblings, 0 replies; 5+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-11-12 18:05 UTC (permalink / raw)
To: Tal Zussman
Cc: Andrew Morton, Mike Rapoport, Peter Xu, James Houghton,
Andrea Arcangeli, Suren Baghdasaryan, Balbir Singh,
Liam R. Howlett, Vlastimil Babka, Jann Horn, Pedro Falcato,
Michał Mirosław, Andrei Vagin, Stephen Rothwell,
Arnd Bergmann, Muhammad Usama Anjum, David Hildenbrand, Zi Yan,
Matthew Brost, Joshua Hahn, Byungchul Park, Gregory Price,
Ying Huang, Alistair Popple, Baolin Wang, Nico Pache,
Ryan Roberts, Dev Jain, Barry Song, Lance Yang, Usama Arif,
Kiryl Shutsemau, Chris Down, Muchun Song, Oscar Salvador,
Mikołaj Lenczewski, Michal Hocko, Mike Kravetz, linux-mm,
linux-kernel, linux-fsdevel, stable
On Wed, Nov 11, 2026 at 12:55:09PM -0500, Tal Zussman wrote:
> If the mm cannot map the shared zero page, UFFDIO_ZEROPAGE falls back to
> mfill_atomic_pte_zeroed_folio(), which allocates a zeroed folio and
> installs it through vma_uffd_ops(), no matter what kind of mapping it
> is. An s390 KVM guest with storage keys or protected virtualization is
> such an mm.
>
> For a MAP_PRIVATE shmem mapping, that adds the zeroed folio to the page
> cache of the file. That is wrong: a private mapping must never modify
> the file it maps, and the file might even be mapped from a read-only fd.
>
> The ioctl can also fail where it should succeed. A missing fault is only
> delivered for a hole in the file, but UFFDIO_ZEROPAGE can be called on
> an address that did not fault, or can race with a write to the file. If
> the file has a page at that offset by then, inserting the zeroed folio
> fails and the ioctl returns -EEXIST although nothing is mapped at the
> destination address.
>
> What we want is what UFFDIO_ZEROPAGE does on a private mapping when the
> shared zero page can be used, and what UFFDIO_COPY does: only check that
> nothing is mapped at the destination address, map zeroed memory there,
> and leave the page cache alone. The zeroed folio path did that before it
> was moved to vm_uffd_ops.
>
> To fix it, pick the ops in the common folio path, as
> mfill_atomic_pte_copy() did for UFFDIO_COPY: anon_uffd_ops for a private
> mapping, the ops of the VMA otherwise. That makes
> mfill_atomic_pte_copy() and mfill_atomic_pte_zeroed_folio() redundant,
> so remove them and rename __mfill_atomic_pte() to
> mfill_atomic_pte_folio().
>
> UFFDIO_ZEROPAGE no longer modifies the file, and only fails with -EEXIST
> if something is already mapped at the destination address.
>
> Fixes: f74991b4e383 ("shmem, userfaultfd: implement shmem uffd operations using vm_uffd_ops")
> Cc: stable@vger.kernel.org
> Signed-off-by: Tal Zussman <tz2294@columbia.edu>
Looks sensible (also another cleanup in disguise which I like ;):
Acked-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
> ---
> mm/userfaultfd.c | 46 ++++++++++++++++------------------------------
> 1 file changed, 16 insertions(+), 30 deletions(-)
>
> diff --git a/mm/userfaultfd.c b/mm/userfaultfd.c
> index d65184d9a480..61a712e1f9f5 100644
> --- a/mm/userfaultfd.c
> +++ b/mm/userfaultfd.c
> @@ -531,15 +531,27 @@ static int mfill_copy_folio_retry(struct mfill_state *mfill_state,
> return 0;
> }
>
> -static int __mfill_atomic_pte(struct mfill_state *state,
> - const struct vm_uffd_ops *ops)
> +static int mfill_atomic_pte_folio(struct mfill_state *state)
> {
> + const struct vm_uffd_ops *ops = vma_uffd_ops(state->vma);
> unsigned long dst_addr = state->dst_addr;
> unsigned long src_addr = state->src_addr;
> uffd_flags_t flags = state->flags;
> struct folio *folio;
> int ret;
>
> + /*
> + * The normal page fault path for a MAP_PRIVATE mapping in a
> + * file-backed VMA will invoke the fault, fill the hole in the file and
> + * COW it right away. The result generates plain anonymous memory.
> + * So when we are asked to fill a hole in a MAP_PRIVATE mapping, we'll
> + * generate anonymous memory directly without actually filling the
> + * hole. For the MAP_PRIVATE case the robustness check only happens in
> + * the pagetable (to verify it's still none) and not in the page cache.
> + */
> + if (!(state->vma->vm_flags & VM_SHARED))
> + ops = &anon_uffd_ops;
> +
> if (!ops) {
> VM_WARN_ONCE(1, "UFFDIO_COPY for unsupported VMA");
> return -EOPNOTSUPP;
> @@ -597,32 +609,6 @@ static int __mfill_atomic_pte(struct mfill_state *state,
> return ret;
> }
>
> -static int mfill_atomic_pte_copy(struct mfill_state *state)
> -{
> - const struct vm_uffd_ops *ops = vma_uffd_ops(state->vma);
> -
> - /*
> - * The normal page fault path for a MAP_PRIVATE mapping in a
> - * file-backed VMA will invoke the fault, fill the hole in the file and
> - * COW it right away. The result generates plain anonymous memory.
> - * So when we are asked to fill a hole in a MAP_PRIVATE mapping, we'll
> - * generate anonymous memory directly without actually filling the
> - * hole. For the MAP_PRIVATE case the robustness check only happens in
> - * the pagetable (to verify it's still none) and not in the page cache.
> - */
> - if (!(state->vma->vm_flags & VM_SHARED))
> - ops = &anon_uffd_ops;
> -
> - return __mfill_atomic_pte(state, ops);
> -}
> -
> -static int mfill_atomic_pte_zeroed_folio(struct mfill_state *state)
> -{
> - const struct vm_uffd_ops *ops = vma_uffd_ops(state->vma);
> -
> - return __mfill_atomic_pte(state, ops);
> -}
> -
> static int mfill_atomic_pte_zeropage(struct mfill_state *state)
> {
> struct vm_area_struct *dst_vma = state->vma;
> @@ -634,7 +620,7 @@ static int mfill_atomic_pte_zeropage(struct mfill_state *state)
>
> if (mm_forbids_zeropage(dst_vma->vm_mm) ||
> (dst_vma->vm_flags & VM_SHARED))
> - return mfill_atomic_pte_zeroed_folio(state);
> + return mfill_atomic_pte_folio(state);
>
> _dst_pte = pte_mkspecial(pfn_pte(zero_pfn(dst_addr),
> dst_vma->vm_page_prot));
> @@ -927,7 +913,7 @@ static __always_inline ssize_t mfill_atomic_pte(struct mfill_state *state)
> if (uffd_flags_mode_is(flags, MFILL_ATOMIC_POISON))
> return mfill_atomic_pte_poison(state);
> if (uffd_flags_mode_is(flags, MFILL_ATOMIC_COPY))
> - return mfill_atomic_pte_copy(state);
> + return mfill_atomic_pte_folio(state);
> if (uffd_flags_mode_is(flags, MFILL_ATOMIC_ZEROPAGE))
> return mfill_atomic_pte_zeropage(state);
>
>
> --
> 2.39.5
>
--
Cheers, Lorenzo
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH 02/12] userfaultfd: refuse UFFDIO_MOVE into a non-present huge destination PMD
2026-11-11 17:55 ` [PATCH 02/12] userfaultfd: refuse UFFDIO_MOVE into a non-present huge destination PMD Tal Zussman
@ 2026-11-12 18:07 ` Lorenzo Stoakes (ARM)
0 siblings, 0 replies; 5+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-11-12 18:07 UTC (permalink / raw)
To: Tal Zussman
Cc: Andrew Morton, Mike Rapoport, Peter Xu, James Houghton,
Andrea Arcangeli, Suren Baghdasaryan, Balbir Singh,
Liam R. Howlett, Vlastimil Babka, Jann Horn, Pedro Falcato,
Michał Mirosław, Andrei Vagin, Stephen Rothwell,
Arnd Bergmann, Muhammad Usama Anjum, David Hildenbrand, Zi Yan,
Matthew Brost, Joshua Hahn, Byungchul Park, Gregory Price,
Ying Huang, Alistair Popple, Baolin Wang, Nico Pache,
Ryan Roberts, Dev Jain, Barry Song, Lance Yang, Usama Arif,
Kiryl Shutsemau, Chris Down, Muchun Song, Oscar Salvador,
Mikołaj Lenczewski, Michal Hocko, Mike Kravetz, linux-mm,
linux-kernel, linux-fsdevel, stable
On Wed, Nov 11, 2026 at 12:55:10PM -0500, Tal Zussman wrote:
> move_pages() refuses to move into a destination PMD that already maps a
> THP, but checks for that with pmd_trans_huge(), which is only true for a
> present THP. A device-private THP or a THP under migration is a
> non-present huge PMD leaf and is not refused.
non-present huge PMD softleaf :)
>
> The destination PMD is then treated as if it pointed to a PTE table. It
> is not empty, so pte_alloc() leaves it alone, and move_pages_ptes()
> fails to map a PTE table from it and returns -EAGAIN. move_pages()
> retries on -EAGAIN, with both VMA read locks held. If the source is a
> THP, it is split first for nothing.
>
> For a THP under migration, the loop ends once the migration finishes.
> A device-private THP can remain in device memory indefinitely, so the
> caller of UFFDIO_MOVE hangs until it receives a fatal signal, and
> anything in the mm that needs to write-lock one of the VMAs blocks along
> with it. This requires a driver that migrates THPs to device memory.
>
> To fix it, check the destination with pmd_is_huge(), which is true for
> any huge leaf, present or not. UFFDIO_MOVE now fails with -EEXIST in
> these cases, as it already does for a present THP.
>
> Fixes: adef440691ba ("userfaultfd: UFFDIO_MOVE uABI")
> Fixes: a30b48bf1b24 ("mm/migrate_device: implement THP migration of zone device pages")
> Cc: stable@vger.kernel.org
> Signed-off-by: Tal Zussman <tz2294@columbia.edu>
Looks sensible to me so:
Acked-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
> ---
> mm/userfaultfd.c | 11 ++++++-----
> 1 file changed, 6 insertions(+), 5 deletions(-)
>
> diff --git a/mm/userfaultfd.c b/mm/userfaultfd.c
> index 61a712e1f9f5..ad9d243dc64b 100644
> --- a/mm/userfaultfd.c
> +++ b/mm/userfaultfd.c
> @@ -2021,12 +2021,13 @@ static ssize_t move_pages(struct userfaultfd_ctx *ctx, unsigned long dst_start,
>
> dst_pmdval = pmdp_get_lockless(dst_pmd);
> /*
> - * If the dst_pmd is mapped as THP don't override it and just
> - * be strict. If dst_pmd changes into TPH after this check, the
> - * move_pages_huge_pmd() will detect the change and retry
> - * while move_pages_pte() will detect the change and fail.
> + * If the dst_pmd is a huge leaf, present or not, don't
> + * override it and just be strict. If dst_pmd changes into a
> + * leaf after this check, move_pages_huge_pmd() will detect the
> + * change and retry while move_pages_ptes() will fail to map
> + * the PTE table and the next iteration will see the leaf.
> */
> - if (unlikely(pmd_trans_huge(dst_pmdval))) {
> + if (unlikely(pmd_is_huge(dst_pmdval))) {
> err = -EEXIST;
> break;
> }
>
> --
> 2.39.5
>
--
Cheers, Lorenzo
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-11-12 18:07 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-11 17:55 [PATCH 00/12] userfaultfd: assorted fixes (part 1) Tal Zussman
2026-11-11 17:55 ` [PATCH 01/12] userfaultfd: use anonymous memory for UFFDIO_ZEROPAGE on private file mappings Tal Zussman
2026-11-12 18:05 ` Lorenzo Stoakes (ARM)
2026-11-11 17:55 ` [PATCH 02/12] userfaultfd: refuse UFFDIO_MOVE into a non-present huge destination PMD Tal Zussman
2026-11-12 18:07 ` Lorenzo Stoakes (ARM)
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox