replace_page() maps the KSM page with a PTE built from scratch, so the soft-dirty and uffd bits of the PTE it replaces are lost. Due to this, a write before the merge no longer shows as soft-dirty, the next write to a uffd-wp page raises no write-protect fault, and because the page is mapped with the VMA's normal permissions, on a uffd-rwp VMA the next access raises no RWP fault. Copy the soft-dirty and uffd bits from the original PTE to the new PTE, and restore PAGE_NONE for RWP VMAs, as try_to_map_unused_to_zeropage() does. Fixes: 292924b26024 ("userfaultfd: wp: apply _PAGE_UFFD_WP bit") Fixes: 0f8975ec4db2 ("mm: soft-dirty bits for user memory changes tracking") Fixes: 763f43865fc4 ("mm: preserve RWP marker across PTE rewrites") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Samarth Narang --- mm/ksm.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/mm/ksm.c b/mm/ksm.c index dbfb8f98b189..b0d0555aa1b5 100644 --- a/mm/ksm.c +++ b/mm/ksm.c @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -1463,6 +1464,18 @@ static int replace_page(struct vm_area_struct *vma, struct page *page, dec_mm_counter(mm, MM_ANONPAGES); } + /* + * The old PTE may carry soft-dirty and uffd-wp state that tracks writes + * to this address. Make sure to preserve these bits in the new PTE. + * On a uffd-rwp VMA, also restore PAGE_NONE. + */ + if (pte_soft_dirty(orig_pte)) + newpte = pte_mksoft_dirty(newpte); + if (pte_uffd(orig_pte)) + newpte = pte_mkuffd(newpte); + if (pte_uffd(orig_pte) && userfaultfd_rwp(vma)) + newpte = pte_modify(newpte, PAGE_NONE); + flush_cache_page(vma, addr, pte_pfn(ptep_get(ptep))); /* * No need to notify as we are replacing a read only page with another base-commit: 48fe11c8d0f1e4e59426a3606f20ac1130b6d8dc -- 2.53.0