Add a range flag marking an invalidation that only upgrades permissions (that is, no PFN change and no revoked access). change_protection() sets it for MM_CP_PERM_UPGRADE and for the userfaultfd permission resolves, which unprotect a page and thus only restore access. A secondary MMU can use the flag to keep its mappings rather than zap them. Consumers that ignore it are unaffected. Signed-off-by: Carlos López --- include/linux/mm.h | 6 ++++++ include/linux/mmu_notifier.h | 5 +++++ mm/hugetlb.c | 6 +++++- mm/mprotect.c | 7 ++++++- 4 files changed, 22 insertions(+), 2 deletions(-) diff --git a/include/linux/mm.h b/include/linux/mm.h index f406ce9d1032..c4fc3876a4b7 100644 --- a/include/linux/mm.h +++ b/include/linux/mm.h @@ -3418,6 +3418,12 @@ int get_cmdline(struct task_struct *task, char *buffer, int buflen); /* Whether this change only upgrades/adds permissions */ #define MM_CP_PERM_UPGRADE (1UL << 6) +static inline bool mm_cp_is_perm_upgrade(unsigned long cp_flags) +{ + return cp_flags & (MM_CP_PERM_UPGRADE | MM_CP_UFFD_WP_RESOLVE | + MM_CP_UFFD_RWP_RESOLVE); +} + bool can_change_pte_writable(struct vm_area_struct *vma, unsigned long addr, pte_t pte); extern long change_protection(struct mmu_gather *tlb, diff --git a/include/linux/mmu_notifier.h b/include/linux/mmu_notifier.h index a11a44eef521..112be28cdf6b 100644 --- a/include/linux/mmu_notifier.h +++ b/include/linux/mmu_notifier.h @@ -60,6 +60,11 @@ enum mmu_notifier_event { }; #define MMU_NOTIFIER_RANGE_BLOCKABLE (1 << 0) +/* + * Set on an invalidation that only expands permissions without + * revoking any existing access, nor freeing/moving the underlying page. + */ +#define MMU_NOTIFIER_RANGE_PERM_UPGRADE (1 << 1) struct mmu_notifier_ops { /* diff --git a/mm/hugetlb.c b/mm/hugetlb.c index b017ae1dfd6f..68d44044fdd2 100644 --- a/mm/hugetlb.c +++ b/mm/hugetlb.c @@ -6548,19 +6548,23 @@ long hugetlb_change_protection(struct vm_area_struct *vma, long pages = 0, psize = huge_page_size(h); struct mmu_notifier_range range; unsigned long last_addr_mask; + unsigned int mn_flags = 0; 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; + if (mm_cp_is_perm_upgrade(cp_flags)) + mn_flags |= MMU_NOTIFIER_RANGE_PERM_UPGRADE; + /* * In the case of shared PMDs, the area to flush could be beyond * start/end. Set range.start/range.end to cover the maximum possible * range if PMD sharing is possible. */ mmu_notifier_range_init(&range, MMU_NOTIFY_PROTECTION_VMA, - 0, mm, start, end); + mn_flags, mm, start, end); adjust_range_if_pmd_sharing_possible(vma, &range.start, &range.end); BUG_ON(address >= end); diff --git a/mm/mprotect.c b/mm/mprotect.c index e9b636809c42..b48d2fd3b7b5 100644 --- a/mm/mprotect.c +++ b/mm/mprotect.c @@ -597,8 +597,13 @@ static inline long change_pud_range(struct mmu_gather *tlb, continue; if (!range.start) { + unsigned int mn_flags = 0; + + if (mm_cp_is_perm_upgrade(cp_flags)) + mn_flags |= MMU_NOTIFIER_RANGE_PERM_UPGRADE; + mmu_notifier_range_init(&range, - MMU_NOTIFY_PROTECTION_VMA, 0, + MMU_NOTIFY_PROTECTION_VMA, mn_flags, vma->vm_mm, addr, end); mmu_notifier_invalidate_range_start(&range); } -- 2.51.0