move_pages_huge_pmd() builds the destination PMD for the huge zero page by copying the source PMD and clearing its uffd bit, so the destination keeps the protection of the source. A source VMA registered for RWP can map the huge zero page with a PAGE_NONE PMD, and UFFDIO_MOVE does not check how the source is registered. The destination then gets a PAGE_NONE PMD without the uffd bit, although nothing asked for that in the destination VMA. The next access fixes it up through the NUMA hinting fault path. Until then, the first access to the destination takes a spurious fault and GUP has to take the fault path. The impact on userspace is small. The moved entry must have the protection of the destination VMA. To fix it, build the entry from the vm_page_prot of the destination VMA, as move_zeropage_pte() does for the small zero page, and keep marking it soft-dirty. A destination VMA registered for RWP still gets its PMD re-armed afterwards. Fixes: 763f43865fc4 ("mm: preserve RWP marker across PTE rewrites") Signed-off-by: Tal Zussman --- mm/huge_memory.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mm/huge_memory.c b/mm/huge_memory.c index 0d589be136d2..09176b290f76 100644 --- a/mm/huge_memory.c +++ b/mm/huge_memory.c @@ -2981,9 +2981,9 @@ int move_pages_huge_pmd(struct mm_struct *mm, pmd_t *dst_pmd, pmd_t *src_pmd, pm /* Follow mremap() behavior and treat the entry dirty after the move */ _dst_pmd = pmd_mkwrite(pmd_mkdirty(_dst_pmd), dst_vma); } else { - src_pmdval = pmdp_huge_clear_flush(src_vma, src_addr, src_pmd); - _dst_pmd = move_soft_dirty_pmd(src_pmdval); - _dst_pmd = clear_uffd_wp_pmd(_dst_pmd); + pmdp_huge_clear_flush(src_vma, src_addr, src_pmd); + _dst_pmd = folio_mk_pmd(huge_zero_folio, dst_vma->vm_page_prot); + _dst_pmd = move_soft_dirty_pmd(pmd_mkspecial(_dst_pmd)); } /* Re-arm RWP on the moved PMD if dst_vma is RWP-registered. */ -- 2.39.5