From: sashiko-bot@kernel.org
To: "Carlos López" <clopez@suse.de>,
kvm@vger.kernel.org, linux-mm@kvack.org, seanjc@google.com,
pbonzini@redhat.com
Cc: linux-s390@vger.kernel.org, imbrenda@linux.ibm.com,
"Andrew Morton" <akpm@linux-foundation.org>,
"David Hildenbrand" <david@kernel.org>,
"Lorenzo Stoakes" <ljs@kernel.org>,
"Liam R. Howlett" <liam@infradead.org>,
"Vlastimil Babka" <vbabka@kernel.org>,
"Mike Rapoport" <rppt@kernel.org>,
"Suren Baghdasaryan" <surenb@google.com>,
"Michal Hocko" <mhocko@suse.com>, "Jann Horn" <jannh@google.com>,
"Pedro Falcato" <pfalcato@suse.de>,
"Heiko Carstens" <hca@linux.ibm.com>,
"Vasily Gorbik" <gor@linux.ibm.com>,
"Alexander Gordeev" <agordeev@linux.ibm.com>,
"Christian Borntraeger" <borntraeger@linux.ibm.com>
Subject: Re: [RFC PATCH 1/4] mm/mprotect: add MM_CP_PERM_UPGRADE
Date: Fri, 30 Oct 2026 14:44:51 +0000 [thread overview]
Message-ID: <sashiko-outbox-191117@kernel.org> (raw)
In-Reply-To: <20261029143336.390231-4-clopez@suse.de>
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] mm/mprotect: MM_CP_PERM_UPGRADE set when change_protection() revokes write
- [Medium] mm/mprotect: vma_flags_is_perm_upgrade() ignores arch-specific prot flags
--
commit f3d5a380cac712469a433d985427420721eea496
Author: Carlos López <clopez@suse.de>
mm/mprotect: add MM_CP_PERM_UPGRADE
This commit introduces the MM_CP_PERM_UPGRADE flag for change_protection()
when mprotect() updates VMA permissions to a superset of the previous
permissions without changing the protection key, allowing MMU notifier
consumers to avoid invalidating PTEs.
> diff --git a/mm/mprotect.c b/mm/mprotect.c
> index 754333698c1ea..96e32e0159166 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);
> +}
[Severity: Medium]
Does vma_flags_is_perm_upgrade() also need to check architecture-specific
protection flags that mprotect() can modify?
In do_mprotect_pkey(), newflags is computed via calc_vm_prot_bits() (which
calls arch_calc_vm_prot_bits()) and masks off VM_FLAGS_CLEAR (which includes
VM_ARCH_CLEAR):
mm/mprotect.c:do_mprotect_pkey() {
...
mask_off_old_flags = VM_ACCESS_FLAGS | VM_FLAGS_CLEAR;
new_vma_pkey = arch_override_mprotect_pkey(vma, prot, pkey);
newflags = calc_vm_prot_bits(prot, new_vma_pkey);
newflags |= (vma->vm_flags & ~mask_off_old_flags);
...
}
Several architectures expose PROT_* flags in mprotect() that set or clear
VMA flags affecting hardware PTE protection or attribute bits in
vm_get_page_prot():
- On arm64, arch_calc_vm_prot_bits() maps PROT_BTI to VM_ARM64_BTI (cleared
via VM_ARCH_CLEAR when omitted) and PROT_MTE to VM_MTE. In
arch/arm64/mm/mmap.c:vm_get_page_prot(), VM_ARM64_BTI sets PTE_GP (which
restricts execution by faulting on indirect branches to non-BTI landing
pads) and VM_MTE sets PTE_ATTRINDX(MT_NORMAL_TAGGED) (which enables MTE
tag check faults).
- On sparc64, PROT_ADI sets VM_SPARC_ADI (cleared via VM_ARCH_CLEAR when
omitted), which sets _PAGE_MCD_4V in
arch/sparc/mm/init_64.c:vm_get_page_prot() to enable hardware ADI tag
checking traps.
- On powerpc64, PROT_SAO sets VM_SAO, which sets _PAGE_SAO in
arch/powerpc/mm/book3s64/pgtable.c:vm_get_page_prot() to alter hardware
memory ordering attributes.
Because vma_flags_is_perm_upgrade() only inspects VM_READ | VM_WRITE |
VM_EXEC and ARCH_VM_PKEY_FLAGS, if userspace calls mprotect() to enable
PROT_BTI, PROT_MTE, PROT_ADI, or PROT_SAO while keeping the read/write/exec
bits identical (or a superset) and the pkey unchanged, won't this set
MM_CP_PERM_UPGRADE and allow MMU notifier consumers to skip secondary MMU
invalidation when access restrictions were tightened or PTE attributes
changed?
> +
> 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);
[Severity: High]
Can setting MM_CP_PERM_UPGRADE when the VMA already had VM_WRITE (or on a
hugetlb VMA) violate the invariant that no access is revoked and the PFN
stays the same?
Follow-up commits ("mm/mmu_notifier: add MMU_NOTIFIER_RANGE_PERM_UPGRADE",
"KVM: plumb MMU_NOTIFIER_RANGE_PERM_UPGRADE into kvm_gfn_range", and
"KVM: x86/mmu: don't zap the secondary MMU on permission upgrade") wire
MM_CP_PERM_UPGRADE to MMU_NOTIFIER_RANGE_PERM_UPGRADE so that
kvm_unmap_gfn_range() skips zapping secondary MMU SPTEs.
However, when a VMA already has VM_WRITE and mprotect() adds permissions
(for example, upgrading PROT_READ | PROT_WRITE to
PROT_READ | PROT_WRITE | PROT_EXEC, or PROT_WRITE to
PROT_READ | PROT_WRITE), vma_set_page_prot() computes vma->vm_page_prot
(newprot) without write permission for all MAP_PRIVATE mappings (COW) and
for MAP_SHARED mappings where vma_wants_writenotify() is true.
When change_protection() applies this read-only newprot across the VMA,
there are several paths where write permission is stripped from existing
entries or entries are unmapped in the primary MMU while MM_CP_PERM_UPGRADE
is set:
1. In mprotect_fixup(), vma_wants_manual_pte_write_upgrade(vma) is called
after vma_flags_reset_once() but before vma_set_page_prot(vma). For
MAP_SHARED VMAs without vm_ops->page_mkwrite that use writenotify for
VM_UFFD_WP or soft-dirty tracking (such as shmem),
vma_wants_writenotify(vma, vma->vm_page_prot) compares the old read-only
vma->vm_page_prot against vma_pgprot_modify(vm_page_prot, vma->flags)
(which still has VMA_SHARED_BIT | VMA_WRITE_BIT) and returns false. As a
result, MM_CP_TRY_CHANGE_WRITABLE is not set, vma_set_page_prot()
computes a read-only vma->vm_page_prot, and change_present_ptes() /
change_huge_pmd() strip write permission from all writable PTEs and PMDs
in the VMA.
2. In mm/hugetlb.c:hugetlb_change_protection(), MM_CP_TRY_CHANGE_WRITABLE
is not handled, so huge_pte_modify(old_pte, newprot) unconditionally
strips write permission from already-writable COW huge PTEs in
MAP_PRIVATE hugetlb VMAs; in addition, huge_pmd_unshare() unmaps shared
PMD tables via pud_clear().
3. In mm/huge_memory.c:change_huge_pud(), MM_CP_TRY_CHANGE_WRITABLE is not
handled, so pud_modify(oldpud, newprot) strips write permission from
writable huge PUDs in writenotify DAX mappings.
4. In mm/mprotect.c:change_softleaf_pte(),
mm/huge_memory.c:change_non_present_huge_pmd(), and
mm/hugetlb.c:hugetlb_change_protection(), writable migration entries
(softleaf_is_migration_write()) and writable device-private entries
(softleaf_is_device_private_write()) are unconditionally downgraded to
read-only entries via make_readable_exclusive_migration_entry(),
make_readable_migration_entry(), or make_readable_device_private_entry().
5. In change_present_ptes() and change_huge_pmd(), pte_modify() /
pmd_modify() strip write permission and do not restore it under
MM_CP_TRY_CHANGE_WRITABLE when !folio in a private PFN/mixed mapping or
when a shared writenotify PTE was cleaned (!pte_dirty(ptent)).
Wouldn't skipping secondary MMU invalidation in these cases leave secondary
MMUs (such as KVM SPTEs) with stale writable mappings to pages that
change_protection() has write-protected or unmapped in the primary MMU?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261029143336.390231-3-clopez@suse.de?part=1
next prev parent reply other threads:[~2026-10-30 14:44 UTC|newest]
Thread overview: 11+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-29 14:33 [RFC PATCH 0/4] KVM: x86/mmu: don't zap secondary MMU on permission upgrade Carlos López
2026-10-29 14:33 ` [RFC PATCH 1/4] mm/mprotect: add MM_CP_PERM_UPGRADE Carlos López
2026-10-30 14:44 ` sashiko-bot [this message]
2026-10-29 14:33 ` [RFC PATCH 2/4] mm/mmu_notifier: add MMU_NOTIFIER_RANGE_PERM_UPGRADE Carlos López
2026-10-30 14:44 ` sashiko-bot
2026-10-29 14:33 ` [RFC PATCH 3/4] KVM: plumb MMU_NOTIFIER_RANGE_PERM_UPGRADE into kvm_gfn_range Carlos López
2026-10-29 14:33 ` [RFC PATCH 4/4] KVM: x86/mmu: don't zap the secondary MMU on permission upgrade Carlos López
2026-10-30 14:44 ` sashiko-bot
2026-10-29 16:52 ` [RFC PATCH 0/4] KVM: x86/mmu: don't zap " Claudio Imbrenda
2026-10-30 11:11 ` Carlos López
2026-10-30 12:39 ` Claudio Imbrenda
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=sashiko-outbox-191117@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=agordeev@linux.ibm.com \
--cc=akpm@linux-foundation.org \
--cc=borntraeger@linux.ibm.com \
--cc=clopez@suse.de \
--cc=david@kernel.org \
--cc=gor@linux.ibm.com \
--cc=hca@linux.ibm.com \
--cc=imbrenda@linux.ibm.com \
--cc=jannh@google.com \
--cc=kvm@vger.kernel.org \
--cc=liam@infradead.org \
--cc=linux-mm@kvack.org \
--cc=linux-s390@vger.kernel.org \
--cc=ljs@kernel.org \
--cc=mhocko@suse.com \
--cc=pbonzini@redhat.com \
--cc=pfalcato@suse.de \
--cc=rppt@kernel.org \
--cc=sashiko-reviews@lists.linux.dev \
--cc=seanjc@google.com \
--cc=surenb@google.com \
--cc=vbabka@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox