mm/userfaultfd.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-)
move_present_ptes() unconditionally makes the destination PTE writable,
dropping uffd-wp write-protection from the source PTE.
The original intent was to follow mremap() behavior, but mremap()'s
move_ptes() preserves the source write state unconditionally.
Modify uffd to preserve the source write state and check the uffd-wp
condition of the source before setting writable on the destination.
Fixes: adef440691ba ("userfaultfd: UFFDIO_MOVE uABI")
Cc: stable@vger.kernel.org
Signed-off-by: Gregory Price <gourry@gourry.net>
---
mm/userfaultfd.c | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/mm/userfaultfd.c b/mm/userfaultfd.c
index e6dfd5f28acd..783ca68aed88 100644
--- a/mm/userfaultfd.c
+++ b/mm/userfaultfd.c
@@ -1123,7 +1123,10 @@ static long move_present_ptes(struct mm_struct *mm,
orig_dst_pte = pte_mksoft_dirty(orig_dst_pte);
if (pte_dirty(orig_src_pte))
orig_dst_pte = pte_mkdirty(orig_dst_pte);
- orig_dst_pte = pte_mkwrite(orig_dst_pte, dst_vma);
+ if (pte_write(orig_src_pte))
+ orig_dst_pte = pte_mkwrite(orig_dst_pte, dst_vma);
+ if (pte_uffd_wp(orig_src_pte))
+ orig_dst_pte = pte_mkuffd_wp(orig_dst_pte);
set_pte_at(mm, dst_addr, dst_pte, orig_dst_pte);
src_addr += PAGE_SIZE;
--
2.52.0
On Thu, 9 Apr 2026 11:28:22 -0400 Gregory Price <gourry@gourry.net> wrote:
> move_present_ptes() unconditionally makes the destination PTE writable,
> dropping uffd-wp write-protection from the source PTE.
>
> The original intent was to follow mremap() behavior, but mremap()'s
> move_ptes() preserves the source write state unconditionally.
>
> Modify uffd to preserve the source write state and check the uffd-wp
> condition of the source before setting writable on the destination.
Please can we have a description of the userspace-visible impact of the
bug.
> Fixes: adef440691ba ("userfaultfd: UFFDIO_MOVE uABI")
> Cc: stable@vger.kernel.org
especially when cc:stable, thanks.
> Signed-off-by: Gregory Price <gourry@gourry.net>
>
> ...
>
> --- a/mm/userfaultfd.c
> +++ b/mm/userfaultfd.c
> @@ -1123,7 +1123,10 @@ static long move_present_ptes(struct mm_struct *mm,
> orig_dst_pte = pte_mksoft_dirty(orig_dst_pte);
> if (pte_dirty(orig_src_pte))
> orig_dst_pte = pte_mkdirty(orig_dst_pte);
> - orig_dst_pte = pte_mkwrite(orig_dst_pte, dst_vma);
> + if (pte_write(orig_src_pte))
> + orig_dst_pte = pte_mkwrite(orig_dst_pte, dst_vma);
> + if (pte_uffd_wp(orig_src_pte))
> + orig_dst_pte = pte_mkuffd_wp(orig_dst_pte);
> set_pte_at(mm, dst_addr, dst_pte, orig_dst_pte);
>
(presently wondering if this is backward compatible)
On Sun, Apr 12, 2026 at 11:18:07AM -0700, Andrew Morton wrote: > On Thu, 9 Apr 2026 11:28:22 -0400 Gregory Price <gourry@gourry.net> wrote: > > > move_present_ptes() unconditionally makes the destination PTE writable, > > dropping uffd-wp write-protection from the source PTE. > > > > The original intent was to follow mremap() behavior, but mremap()'s > > move_ptes() preserves the source write state unconditionally. > > > > Modify uffd to preserve the source write state and check the uffd-wp > > condition of the source before setting writable on the destination. > > Please can we have a description of the userspace-visible impact of the > bug. > Simply: UFFDIO_MOVE silently drops write protection from the source PTE when moving pages to a destination, leading to missing write-protect faults after the page has been moved. I ran into this while futzing around with some user space management of VM memory, and expecting a move to continue firing WP faults after. But Sashiko actually made a useful (though obtuse) observation which has made me realize _MOVE is actually ambiguous on what to do with source region UFFD modes. > > + if (pte_uffd_wp(orig_src_pte)) > > + orig_dst_pte = pte_mkuffd_wp(orig_dst_pte); This line assumes the destination must have intended to be WP, and the the result is essentially stale uffd wp bits in the opposite case (a user not intending to carry over WP now carries it over). tl;dr: this is more of a semantic change than I'd intended, and the existing tests did not catch it. The correct solution here is to make a UFFDIO_MOVE_MODE_WP flag to copy the UFFDIO_COPY_MODE_WP pattern. Otherwise: > (presently wondering if this is backward compatible) Yes, you're right to wonder - this does break backward compatibilty. Will come back around with UFFDIO_MOVE_MODE_WP. ~Gregory
On Thu, Apr 09, 2026 at 11:28:22AM -0400, Gregory Price wrote:
> move_present_ptes() unconditionally makes the destination PTE writable,
> dropping uffd-wp write-protection from the source PTE.
>
> The original intent was to follow mremap() behavior, but mremap()'s
> move_ptes() preserves the source write state unconditionally.
>
> Modify uffd to preserve the source write state and check the uffd-wp
> condition of the source before setting writable on the destination.
>
> Fixes: adef440691ba ("userfaultfd: UFFDIO_MOVE uABI")
> Cc: stable@vger.kernel.org
> Signed-off-by: Gregory Price <gourry@gourry.net>
Reviewed-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
> ---
> mm/userfaultfd.c | 5 ++++-
> 1 file changed, 4 insertions(+), 1 deletion(-)
>
> diff --git a/mm/userfaultfd.c b/mm/userfaultfd.c
> index e6dfd5f28acd..783ca68aed88 100644
> --- a/mm/userfaultfd.c
> +++ b/mm/userfaultfd.c
> @@ -1123,7 +1123,10 @@ static long move_present_ptes(struct mm_struct *mm,
> orig_dst_pte = pte_mksoft_dirty(orig_dst_pte);
> if (pte_dirty(orig_src_pte))
> orig_dst_pte = pte_mkdirty(orig_dst_pte);
> - orig_dst_pte = pte_mkwrite(orig_dst_pte, dst_vma);
> + if (pte_write(orig_src_pte))
> + orig_dst_pte = pte_mkwrite(orig_dst_pte, dst_vma);
> + if (pte_uffd_wp(orig_src_pte))
> + orig_dst_pte = pte_mkuffd_wp(orig_dst_pte);
> set_pte_at(mm, dst_addr, dst_pte, orig_dst_pte);
>
> src_addr += PAGE_SIZE;
> --
> 2.52.0
>
--
Sincerely yours,
Mike.
On Thu, Apr 9, 2026 at 8:28 AM Gregory Price <gourry@gourry.net> wrote:
>
> move_present_ptes() unconditionally makes the destination PTE writable,
> dropping uffd-wp write-protection from the source PTE.
>
> The original intent was to follow mremap() behavior, but mremap()'s
> move_ptes() preserves the source write state unconditionally.
>
> Modify uffd to preserve the source write state and check the uffd-wp
> condition of the source before setting writable on the destination.
>
> Fixes: adef440691ba ("userfaultfd: UFFDIO_MOVE uABI")
> Cc: stable@vger.kernel.org
> Signed-off-by: Gregory Price <gourry@gourry.net>
Reviewed-by: Suren Baghdasaryan <surenb@google.com>
> ---
> mm/userfaultfd.c | 5 ++++-
> 1 file changed, 4 insertions(+), 1 deletion(-)
>
> diff --git a/mm/userfaultfd.c b/mm/userfaultfd.c
> index e6dfd5f28acd..783ca68aed88 100644
> --- a/mm/userfaultfd.c
> +++ b/mm/userfaultfd.c
> @@ -1123,7 +1123,10 @@ static long move_present_ptes(struct mm_struct *mm,
> orig_dst_pte = pte_mksoft_dirty(orig_dst_pte);
> if (pte_dirty(orig_src_pte))
> orig_dst_pte = pte_mkdirty(orig_dst_pte);
> - orig_dst_pte = pte_mkwrite(orig_dst_pte, dst_vma);
> + if (pte_write(orig_src_pte))
> + orig_dst_pte = pte_mkwrite(orig_dst_pte, dst_vma);
> + if (pte_uffd_wp(orig_src_pte))
> + orig_dst_pte = pte_mkuffd_wp(orig_dst_pte);
> set_pte_at(mm, dst_addr, dst_pte, orig_dst_pte);
>
> src_addr += PAGE_SIZE;
> --
> 2.52.0
>
© 2016 - 2026 Red Hat, Inc.