mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH v1 0/2] userfaultfd: clear the inherited uffd bit in move_swap_pte()
@ 2026-09-19  0:46 Donggeun Yoo
  2026-09-19  0:46 ` [PATCH v1 1/2] " Donggeun Yoo
                   ` (2 more replies)
  0 siblings, 3 replies; 11+ messages in thread
From: Donggeun Yoo @ 2026-09-19  0:46 UTC (permalink / raw)
  To: akpm, rppt
  Cc: peterx, surenb, aarcange, david, ljs, liam, vbabka, mhocko,
	shuah, linux-mm, linux-kselftest, linux-kernel,
	donggeunyoo.kernel

UFFDIO_MOVE on a swapped-out page installs the source PTE at the
destination unchanged, so a uffd bit set on the source lands in a
destination VMA that was never registered for write protection, and
nothing clears it afterwards.  Patch 1 clears the bit unless the
destination is RWP-registered, which is what the present-page and
zeropage move paths already do.  Patch 2 adds the test that catches it.

x86_64 defconfig plus USERFAULTFD, TRANSPARENT_HUGEPAGE, GUP_TEST and a
swap device, under QEMU, base 17e7b8eacf4c:

  uffd-unit-tests                    before   after
  move-swap-wp on anon               not ok   ok
  the other 114 unit tests           ok       ok
  uffd-wp-mremap, 38 tests           ok       ok

  pagemap bit 57 at the destination  before   after
  swapped page, dst not armed        set      clear
  swapped page, dst WP-armed         set      clear
  swapped page, dst RWP-armed        set      set
  resident page, dst WP-armed        clear    clear

  MADV_COLLAPSE over 2 MB at dst     EINVAL   0

Donggeun Yoo (2):
  userfaultfd: clear the inherited uffd bit in move_swap_pte()
  selftests/mm: add a test for UFFDIO_MOVE of a write-protected swap
    entry

 mm/userfaultfd.c                             |  2 +
 tools/testing/selftests/mm/uffd-unit-tests.c | 61 ++++++++++++++++++++
 2 files changed, 63 insertions(+)


base-commit: 17e7b8eacf4cac800a4fc89a28729df72a2dabda
-- 
2.53.0


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

* [PATCH v1 1/2] userfaultfd: clear the inherited uffd bit in move_swap_pte()
  2026-09-19  0:46 [PATCH v1 0/2] userfaultfd: clear the inherited uffd bit in move_swap_pte() Donggeun Yoo
@ 2026-09-19  0:46 ` Donggeun Yoo
  2026-09-22  6:45   ` Mike Rapoport
                     ` (2 more replies)
  2026-09-19  0:46 ` [PATCH v1 2/2] selftests/mm: add a test for UFFDIO_MOVE of a write-protected swap entry Donggeun Yoo
  2026-09-22  5:58 ` [PATCH v1 0/2] userfaultfd: clear the inherited uffd bit in move_swap_pte() Mike Rapoport
  2 siblings, 3 replies; 11+ messages in thread
From: Donggeun Yoo @ 2026-09-19  0:46 UTC (permalink / raw)
  To: akpm, rppt
  Cc: peterx, surenb, aarcange, david, ljs, liam, vbabka, mhocko,
	shuah, linux-mm, linux-kselftest, linux-kernel,
	donggeunyoo.kernel, stable

UFFDIO_MOVE on a swapped-out page installs the source PTE at the
destination unchanged, so a uffd bit set on the source lands in a
destination VMA that was never registered for write protection.  It is
then permanent: the bit is dropped only by change_protection() under
MM_CP_UFFD_{WP,RWP}_RESOLVE, which uffd_wp_range(), mrwprotect_range()
and userfaultfd_clear_vma() issue only for a VMA registered in that
mode.  pagemap reports the page as uffd-tracked, and MADV_COLLAPSE
refuses the range while the bit is set, because collapse_scan_pmd() is
strict about uffd on swap entries.

move_present_ptes() and move_zeropage_pte() build the destination PTE
from dst_vma->vm_page_prot and arm RWP only when dst_vma asks for it, so
the destination's own registration decides the result.  move_swap_pte()
copies the source PTE instead and only ever sets the bit, never clears
it, so one UFFDIO_MOVE behaves differently depending on whether the page
happened to be resident.

Clear the uffd bit on the moved swap entry unless the destination is
RWP-registered, as copy_nonpresent_pte() does where it installs a PTE
into a destination that may not be armed.  A WP-registered destination
stops inheriting the bit as well, which is already what it gets when the
moved page is resident.

Fixes: adef440691ba ("userfaultfd: UFFDIO_MOVE uABI")
Cc: <stable@vger.kernel.org>
Signed-off-by: Donggeun Yoo <donggeunyoo.kernel@gmail.com>
---
 mm/userfaultfd.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/mm/userfaultfd.c b/mm/userfaultfd.c
index 74f04c323c50..6495666c596b 100644
--- a/mm/userfaultfd.c
+++ b/mm/userfaultfd.c
@@ -1452,6 +1452,8 @@ static int move_swap_pte(struct mm_struct *mm, struct vm_area_struct *dst_vma,
 	/* Re-arm RWP on the moved swap entry if dst_vma is RWP-registered. */
 	if (userfaultfd_rwp(dst_vma))
 		orig_src_pte = pte_swp_mkuffd(orig_src_pte);
+	else
+		orig_src_pte = pte_swp_clear_uffd(orig_src_pte);
 	set_pte_at(mm, dst_addr, dst_pte, orig_src_pte);
 	double_pt_unlock(dst_ptl, src_ptl);
 
-- 
2.53.0


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

* [PATCH v1 2/2] selftests/mm: add a test for UFFDIO_MOVE of a write-protected swap entry
  2026-09-19  0:46 [PATCH v1 0/2] userfaultfd: clear the inherited uffd bit in move_swap_pte() Donggeun Yoo
  2026-09-19  0:46 ` [PATCH v1 1/2] " Donggeun Yoo
