[RFC PATCH 1/4] mm/mprotect: add MM_CP_PERM_UPGRADE

From: Carlos López

Date: Thu Oct 29 2026 - 10:47:26 EST


Add a change_protection() flag for changes that only add permissions:
the PFN stays the same and no access is revoked. mprotect() sets it when
the new VMA permissions are a superset of the old, and the protection
key is unchanged.

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 691cef038d1e..f406ce9d1032 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -3415,6 +3415,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 2888ee638d87..e9b636809c42 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_flags_t rwx = VM_READ | VM_WRITE | VM_EXEC;
+
+ return !(old & rwx & ~new) &&
+ (old & ARCH_VM_PKEY_FLAGS) == (new & ARCH_VM_PKEY_FLAGS);
+}
+
int
mprotect_fixup(struct vma_iterator *vmi, struct mmu_gather *tlb,
struct vm_area_struct *vma, struct vm_area_struct **pprev,
@@ -837,6 +848,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