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: <[email protected]>
> > Signed-off-by: Donggeun Yoo <[email protected]>
> > ---
> > 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