From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 31A973BE14A for ; Wed, 5 Aug 2026 02:23:45 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785896626; cv=none; b=XL0JiSfSZpw4FD+0lsPkFJPAXv43ouvU/9r78BHLbFatqceyiiXsUh5h5dm+XeuH1xrRSG5mYtHOF6UIy/AgVbQbpmQvFweBtcLS7NjuKdX/REllKT9YM4pRSuKJ+MreS9Eeuu43T+k86kLIHUoJo89oHvKD0d+QmQhXUit0olk= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785896626; c=relaxed/simple; bh=Twmx+jI0VD2fa2oV9DpjVlCmK/La1oiKbEcBuoo7FPA=; h=Date:To:From:Subject:Message-Id; b=Awco0w9hCs29uk08jW2qG3hlG6f59jbJU6mLpQuVKL5Qr9ixGv72w+D9YX63ieH1aFD7DQAiOr03h9tHe9FTpRxZzdVpothOWUlWbLIDNo6tDq5WeCrFsJzAl3NBwlzkCpRn6UegR7dJJWr48fl+EVMCFgZvN+zC5c4K07Pyo0g= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linux-foundation.org header.i=@linux-foundation.org header.b=fUn6qIfi; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linux-foundation.org header.i=@linux-foundation.org header.b="fUn6qIfi" Received: by smtp.kernel.org (Postfix) with ESMTPSA id EED5D1F00A3A; Wed, 5 Aug 2026 02:23:44 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linux-foundation.org; s=korg; t=1785896625; bh=q/rskBL5ZcZbj9X7Q5LH2EYroVaS0tYVdlAwlcmM458=; h=Date:To:From:Subject; b=fUn6qIfio36QtX5hrX0GuXID7QmT8iUG0XQj718YW8BBefAKeiIqhg27uE1IPgJHt yl15xJa37/T1YlBu9wonAYu8ySSNuhORiR1UB/eq3jrIiu2FEmyTyVT+YsysfyrjmY rV/gU2khw0pq9ehYuoQOhlZ/4hexeRQ4S/JVtizs= Date: Tue, 04 Aug 2026 19:23:44 -0700 To: mm-commits@vger.kernel.org,ziy@nvidia.com,vbabka@kernel.org,surenb@google.com,sj@kernel.org,seanjc@google.com,rppt@kernel.org,peterx@redhat.com,pbonzini@redhat.com,ljs@kernel.org,liam@infradead.org,jthoughton@google.com,david@kernel.org,corbet@lwn.net,aarcange@redhat.com,kas@kernel.org,akpm@linux-foundation.org From: Andrew Morton Subject: [merged mm-stable] mm-add-mm_cp_uffd_rwp-change_protection-flag.patch removed from -mm tree Message-Id: <20260805022344.EED5D1F00A3A@smtp.kernel.org> Precedence: bulk X-Mailing-List: mm-commits@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: The quilt patch titled Subject: mm: add MM_CP_UFFD_RWP change_protection() flag has been removed from the -mm tree. Its filename was mm-add-mm_cp_uffd_rwp-change_protection-flag.patch This patch was dropped because it was merged into the mm-stable branch of git://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm ------------------------------------------------------ From: "Kiryl Shutsemau (Meta)" Subject: mm: add MM_CP_UFFD_RWP change_protection() flag Date: Wed, 8 Jul 2026 12:14:07 +0100 Preparatory patch. Add the change_protection() primitive that userfaultfd RWP will use. An RWP-protected PTE is PAGE_NONE with the uffd PTE bit set. The PROT_NONE half makes the CPU fault on any access; the uffd bit distinguishes an RWP fault from a plain mprotect(PROT_NONE) or NUMA hinting fault. MM_CP_UFFD_WP and MM_CP_UFFD_RWP share the same PTE bit, so the two cannot be used together on the same range. Two new change_protection() flags: MM_CP_UFFD_RWP install PAGE_NONE and set the uffd bit MM_CP_UFFD_RWP_RESOLVE restore vma->vm_page_prot, clear the uffd bit Both are wired through change_pte_range(), change_huge_pmd(), and hugetlb_change_protection() so anon, shmem, THP, and hugetlb all share the same semantics. Link: https://lore.kernel.org/20260708111417.173443-7-kirill@shutemov.name Signed-off-by: Kiryl Shutsemau Assisted-by: Claude:claude-opus-4-6 Reviewed-by: Mike Rapoport (Microsoft) Reviewed-by: SeongJae Park Cc: Andrea Arcangeli Cc: David Hildenbrand Cc: James Houghton Cc: Jonathan Corbet Cc: Liam Howlett Cc: Lorenzo Stoakes Cc: Paolo Bonzini Cc: Peter Xu Cc: Sean Christopherson Cc: Suren Baghdasaryan Cc: Vlastimil Babka Cc: Zi Yan Signed-off-by: Andrew Morton --- include/linux/mm.h | 5 +++ include/linux/userfaultfd_k.h | 1 mm/huge_memory.c | 36 +++++++++++++----------- mm/hugetlb.c | 25 ++++++++++++---- mm/mprotect.c | 48 ++++++++++++++++++++++++-------- 5 files changed, 81 insertions(+), 34 deletions(-) --- a/include/linux/mm.h~mm-add-mm_cp_uffd_rwp-change_protection-flag +++ a/include/linux/mm.h @@ -3340,6 +3340,11 @@ int get_cmdline(struct task_struct *task #define MM_CP_UFFD_WP_RESOLVE (1UL << 3) /* Resolve wp */ #define MM_CP_UFFD_WP_ALL (MM_CP_UFFD_WP | \ MM_CP_UFFD_WP_RESOLVE) +/* Whether this change is for uffd RWP */ +#define MM_CP_UFFD_RWP (1UL << 4) /* do rwp */ +#define MM_CP_UFFD_RWP_RESOLVE (1UL << 5) /* resolve rwp */ +#define MM_CP_UFFD_RWP_ALL (MM_CP_UFFD_RWP | \ + MM_CP_UFFD_RWP_RESOLVE) bool can_change_pte_writable(struct vm_area_struct *vma, unsigned long addr, pte_t pte); --- a/include/linux/userfaultfd_k.h~mm-add-mm_cp_uffd_rwp-change_protection-flag +++ a/include/linux/userfaultfd_k.h @@ -369,7 +369,6 @@ static inline bool userfaultfd_huge_pmd_ return false; } - static inline bool userfaultfd_armed(struct vm_area_struct *vma) { return false; --- a/mm/huge_memory.c~mm-add-mm_cp_uffd_rwp-change_protection-flag +++ a/mm/huge_memory.c @@ -2553,8 +2553,8 @@ bool move_huge_pmd(struct vm_area_struct } static void change_non_present_huge_pmd(struct mm_struct *mm, - unsigned long addr, pmd_t *pmd, bool uffd_wp, - bool uffd_wp_resolve) + unsigned long addr, pmd_t *pmd, bool uffd_prot, + bool uffd_prot_resolve) { softleaf_t entry = softleaf_from_pmd(*pmd); pmd_t newpmd; @@ -2583,9 +2583,9 @@ static void change_non_present_huge_pmd( newpmd = *pmd; } - if (uffd_wp) + if (uffd_prot) newpmd = pmd_swp_mkuffd(newpmd); - else if (uffd_wp_resolve) + else if (uffd_prot_resolve) newpmd = pmd_swp_clear_uffd(newpmd); if (!pmd_same(*pmd, newpmd)) set_pmd_at(mm, addr, pmd, newpmd); @@ -2606,8 +2606,9 @@ int change_huge_pmd(struct mmu_gather *t spinlock_t *ptl; pmd_t oldpmd, entry; bool prot_numa = cp_flags & MM_CP_PROT_NUMA; - bool uffd_wp = cp_flags & MM_CP_UFFD_WP; - bool uffd_wp_resolve = cp_flags & MM_CP_UFFD_WP_RESOLVE; + bool uffd_prot = cp_flags & (MM_CP_UFFD_WP | MM_CP_UFFD_RWP); + bool uffd_prot_resolve = cp_flags & + (MM_CP_UFFD_WP_RESOLVE | MM_CP_UFFD_RWP_RESOLVE); int ret = 1; tlb_change_page_size(tlb, HPAGE_PMD_SIZE); @@ -2620,11 +2621,17 @@ int change_huge_pmd(struct mmu_gather *t return 0; if (thp_migration_supported() && pmd_is_valid_softleaf(*pmd)) { - change_non_present_huge_pmd(mm, addr, pmd, uffd_wp, - uffd_wp_resolve); + change_non_present_huge_pmd(mm, addr, pmd, uffd_prot, + uffd_prot_resolve); goto unlock; } + /* Already in the desired state */ + if (prot_numa && pmd_protnone(*pmd)) + goto unlock; + if ((cp_flags & MM_CP_UFFD_RWP) && pmd_protnone(*pmd) && pmd_uffd(*pmd)) + goto unlock; + if (prot_numa) { /* @@ -2635,9 +2642,6 @@ int change_huge_pmd(struct mmu_gather *t if (is_huge_zero_pmd(*pmd)) goto unlock; - if (pmd_protnone(*pmd)) - goto unlock; - if (!folio_can_map_prot_numa(pmd_folio(*pmd), vma, vma_is_single_threaded_private(vma))) goto unlock; @@ -2666,9 +2670,9 @@ int change_huge_pmd(struct mmu_gather *t oldpmd = pmdp_invalidate_ad(vma, addr, pmd); entry = pmd_modify(oldpmd, newprot); - if (uffd_wp) + if (uffd_prot) entry = pmd_mkuffd(entry); - else if (uffd_wp_resolve) + else if (uffd_prot_resolve) /* * Leave the write bit to be handled by PF interrupt * handler, then things like COW could be properly @@ -2714,10 +2718,10 @@ int change_huge_pud(struct mmu_gather *t return 1; /* - * Huge entries on userfault-wp only works with anonymous, while we - * don't have anonymous PUDs yet. + * Huge entries on userfault-wp or userfault-rwp only work with + * anonymous, while we don't have anonymous PUDs yet. */ - if (WARN_ON_ONCE(cp_flags & MM_CP_UFFD_WP_ALL)) + if (WARN_ON_ONCE(cp_flags & (MM_CP_UFFD_WP_ALL | MM_CP_UFFD_RWP_ALL))) return 1; ptl = __pud_trans_huge_lock(pudp, vma); --- a/mm/hugetlb.c~mm-add-mm_cp_uffd_rwp-change_protection-flag +++ a/mm/hugetlb.c @@ -6427,6 +6427,8 @@ long hugetlb_change_protection(struct vm unsigned long last_addr_mask; bool uffd_wp = cp_flags & MM_CP_UFFD_WP; bool uffd_wp_resolve = cp_flags & MM_CP_UFFD_WP_RESOLVE; + bool uffd_rwp = cp_flags & MM_CP_UFFD_RWP; + bool uffd_rwp_resolve = cp_flags & MM_CP_UFFD_RWP_RESOLVE; struct mmu_gather tlb; /* @@ -6452,6 +6454,11 @@ long hugetlb_change_protection(struct vm ptep = hugetlb_walk(vma, address, psize); if (!ptep) { + /* + * uffd_wp installs a pte marker on the unpopulated + * entry; uffd_rwp does not install markers so the + * allocation is unnecessary for it. + */ if (!uffd_wp) { address |= last_addr_mask; continue; @@ -6473,7 +6480,8 @@ long hugetlb_change_protection(struct vm * shouldn't happen at all. Warn about it if it * happened due to some reason. */ - WARN_ON_ONCE(uffd_wp || uffd_wp_resolve); + WARN_ON_ONCE(uffd_wp || uffd_wp_resolve || + uffd_rwp || uffd_rwp_resolve); pages++; spin_unlock(ptl); address |= last_addr_mask; @@ -6507,9 +6515,9 @@ long hugetlb_change_protection(struct vm pages++; } - if (uffd_wp) + if (uffd_wp || uffd_rwp) newpte = pte_swp_mkuffd(newpte); - else if (uffd_wp_resolve) + else if (uffd_wp_resolve || uffd_rwp_resolve) newpte = pte_swp_clear_uffd(newpte); if (!pte_same(pte, newpte)) set_huge_pte_at(mm, address, ptep, newpte, psize); @@ -6520,19 +6528,24 @@ long hugetlb_change_protection(struct vm * pte_marker_uffd_wp()==true implies !poison * because they're mutual exclusive. */ - if (pte_is_uffd_wp_marker(pte) && uffd_wp_resolve) + if (pte_is_uffd_wp_marker(pte) && + (uffd_wp_resolve || uffd_rwp_resolve)) /* Safe to modify directly (non-present->none). */ huge_pte_clear(mm, address, ptep, psize); } else { pte_t old_pte; unsigned int shift = huge_page_shift(hstate_vma(vma)); + /* Already protnone with uffd bit set? Nothing to do. */ + if (uffd_rwp && pte_protnone(pte) && huge_pte_uffd(pte)) + goto next; + old_pte = huge_ptep_modify_prot_start(vma, address, ptep); pte = huge_pte_modify(old_pte, newprot); pte = arch_make_huge_pte(pte, shift, vma->vm_flags); - if (uffd_wp) + if (uffd_wp || uffd_rwp) pte = huge_pte_mkuffd(pte); - else if (uffd_wp_resolve) + else if (uffd_wp_resolve || uffd_rwp_resolve) pte = huge_pte_clear_uffd(pte); huge_ptep_modify_prot_commit(vma, address, ptep, old_pte, pte); pages++; --- a/mm/mprotect.c~mm-add-mm_cp_uffd_rwp-change_protection-flag +++ a/mm/mprotect.c @@ -214,8 +214,9 @@ static __always_inline void set_write_pr static long change_softleaf_pte(struct vm_area_struct *vma, unsigned long addr, pte_t *pte, pte_t oldpte, unsigned long cp_flags) { - const bool uffd_wp = cp_flags & MM_CP_UFFD_WP; - const bool uffd_wp_resolve = cp_flags & MM_CP_UFFD_WP_RESOLVE; + const bool uffd_prot = cp_flags & (MM_CP_UFFD_WP | MM_CP_UFFD_RWP); + const bool uffd_prot_resolve = cp_flags & + (MM_CP_UFFD_WP_RESOLVE | MM_CP_UFFD_RWP_RESOLVE); softleaf_t entry = softleaf_from_pte(oldpte); pte_t newpte; @@ -256,7 +257,7 @@ static long change_softleaf_pte(struct v * to unprotect it, drop it; the next page * fault will trigger without uffd trapping. */ - if (uffd_wp_resolve) { + if (uffd_prot_resolve) { pte_clear(vma->vm_mm, addr, pte); return 1; } @@ -265,9 +266,9 @@ static long change_softleaf_pte(struct v newpte = oldpte; } - if (uffd_wp) + if (uffd_prot) newpte = pte_swp_mkuffd(newpte); - else if (uffd_wp_resolve) + else if (uffd_prot_resolve) newpte = pte_swp_clear_uffd(newpte); if (!pte_same(oldpte, newpte)) { @@ -282,16 +283,17 @@ static __always_inline void change_prese int nr_ptes, unsigned long end, pgprot_t newprot, struct folio *folio, struct page *page, unsigned long cp_flags) { - const bool uffd_wp_resolve = cp_flags & MM_CP_UFFD_WP_RESOLVE; - const bool uffd_wp = cp_flags & MM_CP_UFFD_WP; + const bool uffd_prot = cp_flags & (MM_CP_UFFD_WP | MM_CP_UFFD_RWP); + const bool uffd_prot_resolve = cp_flags & + (MM_CP_UFFD_WP_RESOLVE | MM_CP_UFFD_RWP_RESOLVE); pte_t ptent, oldpte; oldpte = modify_prot_start_ptes(vma, addr, ptep, nr_ptes); ptent = pte_modify(oldpte, newprot); - if (uffd_wp) + if (uffd_prot) ptent = pte_mkuffd(ptent); - else if (uffd_wp_resolve) + else if (uffd_prot_resolve) ptent = pte_clear_uffd(ptent); /* @@ -325,6 +327,7 @@ static long change_pte_range(struct mmu_ long pages = 0; bool is_private_single_threaded; bool prot_numa = cp_flags & MM_CP_PROT_NUMA; + bool uffd_rwp = cp_flags & MM_CP_UFFD_RWP; bool uffd_wp = cp_flags & MM_CP_UFFD_WP; int nr_ptes; @@ -350,6 +353,14 @@ static long change_pte_range(struct mmu_ /* Already in the desired state. */ if (prot_numa && pte_protnone(oldpte)) continue; + /* + * RWP-protected PTEs carry _PAGE_UFFD as a marker on + * top of PROT_NONE. Skip only entries already in that + * exact state; plain PROT_NONE from mprotect() still needs + * to be promoted so future faults can be distinguished. + */ + if (uffd_rwp && pte_protnone(oldpte) && pte_uffd(oldpte)) + continue; page = vm_normal_page(vma, addr, oldpte); if (page) @@ -358,6 +369,8 @@ static long change_pte_range(struct mmu_ /* * Avoid trapping faults against the zero or KSM * pages. See similar comment in change_huge_pmd. + * Skip this filter for uffd RWP which + * must set protnone regardless of NUMA placement. */ if (prot_numa && !folio_can_map_prot_numa(folio, vma, @@ -428,7 +441,7 @@ pgtable_split_needed(struct vm_area_stru * (e.g. 2M shmem) because file thp is handled differently when * split by erasing the pmd so far. */ - return (cp_flags & MM_CP_UFFD_WP) && !vma_is_anonymous(vma); + return (cp_flags & (MM_CP_UFFD_WP | MM_CP_UFFD_RWP)) && !vma_is_anonymous(vma); } /* @@ -667,7 +680,16 @@ long change_protection(struct mmu_gather pgprot_t newprot = vma->vm_page_prot; long pages; - BUG_ON((cp_flags & MM_CP_UFFD_WP_ALL) == MM_CP_UFFD_WP_ALL); + /* + * MM_CP_UFFD_{WP,RWP} and _RESOLVE are mutually exclusive within one + * change, and WP and RWP cannot mix. Miswired callers get a warn and + * a no-op; userspace cannot reach this state. + */ + if (WARN_ON_ONCE((cp_flags & MM_CP_UFFD_WP_ALL) == MM_CP_UFFD_WP_ALL || + (cp_flags & MM_CP_UFFD_RWP_ALL) == MM_CP_UFFD_RWP_ALL || + ((cp_flags & MM_CP_UFFD_WP_ALL) && + (cp_flags & MM_CP_UFFD_RWP_ALL)))) + return 0; #ifdef CONFIG_NUMA_BALANCING /* @@ -681,6 +703,10 @@ long change_protection(struct mmu_gather WARN_ON_ONCE(cp_flags & MM_CP_PROT_NUMA); #endif + if (IS_ENABLED(CONFIG_ARCH_HAS_PTE_PROTNONE) && + (cp_flags & MM_CP_UFFD_RWP)) + newprot = PAGE_NONE; + if (is_vm_hugetlb_page(vma)) pages = hugetlb_change_protection(vma, start, end, newprot, cp_flags); _ Patches currently in -mm which might be from kas@kernel.org are selftests-mm-add-pagemap_scan-test-for-thp-pmd-holes.patch mm-huge_memory-remove-unused-can_split_folio.patch mm-huge_memory-fold-split_folio_to_list_to_order-into-split_folio_to_order.patch mm-fold-userfaultfd_rwp-to-false-without-config_arch_has_pte_protnone.patch