@ 2026-09-19  0:46 ` Donggeun Yoo
  2026-09-22  5:58 ` [PATCH v1 0/2] userfaultfd: clear the inherited uffd bit in move_swap_pte() Mike Rapoport
  2 siblings, 0 replies; 11+ messages in thread
From: Donggeun Yoo @ 2026-09-19  0:46 UTC (permalink / raw)
  To: akpm, rppt
  Cc: peterx, surenb, aarcange, david, ljs, liam, vbabka, mhocko,
	shuah, linux-mm, linux-kselftest, linux-kernel,
	donggeunyoo.kernel

Move a swapped-out page out of a write-protected area into a destination
registered for missing faults only, and read pagemap bit 57 at the
destination.  The destination was never write-protected, so the bit must
be clear.

Signed-off-by: Donggeun Yoo <donggeunyoo.kernel@gmail.com>
---
 tools/testing/selftests/mm/uffd-unit-tests.c | 61 ++++++++++++++++++++
 1 file changed, 61 insertions(+)

diff --git a/tools/testing/selftests/mm/uffd-unit-tests.c b/tools/testing/selftests/mm/uffd-unit-tests.c
index ef9b3956bdcf..ae8b718f0a27 100644
--- a/tools/testing/selftests/mm/uffd-unit-tests.c
+++ b/tools/testing/selftests/mm/uffd-unit-tests.c
@@ -2037,6 +2037,59 @@ static void uffd_move_pmd_split_test(uffd_global_test_opts_t *gopts, uffd_test_a
 			      uffd_move_pmd_handle_fault);
 }
 
+/*
+ * Moving a swapped-out page out of a write-protected area must not carry the
+ * uffd-wp bit into a destination that is not write-protect registered: such a
+ * bit is never cleared afterwards, so pagemap keeps reporting the page as
+ * uffd-tracked.
+ *
+ * Needs a swap device; skipped if MADV_PAGEOUT cannot evict the page.
+ */
+static void uffd_move_swap_wp_test(uffd_global_test_opts_t *gopts,
+				   uffd_test_args_t *targs)
+{
+	unsigned long page_size = gopts->page_size;
+	struct uffdio_move move = { };
+	int pagemap_fd, i;
+
+	if (uffd_register(gopts->uffd, gopts->area_src, page_size,
+			  false, true, false))
+		err("register src failure");
+	if (uffd_register(gopts->uffd, gopts->area_dst, page_size,
+			  true, false, false))
+		err("register dst failure");
+
+	wp_range(gopts->uffd, (unsigned long)gopts->area_src, page_size, true);
+
+	pagemap_fd = pagemap_open();
+	for (i = 0; i < 100; i++) {
+		if (madvise(gopts->area_src, page_size, MADV_PAGEOUT))
+			err("MADV_PAGEOUT");
+		if (pagemap_is_swapped(pagemap_fd, gopts->area_src))
+			break;
+		usleep(10000);
+	}
+	if (!pagemap_is_swapped(pagemap_fd, gopts->area_src)) {
+		uffd_test_skip("MADV_PAGEOUT did not swap the page; is swap enabled?");
+		goto out;
+	}
+
+	move.dst = (unsigned long)gopts->area_dst;
+	move.src = (unsigned long)gopts->area_src;
+	move.len = page_size;
+	if (ioctl(gopts->uffd, UFFDIO_MOVE, &move))
+		err("UFFDIO_MOVE");
+
+	if (pagemap_get_entry(pagemap_fd, gopts->area_dst) & PM_UFFD_WP)
+		uffd_test_fail("uffd-wp bit moved into an area that is not write-protected");
+	else
+		uffd_test_pass();
+out:
+	close(pagemap_fd);
+	uffd_unregister(gopts->uffd, gopts->area_src, page_size);
+	uffd_unregister(gopts->uffd, gopts->area_dst, page_size);
+}
+
 static bool
 uffdio_verify_results(const char *name, int ret, int error, long result)
 {
@@ -2359,6 +2412,14 @@ uffd_test_case_t uffd_tests[] = {
 		.uffd_feature_required = UFFD_FEATURE_MOVE,
 		.test_case_ops = &uffd_move_test_pmd_case_ops,
 	},
+	{
+		.name = "move-swap-wp",
+		.uffd_fn = uffd_move_swap_wp_test,
+		.mem_targets = MEM_ANON,
+		.uffd_feature_required = UFFD_FEATURE_MOVE |
+		UFFD_FEATURE_PAGEFAULT_FLAG_WP,
+		.test_case_ops = &uffd_move_test_case_ops,
+	},
 	{
 		.name = "wp-fork",
 		.uffd_fn = uffd_wp_fork_test,
-- 
2.53.0


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

* Re: [PATCH v1 0/2] userfaultfd: clear the inherited uffd bit in move_swap_pte()
  2026-09-19  0:46 [PATCH v1 0/2] userfaultfd: clear the inherited uffd bit in move_swap_pte() Donggeun Yoo
  2026-09-19  0:46 ` [PATCH v1 1/2] " Donggeun Yoo
  2026-09-19  0:46 ` [PATCH v1 2/2] selftests/mm: add a test for UFFDIO_MOVE of a write-protected swap entry Donggeun Yoo
@ 2026-09-22  5:58 ` Mike Rapoport
  2 siblings, 0 replies; 11+ messages in thread
From: Mike Rapoport @ 2026-09-22  5:58 UTC (permalink / raw)
  To: Donggeun Yoo
  Cc: akpm, peterx, surenb, aarcange, david, ljs, liam, vbabka, mhocko,
	shuah, linux-mm, linux-kselftest, linux-kernel

Hi,

On Sat, Sep 19, 2026 at 09:46:28AM +0900, Donggeun Yoo wrote:
> UFFDIO_MOVE on a swapped-out page installs the source PTE at the
> destination unchanged, so a uffd bit set on the source lands in a
> destination VMA that was never registered for write protection, and
> nothing clears it afterwards.  Patch 1 clears the bit unless the
> destination is RWP-registered, which is what the present-page and
> zeropage move paths already do.  Patch 2 adds the test that catches it.

This and changelogs read like an LLM generated text.
Please add attribution as per

https://docs.kernel.org/process/coding-assistants.html#attribution 

> x86_64 defconfig plus USERFAULTFD, TRANSPARENT_HUGEPAGE, GUP_TEST and a
> swap device, under QEMU, base 17e7b8eacf4c:
> 
>   uffd-unit-tests                    before   after
>   move-swap-wp on anon               not ok   ok
>   the other 114 unit tests           ok       ok
>   uffd-wp-mremap, 38 tests           ok       ok
> 
>   pagemap bit 57 at the destination  before   after
>   swapped page, dst not armed        set      clear
>   swapped page, dst WP-armed         set      clear
>   swapped page, dst RWP-armed        set      set
>   resident page, dst WP-armed        clear    clear
> 
>   MADV_COLLAPSE over 2 MB at dst     EINVAL   0
> 
> Donggeun Yoo (2):
>   userfaultfd: clear the inherited uffd bit in move_swap_pte()
>   selftests/mm: add a test for UFFDIO_MOVE of a write-protected swap
>     entry
> 
>  mm/userfaultfd.c                             |  2 +
>  tools/testing/selftests/mm/uffd-unit-tests.c | 61 ++++++++++++++++++++
>  2 files changed, 63 insertions(+)
> 
> 
> base-commit: 17e7b8eacf4cac800a4fc89a28729df72a2dabda
> -- 
> 2.53.0
> 

-- 
Sincerely yours,
Mike.

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

* Re: [PATCH v1 1/2] userfaultfd: clear the inherited uffd bit in move_swap_pte()
  2026-09-19  0:46 ` [PATCH v1 1/2] " Donggeun Yoo
@ 2026-09-22  6:45   ` Mike Rapoport
  2026-09-22 12:47     ` Donggeun Yoo
  2026-09-22 11:40   ` David Hildenbrand (Arm)
  2026-09-23 11:18   ` Kiryl Shutsemau
  2 siblings, 1 reply; 11+ messages in thread
From: Mike Rapoport @ 2026-09-22  6:45 UTC (permalink / raw)
  To: Donggeun Yoo
  Cc: akpm, peterx, surenb, aarcange, david, ljs, liam, vbabka, mhocko,
	shuah, linux-mm, linux-kselftest, linux-kernel, stable

Hi,

On Sat, Sep 19, 2026 at 09:46:29AM +0900, Donggeun Yoo wrote:
> UFFDIO_MOVE on a swapped-out page installs the source PTE at the
> destination unchanged, so a uffd bit set on the source lands in a
> destination VMA that was never registered for write protection.  It is
> then permanent: the bit is dropped only by change_protection() under
> MM_CP_UFFD_{WP,RWP}_RESOLVE, which uffd_wp_range(), mrwprotect_range()
> and userfaultfd_clear_vma() issue only for a VMA registered in that
> mode.  pagemap reports the page as uffd-tracked, and MADV_COLLAPSE
> refuses the range while the bit is set, because collapse_scan_pmd() is
> strict about uffd on swap entries.
> 
> move_present_ptes() and move_zeropage_pte() build the destination PTE
> from dst_vma->vm_page_prot and arm RWP only when dst_vma asks for it, so
> the destination's own registration decides the result.  move_swap_pte()
> copies the source PTE instead and only ever sets the bit, never clears
> it, so one UFFDIO_MOVE behaves differently depending on whether the page
> happened to be resident.
> 
> Clear the uffd bit on the moved swap entry unless the destination is
> RWP-registered, as copy_nonpresent_pte() does where it installs a PTE
> into a destination that may not be armed.  A WP-registered destination
> stops inheriting the bit as well, which is already what it gets when the
> moved page is resident.

This is a hard-to-read wall of text :(

Tell your LLM to better split it into paragraph, not to list every involved
function and in general make it more to the point.
 
> Fixes: adef440691ba ("userfaultfd: UFFDIO_MOVE uABI")
> Cc: <stable@vger.kernel.org>
> Signed-off-by: Donggeun Yoo <donggeunyoo.kernel@gmail.com>
> ---
>  mm/userfaultfd.c | 2 ++
>  1 file changed, 2 insertions(+)
> 
> diff --git a/mm/userfaultfd.c b/mm/userfaultfd.c
> index 74f04c323c50..6495666c596b 100644
> --- a/mm/userfaultfd.c
> +++ b/mm/userfaultfd.c
> @@ -1452,6 +1452,8 @@ static int move_swap_pte(struct mm_struct *mm, struct vm_area_struct *dst_vma,
>  	/* Re-arm RWP on the moved swap entry if dst_vma is RWP-registered. */
>  	if (userfaultfd_rwp(dst_vma))
>  		orig_src_pte = pte_swp_mkuffd(orig_src_pte);
> +	else
> +		orig_src_pte = pte_swp_clear_uffd(orig_src_pte);
>  	set_pte_at(mm, dst_addr, dst_pte, orig_src_pte);
>  	double_pt_unlock(dst_ptl, src_ptl);
>  
> -- 
> 2.53.0
> 

-- 
Sincerely yours,
Mike.

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

* Re: [PATCH v1 1/2] userfaultfd: clear the inherited uffd bit in move_swap_pte()
  2026-09-19  0:46 ` [PATCH v1 1/2] " Donggeun Yoo
  2026-09-22  6:45   ` Mike Rapoport
@ 2026-09-22 11:40   ` David Hildenbrand (Arm)
  2026-09-22 12:41     ` Donggeun Yoo
                       ` (2 more replies)
  2026-09-23 11:18   ` Kiryl Shutsemau
  2 siblings, 3 replies; 11+ messages in thread
From: David Hildenbrand (Arm) @ 2026-09-22 11:40 UTC (permalink / raw)
  To: Donggeun Yoo, akpm, rppt
  Cc: peterx, surenb, aarcange, ljs, liam, vbabka, mhocko, shuah,
	linux-mm, linux-kselftest, linux-kernel, stable

On 9/19/26 02:46, Donggeun Yoo wrote:
> UFFDIO_MOVE on a swapped-out page installs the source PTE at the
> destination unchanged, so a uffd bit set on the source lands in a
> destination VMA that was never registered for write protection.  It is
> then permanent: the bit is dropped only by change_protection() under
> MM_CP_UFFD_{WP,RWP}_RESOLVE, which uffd_wp_range(), mrwprotect_range()
> and userfaultfd_clear_vma() issue only for a VMA registered in that
> mode.  pagemap reports the page as uffd-tracked, and MADV_COLLAPSE
> refuses the range while the bit is set, because collapse_scan_pmd() is
> strict about uffd on swap entries.
> 
> move_present_ptes() and move_zeropage_pte() build the destination PTE
> from dst_vma->vm_page_prot and arm RWP only when dst_vma asks for it, so
> the destination's own registration decides the result.  move_swap_pte()
> copies the source PTE instead and only ever sets the bit, never clears
> it, so one UFFDIO_MOVE behaves differently depending on whether the page
> happened to be resident.
> 
> Clear the uffd bit on the moved swap entry unless the destination is
> RWP-registered, as copy_nonpresent_pte() does where it installs a PTE
> into a destination that may not be armed.  A WP-registered destination
> stops inheriting the bit as well, which is already what it gets when the
> moved page is resident.
> 
> Fixes: adef440691ba ("userfaultfd: UFFDIO_MOVE uABI")
> Cc: <stable@vger.kernel.org>
> Signed-off-by: Donggeun Yoo <donggeunyoo.kernel@gmail.com>
> ---
>  mm/userfaultfd.c | 2 ++
>  1 file changed, 2 insertions(+)
> 
> diff --git a/mm/userfaultfd.c b/mm/userfaultfd.c
> index 74f04c323c50..6495666c596b 100644
> --- a/mm/userfaultfd.c
> +++ b/mm/userfaultfd.c
> @@ -1452,6 +1452,8 @@ static int move_swap_pte(struct mm_struct *mm, struct vm_area_struct *dst_vma,
>  	/* Re-arm RWP on the moved swap entry if dst_vma is RWP-registered. */
>  	if (userfaultfd_rwp(dst_vma))
>  		orig_src_pte = pte_swp_mkuffd(orig_src_pte);
> +	else
> +		orig_src_pte = pte_swp_clear_uffd(orig_src_pte);
>  	set_pte_at(mm, dst_addr, dst_pte, orig_src_pte);
>  	double_pt_unlock(dst_ptl, src_ptl);
>  

In move_present_ptes() we don't run into that issue as we create a new PTE from
scratch

	orig_dst_pte = folio_mk_pte(src_folio, dst_vma->vm_page_prot);


Staring at the

	if (userfaultfd_rwp(dst_vma))

I do wonder why we don't have to take similar care about wp ... I'm sure the is
a good reason.

Code change itself LGTM.


-- 
Cheers,

David

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

* Re: [PATCH v1 1/2] userfaultfd: clear the inherited uffd bit in move_swap_pte()
  2026-09-22 11:40   ` David Hildenbrand (Arm)
@ 2026-09-22 12:41     ` Donggeun Yoo
  2026-09-22 16:45     ` Mike Rapoport
  2026-09-23 11:20     ` Kiryl Shutsemau
  2 siblings, 0 replies; 11+ messages in thread
From: Donggeun Yoo @ 2026-09-22 12:41 UTC (permalink / raw)
  To: david
  Cc: akpm, rppt, peterx, surenb, aarcange, ljs, liam, vbabka, mhocko,
	shuah, linux-mm, linux-kselftest, linux-kernel, stable,
	donggeunyoo.kernel

Thank you for the review.

My guess is that wp protection always leaves the destination PTE
non-none, and the move path has

1566	if (!pte_none(orig_dst_pte)) {
1567		ret = -EEXIST;
1568		goto out;
1569	}

The move fails when the destination PTE is not none. So there's nothing
to preserve for wp in the first place.

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

* Re: [PATCH v1 1/2] userfaultfd: clear the inherited uffd bit in move_swap_pte()
  2026-09-22  6:45   ` Mike Rapoport
@ 2026-09-22 12:47     ` Donggeun Yoo
  0 siblings, 0 replies; 11+ messages in thread
From: Donggeun Yoo @ 2026-09-22 12:47 UTC (permalink / raw)
  To: rppt
  Cc: akpm, peterx, surenb, aarcange, david, ljs, liam, vbabka, mhocko,
	shuah, linux-mm, linux-kselftest, linux-kernel, stable,
	donggeunyoo.kernel

Thank you for the review.

I'll rewrite the changelog for readability and add the Assisted-by tag
in v2.

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

* Re: [PATCH v1 1/2] userfaultfd: clear the inherited uffd bit in move_swap_pte()
  2026-09-22 11:40   ` David Hildenbrand (Arm)
  2026-09-22 12:41     ` Donggeun Yoo
@ 2026-09-22 16:45     ` Mike Rapoport
  2026-09-23 11:20     ` Kiryl Shutsemau
  2 siblings, 0 replies; 11+ messages in thread
From: Mike Rapoport @ 2026-09-22 16:45 UTC (permalink / raw)
  To: David Hildenbrand (Arm), Kiryl Shutsemau
  Cc: Donggeun Yoo, akpm, peterx, surenb, aarcange, ljs, liam, vbabka,
	mhocko, shuah, linux-mm, linux-kselftest, linux-kernel, stable

(cc Kiryl)

On Tue, Sep 22, 2026 at 01:40:51PM +0200, David Hildenbrand (Arm) wrote:
> On 9/19/26 02:46, Donggeun Yoo wrote:
> > UFFDIO_MOVE on a swapped-out page installs the source PTE at the
> > destination unchanged, so a uffd bit set on the source lands in a
> > destination VMA that was never registered for write protection.  It is
> > then permanent: the bit is dropped only by change_protection() under
> > MM_CP_UFFD_{WP,RWP}_RESOLVE, which uffd_wp_range(), mrwprotect_range()
> > and userfaultfd_clear_vma() issue only for a VMA registered in that
> > mode.  pagemap reports the page as uffd-tracked, and MADV_COLLAPSE
> > refuses the range while the bit is set, because collapse_scan_pmd() is
> > strict about uffd on swap entries.
> > 
> > move_present_ptes() and move_zeropage_pte() build the destination PTE
> > from dst_vma->vm_page_prot and arm RWP only when dst_vma asks for it, so
> > the destination's own registration decides the result.  move_swap_pte()
> > copies the source PTE instead and only ever sets the bit, never clears
> > it, so one UFFDIO_MOVE behaves differently depending on whether the page
> > happened to be resident.
> > 
> > Clear the uffd bit on the moved swap entry unless the destination is
> > RWP-registered, as copy_nonpresent_pte() does where it installs a PTE
> > into a destination that may not be armed.  A WP-registered destination
> > stops inheriting the bit as well, which is already what it gets when the
> > moved page is resident.
> > 
> > Fixes: adef440691ba ("userfaultfd: UFFDIO_MOVE uABI")
> > Cc: <stable@vger.kernel.org>
> > Signed-off-by: Donggeun Yoo <donggeunyoo.kernel@gmail.com>
> > ---
> >  mm/userfaultfd.c | 2 ++
> >  1 file changed, 2 insertions(+)
> > 
> > diff --git a/mm/userfaultfd.c b/mm/userfaultfd.c
> > index 74f04c323c50..6495666c596b 100644
> > --- a/mm/userfaultfd.c
> > +++ b/mm/userfaultfd.c
> > @@ -1452,6 +1452,8 @@ static int move_swap_pte(struct mm_struct *mm, struct vm_area_struct *dst_vma,
> >  	/* Re-arm RWP on the moved swap entry if dst_vma is RWP-registered. */
> >  	if (userfaultfd_rwp(dst_vma))
> >  		orig_src_pte = pte_swp_mkuffd(orig_src_pte);
> > +	else
> > +		orig_src_pte = pte_swp_clear_uffd(orig_src_pte);
> >  	set_pte_at(mm, dst_addr, dst_pte, orig_src_pte);
> >  	double_pt_unlock(dst_ptl, src_ptl);
> >  
> 
> In move_present_ptes() we don't run into that issue as we create a new PTE from
> scratch
> 
> 	orig_dst_pte = folio_mk_pte(src_folio, dst_vma->vm_page_prot);
> 
> 
> Staring at the
> 
> 	if (userfaultfd_rwp(dst_vma))
> 
> I do wonder why we don't have to take similar care about wp ... I'm sure the is
> a good reason.
> 
> Code change itself LGTM.
> 
> 
> -- 
> Cheers,
> 
> David

-- 
Sincerely yours,
Mike.

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

* Re: [PATCH v1 1/2] userfaultfd: clear the inherited uffd bit in move_swap_pte()
  2026-09-19  0:46 ` [PATCH v1 1/2] " Donggeun Yoo
  2026-09-22  6:45   ` Mike Rapoport
  2026-09-22 11:40   ` David Hildenbrand (Arm)
@ 2026-09-23 11:18   ` Kiryl Shutsemau
  2 siblings, 0 replies; 11+ messages in thread
From: Kiryl Shutsemau @ 2026-09-23 11:18 UTC (permalink / raw)
  To: Donggeun Yoo
  Cc: akpm, rppt, peterx, surenb, aarcange, david, ljs, liam, vbabka,
	mhocko, shuah, linux-mm, linux-kselftest, linux-kernel, stable

On Sat, Sep 19, 2026 at 09:46:29AM +0900, Donggeun Yoo wrote:
> UFFDIO_MOVE on a swapped-out page installs the source PTE at the
> destination unchanged, so a uffd bit set on the source lands in a
> destination VMA that was never registered for write protection.  It is
> then permanent: the bit is dropped only by change_protection() under
> MM_CP_UFFD_{WP,RWP}_RESOLVE, which uffd_wp_range(), mrwprotect_range()
> and userfaultfd_clear_vma() issue only for a VMA registered in that
> mode.  pagemap reports the page as uffd-tracked, and MADV_COLLAPSE
> refuses the range while the bit is set, because collapse_scan_pmd() is
> strict about uffd on swap entries.
> 
> move_present_ptes() and move_zeropage_pte() build the destination PTE
> from dst_vma->vm_page_prot and arm RWP only when dst_vma asks for it, so
> the destination's own registration decides the result.  move_swap_pte()
> copies the source PTE instead and only ever sets the bit, never clears
> it, so one UFFDIO_MOVE behaves differently depending on whether the page
> happened to be resident.
> 
> Clear the uffd bit on the moved swap entry unless the destination is
> RWP-registered, as copy_nonpresent_pte() does where it installs a PTE
> into a destination that may not be armed.  A WP-registered destination
> stops inheriting the bit as well, which is already what it gets when the
> moved page is resident.
> 
> Fixes: adef440691ba ("userfaultfd: UFFDIO_MOVE uABI")
> Cc: <stable@vger.kernel.org>
> Signed-off-by: Donggeun Yoo <donggeunyoo.kernel@gmail.com>
> ---
>  mm/userfaultfd.c | 2 ++
>  1 file changed, 2 insertions(+)
> 
> diff --git a/mm/userfaultfd.c b/mm/userfaultfd.c
> index 74f04c323c50..6495666c596b 100644
> --- a/mm/userfaultfd.c
> +++ b/mm/userfaultfd.c
> @@ -1452,6 +1452,8 @@ static int move_swap_pte(struct mm_struct *mm, struct vm_area_struct *dst_vma,
>  	/* Re-arm RWP on the moved swap entry if dst_vma is RWP-registered. */
>  	if (userfaultfd_rwp(dst_vma))
>  		orig_src_pte = pte_swp_mkuffd(orig_src_pte);
> +	else
> +		orig_src_pte = pte_swp_clear_uffd(orig_src_pte);

Putting it in the 'else' is wrong. Clear uffd on the source
unconditionally and re-arm based on the target VMA:

	orig_src_pte = pte_swp_clear_uffd(orig_src_pte);
	/* Re-arm RWP on the moved swap entry if dst_vma is RWP-registered. */
	if (userfaultfd_rwp(dst_vma))
		orig_src_pte = pte_swp_mkuffd(orig_src_pte);

That is what move_pages_huge_pmd() does with clear_uffd_wp_pmd() before
its own re-arm, and it is the logically correct way to do this: the
source's state is gone, the destination decides.

>  	set_pte_at(mm, dst_addr, dst_pte, orig_src_pte);
>  	double_pt_unlock(dst_ptl, src_ptl);
>  
> -- 
> 2.53.0
> 

-- 
  Kiryl Shutsemau / Kirill A. Shutemov

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

* Re: [PATCH v1 1/2] userfaultfd: clear the inherited uffd bit in move_swap_pte()
  2026-09-22 11:40   ` David Hildenbrand (Arm)
  2026-09-22 12:41     ` Donggeun Yoo
  2026-09-22 16:45     ` Mike Rapoport
@ 2026-09-23 11:20     ` Kiryl Shutsemau
  2 siblings, 0 replies; 11+ messages in thread
From: Kiryl Shutsemau @ 2026-09-23 11:20 UTC (permalink / raw)
  To: David Hildenbrand (Arm)
  Cc: Donggeun Yoo, akpm, rppt, peterx, surenb, aarcange, ljs, liam,
	vbabka, mhocko, shuah, linux-mm, linux-kselftest, linux-kernel,
	stable

On Tue, Sep 22, 2026 at 01:40:51PM +0200, David Hildenbrand (Arm) wrote:
> On 9/19/26 02:46, Donggeun Yoo wrote:
> > UFFDIO_MOVE on a swapped-out page installs the source PTE at the
> > destination unchanged, so a uffd bit set on the source lands in a
> > destination VMA that was never registered for write protection.  It is
> > then permanent: the bit is dropped only by change_protection() under
> > MM_CP_UFFD_{WP,RWP}_RESOLVE, which uffd_wp_range(), mrwprotect_range()
> > and userfaultfd_clear_vma() issue only for a VMA registered in that
> > mode.  pagemap reports the page as uffd-tracked, and MADV_COLLAPSE
> > refuses the range while the bit is set, because collapse_scan_pmd() is
> > strict about uffd on swap entries.
> > 
> > move_present_ptes() and move_zeropage_pte() build the destination PTE
> > from dst_vma->vm_page_prot and arm RWP only when dst_vma asks for it, so
> > the destination's own registration decides the result.  move_swap_pte()
> > copies the source PTE instead and only ever sets the bit, never clears
> > it, so one UFFDIO_MOVE behaves differently depending on whether the page
> > happened to be resident.
> > 
> > Clear the uffd bit on the moved swap entry unless the destination is
> > RWP-registered, as copy_nonpresent_pte() does where it installs a PTE
> > into a destination that may not be armed.  A WP-registered destination
> > stops inheriting the bit as well, which is already what it gets when the
> > moved page is resident.
> > 
> > Fixes: adef440691ba ("userfaultfd: UFFDIO_MOVE uABI")
> > Cc: <stable@vger.kernel.org>
> > Signed-off-by: Donggeun Yoo <donggeunyoo.kernel@gmail.com>
> > ---
> >  mm/userfaultfd.c | 2 ++
> >  1 file changed, 2 insertions(+)
> > 
> > diff --git a/mm/userfaultfd.c b/mm/userfaultfd.c
> > index 74f04c323c50..6495666c596b 100644
> > --- a/mm/userfaultfd.c
> > +++ b/mm/userfaultfd.c
> > @@ -1452,6 +1452,8 @@ static int move_swap_pte(struct mm_struct *mm, struct vm_area_struct *dst_vma,
> >  	/* Re-arm RWP on the moved swap entry if dst_vma is RWP-registered. */
> >  	if (userfaultfd_rwp(dst_vma))
> >  		orig_src_pte = pte_swp_mkuffd(orig_src_pte);
> > +	else
> > +		orig_src_pte = pte_swp_clear_uffd(orig_src_pte);
> >  	set_pte_at(mm, dst_addr, dst_pte, orig_src_pte);
> >  	double_pt_unlock(dst_ptl, src_ptl);
> >  
> 
> In move_present_ptes() we don't run into that issue as we create a new PTE from
> scratch
> 
> 	orig_dst_pte = folio_mk_pte(src_folio, dst_vma->vm_page_prot);
> 
> 
> Staring at the
> 
> 	if (userfaultfd_rwp(dst_vma))
> 
> I do wonder why we don't have to take similar care about wp ... I'm sure the is
> a good reason.

The RWP branch does not preserve anything from the source. It arms the
destination whether or not the source had the bit.

WP has nothing to arm. The bit is per-PTE state that userspace sets
explicitly with UFFDIO_WRITEPROTECT or UFFDIO_COPY_MODE_WP. Registration
alone protects nothing, and UFFDIO_MOVE has no MODE_WP flag, so a moved
page in a WP destination starts unprotected, same as a faulted or
copied one.

-- 
  Kiryl Shutsemau / Kirill A. Shutemov

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

end of thread, other threads:[~2026-09-23 11:20 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-19  0:46 [PATCH v1 0/2] userfaultfd: clear the inherited uffd bit in move_swap_pte() Donggeun Yoo
2026-09-19  0:46 ` [PATCH v1 1/2] " Donggeun Yoo
2026-09-22  6:45   ` Mike Rapoport
2026-09-22 12:47     ` Donggeun Yoo
2026-09-22 11:40   ` David Hildenbrand (Arm)
2026-09-22 12:41     ` Donggeun Yoo
2026-09-22 16:45     ` Mike Rapoport
2026-09-23 11:20     ` Kiryl Shutsemau
2026-09-23 11:18   ` Kiryl Shutsemau
2026-09-19  0:46 ` [PATCH v1 2/2] selftests/mm: add a test for UFFDIO_MOVE of a write-protected swap entry Donggeun Yoo
2026-09-22  5:58 ` [PATCH v1 0/2] userfaultfd: clear the inherited uffd bit in move_swap_pte() Mike Rapoport

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®