[PATCH v2 4/9] mm/mprotect: add MM_CP_PERM_UPGRADE

From: Carlos López

Date: Thu Nov 05 2026 - 12:07:52 EST


Add a change_protection() flag for changes that only add permissions,
keeping the PFN and revoking no access. mprotect() sets it when the new
VMA flags only add read, write or execute permissions. VM_ACCOUNT may be
added as well, as it does not affect the page tables. Any other change,
such as a different protection key, is not considered an upgrade.

This will allow MMU notifier users to perform optimizations, as they
can avoid invalidating PTEs whose permissions can be faulted in.

Signed-off-by: Carlos López <clopez@xxxxxxx>
---
include/linux/mm.h | 2 ++
mm/mprotect.c | 13 +++++++++++++
2 files changed, 15 insertions(+)

diff --git a/include/linux/mm.h b/include/linux/mm.h
index 9f167950926a..dddbd76a2760 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -3581,6 +3581,8 @@ int get_cmdline(struct task_struct *task, char *buffer, int buflen);
#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)
+/* Whether this change only upgrades/adds permissions */
+#define MM_CP_PERM_UPGRADE (1UL << 6)

bool can_change_pte_writable(struct vm_area_struct *vma, unsigned long addr,
pte_t pte);
diff --git a/mm/mprotect.c b/mm/mprotect.c
index 754333698c1e..683131c9c4a0 100644
--- a/mm/mprotect.c
+++ b/mm/mprotect.c
@@ -756,6 +756,17 @@ static const struct mm_walk_ops prot_none_walk_ops = {
.walk_lock = PGWALK_WRLOCK,
};

+static bool vma_flags_is_perm_upgrade(vma_flags_t old_vma_flags,
+ vma_flags_t new_vma_flags)
+{
+ vm_flags_t old = vma_flags_to_legacy(old_vma_flags);
+ vm_flags_t new = vma_flags_to_legacy(new_vma_flags);
+ /* VM_ACCOUNT does not affect page tables */
+ vm_flags_t mask = VM_READ | VM_WRITE | VM_EXEC | VM_ACCOUNT;
+
+ return new == (old | (new & mask));
+}
+
int
mprotect_fixup(struct vma_iterator *vmi, struct mmu_gather *tlb,
struct vm_area_struct *vma, struct vm_area_struct **pprev,
@@ -836,6 +847,8 @@ mprotect_fixup(struct vma_iterator *vmi, struct mmu_gather *tlb,
vma_flags_reset_once(vma, &new_vma_flags);
if (vma_wants_manual_pte_write_upgrade(vma))
mm_cp_flags |= MM_CP_TRY_CHANGE_WRITABLE;
+ if (vma_flags_is_perm_upgrade(old_vma_flags, new_vma_flags))
+ mm_cp_flags |= MM_CP_PERM_UPGRADE;
vma_set_page_prot(vma);

change_protection(tlb, vma, start, end, mm_cp_flags);
--
2.51.0