* [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®