* [PATCH v2 0/6] mm, drm: fix interaction of .pfn_mkwrite() with fixup_user_fault()
@ 2026-08-04 12:05 Paolo Bonzini
2026-08-04 12:05 ` [PATCH v2 1/6] mm: export vmf_insert_pfn_prot_mkwrite(), change variants to inline Paolo Bonzini
` (5 more replies)
0 siblings, 6 replies; 24+ messages in thread
From: Paolo Bonzini @ 2026-08-04 12:05 UTC (permalink / raw)
To: linux-kernel, kvm
Cc: Alex Williamson, bcm-kernel-feedback-list, Boris Brezillon,
Christian Koenig, David Hildenbrand, dri-devel, Fei Li, Huang Rui,
linux-mm, linux-s390, Michal Hocko, Peter Xu, Sergio Lopez,
Sean Christopherson, Thomas Zimmermann
This "v2" combines three series that I have previously posted separately to gather
reviews and tests:
- kvm: apply VM_READ/VM_WRITE checks to all VMA types
https://lore.kernel.org/kvm/20260731175834.1121005-1-pbonzini@redhat.com/
- mm: pull writability check to follow_pfnmap_start()
https://lore.kernel.org/kvm/20260731160514.1101989-1-pbonzini@redhat.com/T/#u
- mm, drm: ensure .fault() does not have to be followed by .pfn_mkwrite() for write faults
https://lore.kernel.org/kvm/20260731164341.1109827-1-pbonzini@redhat.com/
All three, together, make it possible to write drivers that use .fault()
and .pfn_mkwrite() callbacks for VM_IO|VM_PFNMAP regions, and that also
interact correctly with users of fixup_user_fault().
The problem is that if you define .pfn_mkwrite(), vma_set_page_prot()
clears the writable PTE bit in vma->vm_page_prot, at which point
the .fault() callback has no way to create a writable PTE. Users of
fixup_user_fault() will then see a read-only PTE and have no clue that
the page needs a *second* fault to reach its final status.
fixup_user_fault() itself does not have a good way to notice this, because
vma->vm_page_prot is an opaque pgprot_t, so the fix needs to be somewhere
else. Other preexisting functions, namely vmf_insert_page_mkwrite()
as well as vmf_insert_pfn_pmd(), suggest that this has to be the driver.
In fact, of the five vm_ops that use .pfn_mkwrite() together with .fault(),
three are in file systems and are not buggy: all of them ultimately end
up in dax_fault_iter(), which uses vmf_insert_page_mkwrite() to correctly
insert the PTE.
The two problematic implementations instead are both in drm code. One, in
drm_gem_shmem_helper, was reported as a KVM regression; the other, in
vmwgfx, was found by inspection of .pfn_mkwrite() implementors.
Both of these use pfn-mapped regions, but there is nothing like a
vmf_insert_pfn_mkwrite() function that they could use; the first part of
this series thus adjusts mm.h to provide two new functions for this
usecase---vmf_insert_pfn_prot_mkwrite() and vmf_insert_pfn_mkwrite()---and
then teaches drm's two users of .pfn_mkwrite() to call them.
This however leaves another case buggy where fixup_user_fault() is preceded
by follow_pfnmap_start(). Most callers of follow_pfnmap_start(), seeing
it return 0 for a PFN that is mapped read-only, would not attempt to
call fixup_user_fault() on it, and thus the PTE would not be upgraded
to writable.
This is arguably a bug in... almost all the callers of follow_pfnmap_start(),
but fixing it is much better achieved with a small improvement to the API;
if follow_pfnmap_start() is told by the caller that it needs the memory
for a write, most callers are simplified because they were doing such a check
anyway and now just see -EFAULT. They then proceed to call fixup_user_fault()
and everyone is happy. This is done in patch 5.
To sum up:
- patches 1-3 introduce the new MM API, and use it in the DRM .fault()
callbacks to install writable PTEs in response to write faults
- patch 4 is a preparatory fix in KVM, eliminating inconsistencies in the
handling of !VM_READ and !VM_WRITE VMAs; these would return different
error codes for a !VM_WRITE VMA depending on whether the PTE happens
to be mapped (but with wrong permissions). This needs to be here
because the next patch would introduce even more inconsistencies.
- patch 5 moves the check for writable PTEs from follow_pfnmap_start()'s
callers to the function itself
- finally, patch 6 is a KVM addendum that will have to wait until
the next merge window; it removes yet another inconsistency in
KVM's handling of !VM_WRITE but is technically userspace-visible,
and therefore it shouldn't be included in stable kernel releases
unlike the rest.
Thanks,
Paolo
Paolo Bonzini (6):
mm: export vmf_insert_pfn_prot_mkwrite(), change variants to inline
drm/shmem_helper: use vmf_insert_pfn_mkwrite()
drm/ttm, drm/vmwgfx: directly create writable PTEs when mkwrite is in
use
kvm: apply VM_READ/VM_WRITE checks to all VMA types
mm: pull writability check to follow_pfnmap_start()
kvm: return -EFAULT for writes to !VM_WRITE IO mappings
arch/s390/pci/pci_mmio.c | 2 +
drivers/gpu/drm/drm_gem_shmem_helper.c | 38 +++---
drivers/gpu/drm/ttm/ttm_bo_vm.c | 7 +-
drivers/gpu/drm/vmwgfx/vmwgfx_page_dirty.c | 42 +++---
drivers/vfio/vfio_iommu_type1.c | 17 ++-
drivers/virt/acrn/mm.c | 10 +-
include/linux/mm.h | 84 +++++++++++-
mm/huge_memory.c | 2 +-
mm/memory.c | 146 +++++++++++----------
virt/kvm/kvm_main.c | 44 +++----
10 files changed, 232 insertions(+), 160 deletions(-)
--
2.55.0
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH v2 1/6] mm: export vmf_insert_pfn_prot_mkwrite(), change variants to inline
2026-08-04 12:05 [PATCH v2 0/6] mm, drm: fix interaction of .pfn_mkwrite() with fixup_user_fault() Paolo Bonzini
@ 2026-08-04 12:05 ` Paolo Bonzini
2026-08-04 12:20 ` sashiko-bot
2026-08-04 12:05 ` [PATCH v2 2/6] drm/shmem_helper: use vmf_insert_pfn_mkwrite() Paolo Bonzini
` (4 subsequent siblings)
5 siblings, 1 reply; 24+ messages in thread
From: Paolo Bonzini @ 2026-08-04 12:05 UTC (permalink / raw)
To: linux-kernel, kvm
Cc: Alex Williamson, bcm-kernel-feedback-list, Boris Brezillon,
Christian Koenig, David Hildenbrand, dri-devel, Fei Li, Huang Rui,
linux-mm, linux-s390, Michal Hocko, Peter Xu, Sergio Lopez,
Sean Christopherson, Thomas Zimmermann, stable
Right now, users of .pfn_mkwrite() have no way to create a PTE
that has gone through maybe_mkwrite(). Because vma_set_page_prot()
will have cleared the writable PTE bit, users of fixup_user_fault()
will see a read-only PTE and have no clue that the page needs
a *second* fault to reach its final status.
Handling this in fixup_user_fault() is problematic: the information
about the presence of *_mkwrite is only recorded in vma->vm_page_prot,
which is an opaque pgprot_t, therefore only follow_pfnmap_start()
knows how to retrieve it.
There are actually some preexisting functions that suggest how this
is supposed to be handled, namely vmf_insert_page_mkwrite() and
vmf_insert_pfn_pmd(). Fixing the drivers requires similar variants
of vm_insert_pfn(), namely vmf_insert_pfn_mkwrite() for the common
case where vma->vm_page_prot is okay, and vmf_insert_pfn_prot_mkwrite()
when really all parameters are needed. This makes it possible
to fix drivers that use .pfn_mkwrite together with
vmf_insert_pfn() and vmf_insert_pfn_prot().
Since vmf_insert_pfn_prot_mkwrite() is the most general variant
and all the others are just special cases, turn them into inline
functions in the header.
Fixes: 28e3918179aa ("drm/gem-shmem: Track folio accessed/dirty status in mmap")
Cc: stable@vger.kernel.org
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
include/linux/mm.h | 81 +++++++++++++++++++++++++++++++++++++++++---
mm/huge_memory.c | 2 +-
mm/memory.c | 84 ++++++++++++++++++++--------------------------
3 files changed, 114 insertions(+), 53 deletions(-)
diff --git a/include/linux/mm.h b/include/linux/mm.h
index 485df9c2dbdd..01184a4bdd6f 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -4544,16 +4544,89 @@ int vm_map_pages_zero(struct vm_area_struct *vma, struct page **pages,
unsigned long num);
vm_fault_t vmf_insert_page_mkwrite(struct vm_fault *vmf, struct page *page,
bool write);
-vm_fault_t vmf_insert_pfn(struct vm_area_struct *vma, unsigned long addr,
- unsigned long pfn);
-vm_fault_t vmf_insert_pfn_prot(struct vm_area_struct *vma, unsigned long addr,
- unsigned long pfn, pgprot_t pgprot);
+vm_fault_t vmf_insert_pfn_prot_mkwrite(struct vm_area_struct *vma, unsigned long addr,
+ unsigned long pfn, pgprot_t pgprot, bool mkwrite);
vm_fault_t vmf_insert_mixed(struct vm_area_struct *vma, unsigned long addr,
unsigned long pfn);
vm_fault_t vmf_insert_mixed_mkwrite(struct vm_area_struct *vma,
unsigned long addr, unsigned long pfn);
int vm_iomap_memory(struct vm_area_struct *vma, phys_addr_t start, unsigned long len);
+
+/**
+ * vmf_insert_pfn_prot - insert single pfn into user vma with specified pgprot
+ * @vma: user vma to map to
+ * @addr: target user address of this page
+ * @pfn: source kernel pfn
+ * @pgprot: pgprot flags for the inserted page
+ *
+ * This is exactly like vmf_insert_pfn(), except that it allows drivers
+ * to override pgprot on a per-page basis. For more information,
+ * see vmf_insert_pfn_prot_mkwrite().
+ *
+ * This only makes sense for IO mappings, and it makes no sense for
+ * COW mappings. In general, using multiple vmas is preferable;
+ * vmf_insert_pfn_prot should only be used if using multiple VMAs is
+ * impractical.
+ *
+ * Context: Process context. May allocate using %GFP_KERNEL.
+ * Return: vm_fault_t value.
+ */
+static inline vm_fault_t vmf_insert_pfn_prot(struct vm_area_struct *vma,
+ unsigned long addr, unsigned long pfn, pgprot_t pgprot)
+{
+ return vmf_insert_pfn_prot_mkwrite(vma, addr, pfn, pgprot, false);
+}
+
+/**
+ * vmf_insert_pfn_mkwrite - insert single pfn into user vma, possibly writable
+ * @vma: user vma to map to
+ * @addr: target user address of this page
+ * @pfn: source kernel pfn
+ * @write: whether the PTE should be installed writable
+ *
+ * Like vmf_insert_pfn(), except that @write allows installing a writable
+ * PTE even when @vma is under write notification. For more information,
+ * see vmf_insert_pfn_prot_mkwrite().
+ *
+ * Note that neither .pfn_mkwrite() nor .page_mkwrite() is invoked, so the
+ * caller must itself do whatever they would have done if @write is true.
+ *
+ * Context: Process context. May allocate using %GFP_KERNEL.
+ * Return: vm_fault_t value.
+ */
+static inline vm_fault_t vmf_insert_pfn_mkwrite(struct vm_area_struct *vma,
+ unsigned long addr, unsigned long pfn, bool write)
+{
+ return vmf_insert_pfn_prot_mkwrite(vma, addr, pfn, vma->vm_page_prot, write);
+}
+
+/**
+ * vmf_insert_pfn - insert single pfn into user vma
+ * @vma: user vma to map to
+ * @addr: target user address of this page
+ * @pfn: source kernel pfn
+ *
+ * Similar to vm_insert_page, this allows drivers to insert individual pages
+ * they've allocated into a user vma. Same comments apply.
+ *
+ * This function should only be called from a vm_ops->fault handler, and
+ * in that case the handler should return the result of this function.
+ *
+ * vma cannot be a COW mapping.
+ *
+ * As this is called only for pages that do not currently exist, we
+ * do not need to flush old virtual caches or the TLB.
+ *
+ * Context: Process context. May allocate using %GFP_KERNEL.
+ * Return: vm_fault_t value.
+ */
+static inline vm_fault_t vmf_insert_pfn(struct vm_area_struct *vma,
+ unsigned long addr, unsigned long pfn)
+{
+ return vmf_insert_pfn_mkwrite(vma, addr, pfn, false);
+}
+
static inline vm_fault_t vmf_insert_page(struct vm_area_struct *vma,
unsigned long addr, struct page *page)
{
diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index b5d1e9d4463d..2f4dcaa819b7 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -1615,7 +1615,7 @@ static vm_fault_t insert_pmd(struct vm_area_struct *vma, unsigned long addr,
* @pfn: pfn to insert
* @write: whether it's a write fault
*
- * Insert a pmd size pfn. See vmf_insert_pfn() for additional info.
+ * Insert a pmd size pfn. See vmf_insert_pfn_mkwrite() for additional info.
*
* Return: vm_fault_t value.
*/
diff --git a/mm/memory.c b/mm/memory.c
index ff338c2abe92..b5555217b121 100644
--- a/mm/memory.c
+++ b/mm/memory.c
@@ -2719,40 +2719,55 @@ static vm_fault_t insert_pfn(struct vm_area_struct *vma, unsigned long addr,
}
/**
- * vmf_insert_pfn_prot - insert single pfn into user vma with specified pgprot
+ * vmf_insert_pfn_prot_mkwrite - insert single pfn into user vma with specified pgprot
* @vma: user vma to map to
* @addr: target user address of this page
* @pfn: source kernel pfn
* @pgprot: pgprot flags for the inserted page
+ * @mkwrite: whether to make the page writable.
*
- * This is exactly like vmf_insert_pfn(), except that it allows drivers
- * to override pgprot on a per-page basis.
+ * This is the function underlying all the others in the vmf_insert_pfn()
+ * family. It is the most flexible, as it allows drivers to override pgprot
+ * on a per-page basis, as well as to insert the pfn as if it already had
+ * a write fault. vmf_insert_pfn() is usually sufficient, however.
+ *
+ * These functions should only be called from a vm_ops->fault handler, and
+ * in that case the handler should return the result of these functions.
*
* This only makes sense for IO mappings, and it makes no sense for
- * COW mappings. In general, using multiple vmas is preferable;
- * vmf_insert_pfn_prot should only be used if using multiple VMAs is
- * impractical.
+ * COW mappings.
*
- * pgprot typically only differs from @vma->vm_page_prot when drivers set
- * caching- and encryption bits different than those of @vma->vm_page_prot,
- * because the caching- or encryption mode may not be known at mmap() time.
+ * For vmf_insert_pfn_prot_mkwrite() and vmf_insert_pfn_mkwrite(), the
+ * @mkwrite argument allows installing a writable PTE even when @vma is
+ * under write notification, i.e. when it has a .pfn_mkwrite() callback.
+ * In this case, vma_set_page_prot() has cleared the write bit from
+ * @vma->vm_page_prot. This lets the fault() callback install a writable
+ * PTE in response to write faults; note that .pfn_mkwrite() is not called,
+ * and therefore the caller has to do by itself whatever the callback would
+ * have done.
*
- * This is ok as long as @vma->vm_page_prot is not used by the core vm
+ * For vmf_insert_pfn_prot_mkwrite() and vmf_insert_pfn_prot(),
+ * pgprot can differ from @vma->vm_page_prot. This typically happens only
+ * for caching and encryption bits, which may not be known at mmap() time;
+ * it is ok as long as @vma->vm_page_prot is not used by the core vm
* to set caching and encryption bits for those vmas (except for COW pages).
- * This is ensured by core vm only modifying these page table entries using
- * functions that don't touch caching- or encryption bits, using pte_modify()
- * if needed. (See for example mprotect()).
+ * This is ensured in two ways:
*
- * Also when new page-table entries are created, this is only done using the
- * fault() callback, and never using the value of vma->vm_page_prot,
- * except for page-table entries that point to anonymous pages as the result
- * of COW.
+ * - core vm only modifies these page table entries using functions that don't
+ * touch caching- or encryption bits, using pte_modify() if needed. (See
+ * for example mprotect()).
+ *
+ * - when new page-table entries are created, this is only done using the
+ * fault() callback, and never using the value of vma->vm_page_prot,
+ * except for page-table entries that point to anonymous pages as the result
+ * of COW.
*
* Context: Process context. May allocate using %GFP_KERNEL.
* Return: vm_fault_t value.
*/
-vm_fault_t vmf_insert_pfn_prot(struct vm_area_struct *vma, unsigned long addr,
- unsigned long pfn, pgprot_t pgprot)
+vm_fault_t vmf_insert_pfn_prot_mkwrite(struct vm_area_struct *vma,
+ unsigned long addr, unsigned long pfn, pgprot_t pgprot,
+ bool mkwrite)
{
/*
* Technically, architectures with pte_special can avoid all these
@@ -2774,36 +2789,9 @@ vm_fault_t vmf_insert_pfn_prot(struct vm_area_struct *vma, unsigned long addr,
pfnmap_setup_cachemode_pfn(pfn, &pgprot);
- return insert_pfn(vma, addr, pfn, pgprot, false);
+ return insert_pfn(vma, addr, pfn, pgprot, mkwrite);
}
-EXPORT_SYMBOL(vmf_insert_pfn_prot);
-
-/**
- * vmf_insert_pfn - insert single pfn into user vma
- * @vma: user vma to map to
- * @addr: target user address of this page
- * @pfn: source kernel pfn
- *
- * Similar to vm_insert_page, this allows drivers to insert individual pages
- * they've allocated into a user vma. Same comments apply.
- *
- * This function should only be called from a vm_ops->fault handler, and
- * in that case the handler should return the result of this function.
- *
- * vma cannot be a COW mapping.
- *
- * As this is called only for pages that do not currently exist, we
- * do not need to flush old virtual caches or the TLB.
- *
- * Context: Process context. May allocate using %GFP_KERNEL.
- * Return: vm_fault_t value.
- */
-vm_fault_t vmf_insert_pfn(struct vm_area_struct *vma, unsigned long addr,
- unsigned long pfn)
-{
- return vmf_insert_pfn_prot(vma, addr, pfn, vma->vm_page_prot);
-}
-EXPORT_SYMBOL(vmf_insert_pfn);
+EXPORT_SYMBOL(vmf_insert_pfn_prot_mkwrite);
static bool vm_mixed_ok(struct vm_area_struct *vma, unsigned long pfn,
bool mkwrite)
--
2.55.0
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH v2 2/6] drm/shmem_helper: use vmf_insert_pfn_mkwrite()
2026-08-04 12:05 [PATCH v2 0/6] mm, drm: fix interaction of .pfn_mkwrite() with fixup_user_fault() Paolo Bonzini
2026-08-04 12:05 ` [PATCH v2 1/6] mm: export vmf_insert_pfn_prot_mkwrite(), change variants to inline Paolo Bonzini
@ 2026-08-04 12:05 ` Paolo Bonzini
2026-08-04 12:30 ` sashiko-bot
2026-08-04 14:15 ` Boris Brezillon
2026-08-04 12:05 ` [PATCH v2 3/6] drm/ttm, drm/vmwgfx: directly create writable PTEs when mkwrite is in use Paolo Bonzini
` (3 subsequent siblings)
5 siblings, 2 replies; 24+ messages in thread
From: Paolo Bonzini @ 2026-08-04 12:05 UTC (permalink / raw)
To: linux-kernel, kvm
Cc: Alex Williamson, bcm-kernel-feedback-list, Boris Brezillon,
Christian Koenig, David Hildenbrand, dri-devel, Fei Li, Huang Rui,
linux-mm, linux-s390, Michal Hocko, Peter Xu, Sergio Lopez,
Sean Christopherson, Thomas Zimmermann, stable
This ensures that KVM or VFIO correctly see a writable PTE when
they request one. Otherwise, a guest write to an unpopulated
PTE from a mapping backed by a DRM GEM BO triggers a VM exit
with EFAULT.
The code actually is simpler, because the same logic already
applied to the hugepage mapping case using vmf_insert_pfn_pmd().
Reported-by: Sergio Lopez <slp@redhat.com>
Link: https://lore.kernel.org/kvm/20260729072044.25796-1-slp@redhat.com/
Tested-by: Sergio Lopez <slp@redhat.com>
Reviewed-by: Boris Brezillon <boris.brezillon@collabora.com>
Fixes: 28e3918179aa ("drm/gem-shmem: Track folio accessed/dirty status in mmap")
Cc: stable@vger.kernel.org
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
drivers/gpu/drm/drm_gem_shmem_helper.c | 38 ++++++++++++++------------
1 file changed, 20 insertions(+), 18 deletions(-)
diff --git a/drivers/gpu/drm/drm_gem_shmem_helper.c b/drivers/gpu/drm/drm_gem_shmem_helper.c
index c989459eb215..c81be3e97317 100644
--- a/drivers/gpu/drm/drm_gem_shmem_helper.c
+++ b/drivers/gpu/drm/drm_gem_shmem_helper.c
@@ -589,11 +589,25 @@ static void drm_gem_shmem_record_mkwrite(struct vm_fault *vmf)
folio_mark_dirty(page_folio(shmem->pages[page_offset]));
}
+/*
+ * Because the vm_ops have a .pfn_mkwrite() callback, vma_set_page_prot()
+ * has cleared the write bit from vma->vm_page_prot. vmf_insert_pfn()
+ * would install a read-only entry even for a write fault, relying on a
+ * second fault to reach .pfn_mkwrite() and upgrade it, but that second
+ * fault never happens for fixup_user_fault() callers that directly
+ * walk the page tables with follow_pfnmap_start(). To ensure that
+ * they don't see the read-only entry, pass FAULT_FLAG_WRITE info down
+ * to install a writable entry right away. Because .pfn_mkwrite() is
+ * not invoked, record the write afterwards.
+ */
static vm_fault_t try_insert_pfn(struct vm_fault *vmf, unsigned int order,
unsigned long pfn)
{
+ bool write = vmf->flags & FAULT_FLAG_WRITE;
+ vm_fault_t ret = VM_FAULT_FALLBACK;
+
if (!order) {
- return vmf_insert_pfn(vmf->vma, vmf->address, pfn);
+ ret = vmf_insert_pfn_mkwrite(vmf->vma, vmf->address, pfn, write);
#ifdef CONFIG_ARCH_SUPPORTS_PMD_PFNMAP
} else if (order == PMD_ORDER) {
unsigned long paddr = pfn << PAGE_SHIFT;
@@ -601,27 +615,15 @@ static vm_fault_t try_insert_pfn(struct vm_fault *vmf, unsigned int order,
if (aligned &&
folio_test_pmd_mappable(page_folio(pfn_to_page(pfn)))) {
- vm_fault_t ret;
-
pfn &= PMD_MASK >> PAGE_SHIFT;
-
- /* Unlike PTEs which are automatically upgraded to
- * writeable entries, the PMD upgrades go through
- * .huge_fault(). Make sure we pass the "write" info
- * along in that case.
- * This also means we have to record the write fault
- * here, instead of in .pfn_mkwrite().
- */
- ret = vmf_insert_pfn_pmd(vmf, pfn,
- vmf->flags & FAULT_FLAG_WRITE);
- if (ret == VM_FAULT_NOPAGE && (vmf->flags & FAULT_FLAG_WRITE))
- drm_gem_shmem_record_mkwrite(vmf);
-
- return ret;
+ ret = vmf_insert_pfn_pmd(vmf, pfn, write);
}
#endif
}
- return VM_FAULT_FALLBACK;
+
+ if (ret == VM_FAULT_NOPAGE && write)
+ drm_gem_shmem_record_mkwrite(vmf);
+ return ret;
}
static vm_fault_t drm_gem_shmem_any_fault(struct vm_fault *vmf, unsigned int order)
--
2.55.0
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH v2 3/6] drm/ttm, drm/vmwgfx: directly create writable PTEs when mkwrite is in use
2026-08-04 12:05 [PATCH v2 0/6] mm, drm: fix interaction of .pfn_mkwrite() with fixup_user_fault() Paolo Bonzini
2026-08-04 12:05 ` [PATCH v2 1/6] mm: export vmf_insert_pfn_prot_mkwrite(), change variants to inline Paolo Bonzini
2026-08-04 12:05 ` [PATCH v2 2/6] drm/shmem_helper: use vmf_insert_pfn_mkwrite() Paolo Bonzini
@ 2026-08-04 12:05 ` Paolo Bonzini
2026-08-04 12:21 ` sashiko-bot
2026-08-06 23:32 ` Peter Xu
2026-08-04 12:05 ` [PATCH v2 4/6] kvm: apply VM_READ/VM_WRITE checks to all VMA types Paolo Bonzini
` (2 subsequent siblings)
5 siblings, 2 replies; 24+ messages in thread
From: Paolo Bonzini @ 2026-08-04 12:05 UTC (permalink / raw)
To: linux-kernel, kvm
Cc: Alex Williamson, bcm-kernel-feedback-list, Boris Brezillon,
Christian Koenig, David Hildenbrand, dri-devel, Fei Li, Huang Rui,
linux-mm, linux-s390, Michal Hocko, Peter Xu, Sergio Lopez,
Sean Christopherson, Thomas Zimmermann, stable
This ensures that fixup_user_fault() users see a writable PTE when
they request one. The flip side is that vmw_bo_vm_fault() now has
to record by hand the write fault, because .pfn_mkwrite() is
not invoked.
Prefaulting works as before because only the first entry comes
out writable, while the following ones still end up executing
the .pfn_mkwrite() callback.
Cc: stable@vger.kernel.org
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
drivers/gpu/drm/ttm/ttm_bo_vm.c | 7 ++--
drivers/gpu/drm/vmwgfx/vmwgfx_page_dirty.c | 42 ++++++++++++----------
2 files changed, 29 insertions(+), 20 deletions(-)
diff --git a/drivers/gpu/drm/ttm/ttm_bo_vm.c b/drivers/gpu/drm/ttm/ttm_bo_vm.c
index a80510489c45..3ebde936ce60 100644
--- a/drivers/gpu/drm/ttm/ttm_bo_vm.c
+++ b/drivers/gpu/drm/ttm/ttm_bo_vm.c
@@ -191,6 +191,7 @@ vm_fault_t ttm_bo_vm_fault_reserved(struct vm_fault *vmf,
unsigned long pfn;
struct ttm_tt *ttm = NULL;
struct page *page;
+ bool mkwrite;
int err;
pgoff_t i;
vm_fault_t ret = VM_FAULT_NOPAGE;
@@ -242,6 +243,7 @@ vm_fault_t ttm_bo_vm_fault_reserved(struct vm_fault *vmf,
* Speculatively prefault a number of pages. Only error on
* first page.
*/
+ mkwrite = !!(vmf->flags & FAULT_FLAG_WRITE);
for (i = 0; i < num_prefault; ++i) {
if (bo->resource->bus.is_iomem) {
pfn = ttm_bo_io_mem_pfn(bo, page_offset);
@@ -263,9 +265,10 @@ vm_fault_t ttm_bo_vm_fault_reserved(struct vm_fault *vmf,
* at arbitrary times while the data is mmap'ed.
* See vmf_insert_pfn_prot() for a discussion.
*/
- ret = vmf_insert_pfn_prot(vma, address, pfn, prot);
+ ret = vmf_insert_pfn_prot_mkwrite(vma, address, pfn, prot, mkwrite);
- /* Never error on prefaulted PTEs */
+ /* Never error on prefaulted PTEs and never map them writable */
+ mkwrite = false;
if (unlikely((ret & VM_FAULT_ERROR))) {
if (i == 0)
return VM_FAULT_NOPAGE;
diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_page_dirty.c b/drivers/gpu/drm/vmwgfx/vmwgfx_page_dirty.c
index 45561bc1c9ef..3099558c0762 100644
--- a/drivers/gpu/drm/vmwgfx/vmwgfx_page_dirty.c
+++ b/drivers/gpu/drm/vmwgfx/vmwgfx_page_dirty.c
@@ -398,15 +398,33 @@ void vmw_bo_dirty_clear_res(struct vmw_resource *res)
dirty->end = res_start;
}
+static vm_fault_t vmw_bo_dirty_mkwrite(struct vm_fault *vmf, struct ttm_buffer_object *bo)
+{
+ unsigned long page_offset;
+ struct vmw_bo *vbo = to_vmw_bo(&bo->base);
+
+ page_offset = vmf->pgoff - drm_vma_node_start(&bo->base.vma_node);
+ if (unlikely(page_offset >= PFN_UP(bo->resource->size)))
+ return VM_FAULT_SIGBUS;
+
+ if (vbo->dirty && vbo->dirty->method == VMW_BO_DIRTY_MKWRITE &&
+ !test_bit(page_offset, &vbo->dirty->bitmap[0])) {
+ struct vmw_bo_dirty *dirty = vbo->dirty;
+
+ __set_bit(page_offset, &dirty->bitmap[0]);
+ dirty->start = min(dirty->start, page_offset);
+ dirty->end = max(dirty->end, page_offset + 1);
+ }
+ return 0;
+}
+
vm_fault_t vmw_bo_vm_mkwrite(struct vm_fault *vmf)
{
struct vm_area_struct *vma = vmf->vma;
struct ttm_buffer_object *bo = (struct ttm_buffer_object *)
vma->vm_private_data;
vm_fault_t ret;
- unsigned long page_offset;
unsigned int save_flags;
- struct vmw_bo *vbo = to_vmw_bo(&bo->base);
/*
* mkwrite() doesn't handle the VM_FAULT_RETRY return value correctly.
@@ -419,22 +437,7 @@ vm_fault_t vmw_bo_vm_mkwrite(struct vm_fault *vmf)
if (ret)
return ret;
- page_offset = vmf->pgoff - drm_vma_node_start(&bo->base.vma_node);
- if (unlikely(page_offset >= PFN_UP(bo->resource->size))) {
- ret = VM_FAULT_SIGBUS;
- goto out_unlock;
- }
-
- if (vbo->dirty && vbo->dirty->method == VMW_BO_DIRTY_MKWRITE &&
- !test_bit(page_offset, &vbo->dirty->bitmap[0])) {
- struct vmw_bo_dirty *dirty = vbo->dirty;
-
- __set_bit(page_offset, &dirty->bitmap[0]);
- dirty->start = min(dirty->start, page_offset);
- dirty->end = max(dirty->end, page_offset + 1);
- }
-
-out_unlock:
+ ret = vmw_bo_dirty_mkwrite(vmf, bo);
dma_resv_unlock(bo->base.resv);
return ret;
}
@@ -484,6 +487,9 @@ vm_fault_t vmw_bo_vm_fault(struct vm_fault *vmf)
prot = vm_get_page_prot(vma->vm_flags);
ret = ttm_bo_vm_fault_reserved(vmf, prot, num_prefault);
+ if (ret == VM_FAULT_NOPAGE && (vmf->flags & FAULT_FLAG_WRITE))
+ WARN_ON_ONCE(vmw_bo_dirty_mkwrite(vmf, bo));
+
if (ret == VM_FAULT_RETRY && !(vmf->flags & FAULT_FLAG_RETRY_NOWAIT))
return ret;
--
2.55.0
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH v2 4/6] kvm: apply VM_READ/VM_WRITE checks to all VMA types
2026-08-04 12:05 [PATCH v2 0/6] mm, drm: fix interaction of .pfn_mkwrite() with fixup_user_fault() Paolo Bonzini
` (2 preceding siblings ...)
2026-08-04 12:05 ` [PATCH v2 3/6] drm/ttm, drm/vmwgfx: directly create writable PTEs when mkwrite is in use Paolo Bonzini
@ 2026-08-04 12:05 ` Paolo Bonzini
2026-08-04 12:23 ` sashiko-bot
2026-08-04 21:15 ` Sean Christopherson
2026-08-04 12:05 ` [PATCH v2 5/6] mm: pull writability check to follow_pfnmap_start() Paolo Bonzini
2026-08-04 12:05 ` [PATCH v2 6/6] kvm: return -EFAULT for writes to !VM_WRITE IO mappings Paolo Bonzini
5 siblings, 2 replies; 24+ messages in thread
From: Paolo Bonzini @ 2026-08-04 12:05 UTC (permalink / raw)
To: linux-kernel, kvm
Cc: Alex Williamson, bcm-kernel-feedback-list, Boris Brezillon,
Christian Koenig, David Hildenbrand, dri-devel, Fei Li, Huang Rui,
linux-mm, linux-s390, Michal Hocko, Peter Xu, Sergio Lopez,
Sean Christopherson, Thomas Zimmermann, stable
The VM_READ and VM_WRITE flags are checked only at the very end of
hva_to_pfn(). For both the hva_to_pfn_remapped() case and for regular
mappings, this adds unnecessary cases and inconsistent error behavior.
For hva_to_pfn_remapped(), the code is relying on fixup_user_fault() to
detect this situation. This is fragile because hva_to_pfn_remapped()
returns different error codes for a !VM_WRITE VMA depending on whether
the PTE happens to be mapped:
* if the PTE is present, follow_pfnmap_start() sets args.writable to
false and KVM_PFN_ERR_RO_FAULT is returned;
* if no PTE is present, fixup_user_fault(FAULT_FLAG_WRITE) returns
-EFAULT after checking vma_permits_fault(), and hva_to_pfn() ends
up returning KVM_PFN_ERR_FAULT.
With this patch KVM_PFN_ERR_RO_FAULT is returned uniformly. Likewise,
a PROT_NONE pfnmap VMA would be mapped into the guest if the PTE was
pte_present()[1] when the guest attempted to read it; with the patch
instead KVM uniformly returns KVM_PFN_ERR_FAULT. Doing the check early
avoids these special cases and also sidesteps the issue pointed out at
https://sashiko.dev/#/patchset/20260731160514.1101989-1-pbonzini%40redhat.com.
For regular mappings a PROT_READ VMA, if placed in a writable memslot,
would return KVM_PFN_ERR_FAULT instead of KVM_PFN_ERR_RO_FAULT when
the guest writes to it. This would cause a -EFAULT exit to userspace,
instead of triggering emulation as the VM_IO|VM_PFNMAP arm would do;
however it should be considered part of the KVM API because mmu_stress_test
relies on it.
Still, even with this snag about the returned pfn error code, pull the
vm_flags checks in front so that they are done for all VMAs and the
above inconsistency goes away for the VM_IO|VM_PFNMAP case.
[1] on x86, for example, such a page would have _PAGE_PRESENT clear
but _PAGE_PROTNONE set
Fixes: 28e3918179aa ("drm/gem-shmem: Track folio accessed/dirty status in mmap")
Cc: stable@vger.kernel.org
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
virt/kvm/kvm_main.c | 34 ++++++++++++++++------------------
1 file changed, 16 insertions(+), 18 deletions(-)
diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c
index 45e784462ec6..576bcb21be3a 100644
--- a/virt/kvm/kvm_main.c
+++ b/virt/kvm/kvm_main.c
@@ -2925,17 +2925,6 @@ static int hva_to_pfn_slow(struct kvm_follow_pfn *kfp, kvm_pfn_t *pfn)
return npages;
}
-static bool vma_is_valid(struct vm_area_struct *vma, bool write_fault)
-{
- if (unlikely(!(vma->vm_flags & VM_READ)))
- return false;
-
- if (write_fault && (unlikely(!(vma->vm_flags & VM_WRITE))))
- return false;
-
- return true;
-}
-
static int hva_to_pfn_remapped(struct vm_area_struct *vma,
struct kvm_follow_pfn *kfp, kvm_pfn_t *p_pfn)
{
@@ -3008,20 +2997,29 @@ kvm_pfn_t hva_to_pfn(struct kvm_follow_pfn *kfp)
retry:
vma = vma_lookup(current->mm, kfp->hva);
- if (vma == NULL)
+ /*
+ * GUP failed. It could be an inaccessible mapping, a pfnmap one,
+ * or the page might be absent.
+ */
+
+ if (vma == NULL || unlikely(!(vma->vm_flags & VM_READ))) {
pfn = KVM_PFN_ERR_FAULT;
- else if (vma->vm_flags & (VM_IO | VM_PFNMAP)) {
+ } else if ((kfp->flags & FOLL_WRITE) && unlikely(!(vma->vm_flags & VM_WRITE))) {
+ /*
+ * Exit to userspace for PROT_READ mappings in a writable
+ * memslot, as this is part of the API.
+ */
+ pfn = vma->vm_flags & (VM_IO | VM_PFNMAP) ? KVM_PFN_ERR_RO_FAULT :
+ KVM_PFN_ERR_FAULT;
+ } else if (vma->vm_flags & (VM_IO | VM_PFNMAP)) {
r = hva_to_pfn_remapped(vma, kfp, &pfn);
if (r == -EAGAIN)
goto retry;
if (r < 0)
pfn = KVM_PFN_ERR_FAULT;
} else {
- if ((kfp->flags & FOLL_NOWAIT) &&
- vma_is_valid(vma, kfp->flags & FOLL_WRITE))
- pfn = KVM_PFN_ERR_NEEDS_IO;
- else
- pfn = KVM_PFN_ERR_FAULT;
+ pfn = kfp->flags & FOLL_NOWAIT ? KVM_PFN_ERR_NEEDS_IO :
+ KVM_PFN_ERR_FAULT;
}
mmap_read_unlock(current->mm);
return pfn;
--
2.55.0
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH v2 5/6] mm: pull writability check to follow_pfnmap_start()
2026-08-04 12:05 [PATCH v2 0/6] mm, drm: fix interaction of .pfn_mkwrite() with fixup_user_fault() Paolo Bonzini
` (3 preceding siblings ...)
2026-08-04 12:05 ` [PATCH v2 4/6] kvm: apply VM_READ/VM_WRITE checks to all VMA types Paolo Bonzini
@ 2026-08-04 12:05 ` Paolo Bonzini
2026-08-04 12:14 ` sashiko-bot
2026-08-04 12:05 ` [PATCH v2 6/6] kvm: return -EFAULT for writes to !VM_WRITE IO mappings Paolo Bonzini
5 siblings, 1 reply; 24+ messages in thread
From: Paolo Bonzini @ 2026-08-04 12:05 UTC (permalink / raw)
To: linux-kernel, kvm
Cc: Alex Williamson, bcm-kernel-feedback-list, Boris Brezillon,
Christian Koenig, David Hildenbrand, dri-devel, Fei Li, Huang Rui,
linux-mm, linux-s390, Michal Hocko, Peter Xu, Sergio Lopez,
Sean Christopherson, Thomas Zimmermann, stable
All callers of follow_pfnmap_start() except s390_pci_mmio_write()
are following it, if they are doing a write, with a check that
args.writable is true; for s390_pci_mmio_write() that's a bug.
Also, most of them return -EFAULT if it is not. Pull the check
directly into follow_pfnmap_start() through another input parameter
args.write_fault, to eliminate the need to do it in the caller.
This also fixes an issue where follow_pfnmap_start() would return
0 for a PFN that is mapped read-only, and the caller would not
attempt to call fixup_user_fault() on it; this can happen with
vm_ops that set .pfn_mkwrite(), for example. Instead, now the
caller (for example hva_to_pfn_remapped()) sees an error,
does attempt to fix it, and only returns -EFAULT if the
fixup was fruitless.
Reported-by: Sergio Lopez <slp@redhat.com>
Fixes: 28e3918179aa ("drm/gem-shmem: Track folio accessed/dirty status in mmap")
Link: https://lore.kernel.org/kvm/CAAiTLFU1ALsDoJoKW3d9bUvv990AozAoX=bEHmfnG54qyBAHFg@mail.gmail.com/
Cc: stable@vger.kernel.org
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
arch/s390/pci/pci_mmio.c | 2 ++
drivers/vfio/vfio_iommu_type1.c | 17 +++++----
drivers/virt/acrn/mm.c | 10 +-----
include/linux/mm.h | 3 ++
mm/memory.c | 62 ++++++++++++++++++++-------------
virt/kvm/kvm_main.c | 15 ++++----
6 files changed, 58 insertions(+), 51 deletions(-)
diff --git a/arch/s390/pci/pci_mmio.c b/arch/s390/pci/pci_mmio.c
index 51e7a28af899..d9d5b3318cbc 100644
--- a/arch/s390/pci/pci_mmio.c
+++ b/arch/s390/pci/pci_mmio.c
@@ -180,6 +180,7 @@ SYSCALL_DEFINE3(s390_pci_mmio_write, unsigned long, mmio_addr,
args.address = mmio_addr;
args.vma = vma;
+ args.write = true;
ret = follow_pfnmap_start(&args);
if (ret) {
fixup_user_fault(current->mm, mmio_addr, FAULT_FLAG_WRITE, NULL);
@@ -332,6 +333,7 @@ SYSCALL_DEFINE3(s390_pci_mmio_read, unsigned long, mmio_addr,
args.vma = vma;
args.address = mmio_addr;
+ args.write = false;
ret = follow_pfnmap_start(&args);
if (ret) {
fixup_user_fault(current->mm, mmio_addr, 0, NULL);
diff --git a/drivers/vfio/vfio_iommu_type1.c b/drivers/vfio/vfio_iommu_type1.c
index c8151ba54de3..e6d3a2311a99 100644
--- a/drivers/vfio/vfio_iommu_type1.c
+++ b/drivers/vfio/vfio_iommu_type1.c
@@ -541,7 +541,11 @@ static int follow_fault_pfn(struct vm_area_struct *vma, struct mm_struct *mm,
unsigned long vaddr, unsigned long *pfn,
unsigned long *addr_mask, bool write_fault)
{
- struct follow_pfnmap_args args = { .vma = vma, .address = vaddr };
+ struct follow_pfnmap_args args = {
+ .vma = vma,
+ .address = vaddr,
+ .write = write_fault,
+ };
int ret;
ret = follow_pfnmap_start(&args);
@@ -563,15 +567,10 @@ static int follow_fault_pfn(struct vm_area_struct *vma, struct mm_struct *mm,
return ret;
}
- if (write_fault && !args.writable) {
- ret = -EFAULT;
- } else {
- *pfn = args.pfn;
- *addr_mask = args.addr_mask;
- }
-
+ *pfn = args.pfn;
+ *addr_mask = args.addr_mask;
follow_pfnmap_end(&args);
- return ret;
+ return 0;
}
/*
diff --git a/drivers/virt/acrn/mm.c b/drivers/virt/acrn/mm.c
index 5bca500a83e0..2f9808399f19 100644
--- a/drivers/virt/acrn/mm.c
+++ b/drivers/virt/acrn/mm.c
@@ -177,7 +177,6 @@ int acrn_vm_ram_map(struct acrn_vm *vm, struct acrn_vm_memmap *memmap)
vma = vma_lookup(current->mm, memmap->vma_base);
if (vma && ((vma->vm_flags & VM_PFNMAP) != 0)) {
unsigned long start_pfn, cur_pfn;
- bool writable;
if ((memmap->vma_base + memmap->len) > vma->vm_end) {
mmap_read_unlock(current->mm);
@@ -188,6 +187,7 @@ int acrn_vm_ram_map(struct acrn_vm *vm, struct acrn_vm_memmap *memmap)
struct follow_pfnmap_args args = {
.vma = vma,
.address = memmap->vma_base + i * PAGE_SIZE,
+ .write = !!(memmap->attr & ACRN_MEM_ACCESS_WRITE),
};
ret = follow_pfnmap_start(&args);
@@ -197,16 +197,8 @@ int acrn_vm_ram_map(struct acrn_vm *vm, struct acrn_vm_memmap *memmap)
cur_pfn = args.pfn;
if (i == 0)
start_pfn = cur_pfn;
- writable = args.writable;
follow_pfnmap_end(&args);
- /* Disallow write access if the PTE is not writable. */
- if (!writable &&
- (memmap->attr & ACRN_MEM_ACCESS_WRITE)) {
- ret = -EFAULT;
- break;
- }
-
/* Disallow refcounted pages. */
if (pfn_valid(cur_pfn) &&
!PageReserved(pfn_to_page(cur_pfn))) {
diff --git a/include/linux/mm.h b/include/linux/mm.h
index 01184a4bdd6f..1659cb8f42fd 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -3136,9 +3136,12 @@ struct follow_pfnmap_args {
* Inputs:
* @vma: Pointer to @vm_area_struct struct
* @address: the virtual address to walk
+ * @write: if true, fail with -EFAULT unless the mapping is
+ * writable
*/
struct vm_area_struct *vma;
unsigned long address;
+ bool write;
/**
* Internals:
*
diff --git a/mm/memory.c b/mm/memory.c
index b5555217b121..27f5dcc319c8 100644
--- a/mm/memory.c
+++ b/mm/memory.c
@@ -6774,12 +6774,15 @@ int __pmd_alloc(struct mm_struct *mm, pud_t *pud, unsigned long address)
}
#endif /* __PAGETABLE_PMD_FOLDED */
-static inline void pfnmap_args_setup(struct follow_pfnmap_args *args,
- spinlock_t *lock, pte_t *ptep,
- pgprot_t pgprot, unsigned long pfn_base,
- unsigned long addr_mask, bool writable,
- bool special)
+static inline int pfnmap_args_setup(struct follow_pfnmap_args *args,
+ spinlock_t *lock, pte_t *ptep,
+ pgprot_t pgprot, unsigned long pfn_base,
+ unsigned long addr_mask, bool writable,
+ bool special)
{
+ if (!writable && args->write)
+ return -EFAULT;
+
args->lock = lock;
args->ptep = ptep;
args->pfn = pfn_base + ((args->address & ~addr_mask) >> PAGE_SHIFT);
@@ -6787,6 +6790,7 @@ static inline void pfnmap_args_setup(struct follow_pfnmap_args *args,
args->pgprot = pgprot;
args->writable = writable;
args->special = special;
+ return 0;
}
static inline void pfnmap_lockdep_assert(struct vm_area_struct *vma)
@@ -6808,8 +6812,9 @@ static inline void pfnmap_lockdep_assert(struct vm_area_struct *vma)
* @args: Pointer to struct @follow_pfnmap_args
*
* The caller needs to setup args->vma and args->address to point to the
- * virtual address as the target of such lookup. On a successful return,
- * the results will be put into other output fields.
+ * virtual address as the target of such lookup, and optionally set
+ * args->write to require a writable mapping. On a successful
+ * return, the results will be put into other output fields.
*
* After the caller finished using the fields, the caller must invoke
* another follow_pfnmap_end() to proper releases the locks and resources
@@ -6832,7 +6837,8 @@ static inline void pfnmap_lockdep_assert(struct vm_area_struct *vma)
*
* This function must not be used to modify PTE content.
*
- * Return: zero on success, negative otherwise.
+ * Return: zero on success, -EFAULT if @args->write was set but the
+ * mapping is not writable, -EINVAL if there is no mapping at all.
*/
int follow_pfnmap_start(struct follow_pfnmap_args *args)
{
@@ -6845,6 +6851,7 @@ int follow_pfnmap_start(struct follow_pfnmap_args *args)
pud_t *pudp, pud;
pmd_t *pmdp, pmd;
pte_t *ptep, pte;
+ int r = -EINVAL;
pfnmap_lockdep_assert(vma);
@@ -6878,10 +6885,12 @@ int follow_pfnmap_start(struct follow_pfnmap_args *args)
spin_unlock(lock);
goto retry;
}
- pfnmap_args_setup(args, lock, NULL, pud_pgprot(pud),
- pud_pfn(pud), PUD_MASK, pud_write(pud),
- pud_special(pud));
- return 0;
+ r = pfnmap_args_setup(args, lock, NULL, pud_pgprot(pud),
+ pud_pfn(pud), PUD_MASK, pud_write(pud),
+ pud_special(pud));
+ if (r)
+ spin_unlock(lock);
+ return r;
}
pmdp = pmd_offset(pudp, address);
@@ -6899,10 +6908,12 @@ int follow_pfnmap_start(struct follow_pfnmap_args *args)
spin_unlock(lock);
goto retry;
}
- pfnmap_args_setup(args, lock, NULL, pmd_pgprot(pmd),
- pmd_pfn(pmd), PMD_MASK, pmd_write(pmd),
- pmd_special(pmd));
- return 0;
+ r = pfnmap_args_setup(args, lock, NULL, pmd_pgprot(pmd),
+ pmd_pfn(pmd), PMD_MASK, pmd_write(pmd),
+ pmd_special(pmd));
+ if (r)
+ spin_unlock(lock);
+ return r;
}
ptep = pte_offset_map_lock(mm, pmdp, address, &lock);
@@ -6911,14 +6922,16 @@ int follow_pfnmap_start(struct follow_pfnmap_args *args)
pte = ptep_get(ptep);
if (!pte_present(pte))
goto unlock;
- pfnmap_args_setup(args, lock, ptep, pte_pgprot(pte),
- pte_pfn(pte), PAGE_MASK, pte_write(pte),
- pte_special(pte));
+ r = pfnmap_args_setup(args, lock, ptep, pte_pgprot(pte),
+ pte_pfn(pte), PAGE_MASK, pte_write(pte),
+ pte_special(pte));
+ if (r)
+ goto unlock;
return 0;
unlock:
pte_unmap_unlock(ptep, lock);
out:
- return -EINVAL;
+ return r;
}
EXPORT_SYMBOL_GPL(follow_pfnmap_start);
@@ -6960,7 +6973,11 @@ int generic_access_phys(struct vm_area_struct *vma, unsigned long addr,
int offset = offset_in_page(addr);
int ret = -EINVAL;
bool writable;
- struct follow_pfnmap_args args = { .vma = vma, .address = addr };
+ struct follow_pfnmap_args args = {
+ .vma = vma,
+ .address = addr,
+ .write = !!(write & FOLL_WRITE)
+ };
retry:
if (follow_pfnmap_start(&args))
@@ -6970,9 +6987,6 @@ int generic_access_phys(struct vm_area_struct *vma, unsigned long addr,
writable = args.writable;
follow_pfnmap_end(&args);
- if ((write & FOLL_WRITE) && !writable)
- return -EINVAL;
-
maddr = ioremap_prot(phys_addr, PAGE_ALIGN(len + offset), prot);
if (!maddr)
return -ENOMEM;
diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c
index 576bcb21be3a..b7c21a48a45c 100644
--- a/virt/kvm/kvm_main.c
+++ b/virt/kvm/kvm_main.c
@@ -2928,8 +2928,11 @@ static int hva_to_pfn_slow(struct kvm_follow_pfn *kfp, kvm_pfn_t *pfn)
static int hva_to_pfn_remapped(struct vm_area_struct *vma,
struct kvm_follow_pfn *kfp, kvm_pfn_t *p_pfn)
{
- struct follow_pfnmap_args args = { .vma = vma, .address = kfp->hva };
- bool write_fault = kfp->flags & FOLL_WRITE;
+ struct follow_pfnmap_args args = {
+ .vma = vma,
+ .address = kfp->hva,
+ .write = !!(kfp->flags & FOLL_WRITE),
+ };
int r;
/*
@@ -2948,7 +2951,7 @@ static int hva_to_pfn_remapped(struct vm_area_struct *vma,
*/
bool unlocked = false;
r = fixup_user_fault(current->mm, kfp->hva,
- (write_fault ? FAULT_FLAG_WRITE : 0),
+ (args.write ? FAULT_FLAG_WRITE : 0),
&unlocked);
if (unlocked)
return -EAGAIN;
@@ -2960,13 +2963,7 @@ static int hva_to_pfn_remapped(struct vm_area_struct *vma,
return r;
}
- if (write_fault && !args.writable) {
- *p_pfn = KVM_PFN_ERR_RO_FAULT;
- goto out;
- }
-
*p_pfn = kvm_resolve_pfn(kfp, NULL, &args, args.writable);
-out:
follow_pfnmap_end(&args);
return r;
}
--
2.55.0
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH v2 6/6] kvm: return -EFAULT for writes to !VM_WRITE IO mappings
2026-08-04 12:05 [PATCH v2 0/6] mm, drm: fix interaction of .pfn_mkwrite() with fixup_user_fault() Paolo Bonzini
` (4 preceding siblings ...)
2026-08-04 12:05 ` [PATCH v2 5/6] mm: pull writability check to follow_pfnmap_start() Paolo Bonzini
@ 2026-08-04 12:05 ` Paolo Bonzini
2026-08-04 12:14 ` sashiko-bot
2026-08-04 21:08 ` Sean Christopherson
5 siblings, 2 replies; 24+ messages in thread
From: Paolo Bonzini @ 2026-08-04 12:05 UTC (permalink / raw)
To: linux-kernel, kvm
Cc: Alex Williamson, bcm-kernel-feedback-list, Boris Brezillon,
Christian Koenig, David Hildenbrand, dri-devel, Fei Li, Huang Rui,
linux-mm, linux-s390, Michal Hocko, Peter Xu, Sergio Lopez,
Sean Christopherson, Thomas Zimmermann
KVM's behavior when the guest writes to a non-writable VMA is inconsistent.
For regular, page-backed mappings it returns KVM_PFN_ERR_FAULT and thus
returns -EFAULT to userspace (which is ABI, and relied upon by tests);
for VM_IO/VM_PFNMAP mappings instead it returns KVM_PFN_ERR_RO_FAULT
and thus exits to userspace with KVM_EXIT_MMIO.
This behavior for VM_{IO,PFNMAP} was added by commit bd2fae8da794 ("KVM:
do not assume PTE is writable after follow_pfn"), and even if it has been
in place for five years it is unlikely that it is relied upon by userspace,
since it is inconsistent with KVM itself. Change hva_to_pfn() to return
KVM_PFN_ERR_FAULT for all non-writable VMAs, and restrict KVM_EXIT_MMIO
to the case of an explicitly read-only memslot.
Suggested-by: Sean Christopherson <seanjc@google.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
virt/kvm/kvm_main.c | 11 +++--------
1 file changed, 3 insertions(+), 8 deletions(-)
diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c
index b7c21a48a45c..da5b0bb62590 100644
--- a/virt/kvm/kvm_main.c
+++ b/virt/kvm/kvm_main.c
@@ -2999,15 +2999,10 @@ kvm_pfn_t hva_to_pfn(struct kvm_follow_pfn *kfp)
* or the page might be absent.
*/
- if (vma == NULL || unlikely(!(vma->vm_flags & VM_READ))) {
+ if (vma == NULL ||
+ unlikely(!(vma->vm_flags & VM_READ)) ||
+ ((kfp->flags & FOLL_WRITE) && unlikely(!(vma->vm_flags & VM_WRITE)))) {
pfn = KVM_PFN_ERR_FAULT;
- } else if ((kfp->flags & FOLL_WRITE) && unlikely(!(vma->vm_flags & VM_WRITE))) {
- /*
- * Exit to userspace for PROT_READ mappings in a writable
- * memslot, as this is part of the API.
- */
- pfn = vma->vm_flags & (VM_IO | VM_PFNMAP) ? KVM_PFN_ERR_RO_FAULT :
- KVM_PFN_ERR_FAULT;
} else if (vma->vm_flags & (VM_IO | VM_PFNMAP)) {
r = hva_to_pfn_remapped(vma, kfp, &pfn);
if (r == -EAGAIN)
--
2.55.0
^ permalink raw reply related [flat|nested] 24+ messages in thread
* Re: [PATCH v2 5/6] mm: pull writability check to follow_pfnmap_start()
2026-08-04 12:05 ` [PATCH v2 5/6] mm: pull writability check to follow_pfnmap_start() Paolo Bonzini
@ 2026-08-04 12:14 ` sashiko-bot
0 siblings, 0 replies; 24+ messages in thread
From: sashiko-bot @ 2026-08-04 12:14 UTC (permalink / raw)
To: Paolo Bonzini
Cc: dri-devel, Vasily Gorbik, Heiko Carstens, Alexander Gordeev, kvm,
linux-s390
> All callers of follow_pfnmap_start() except s390_pci_mmio_write()
> are following it, if they are doing a write, with a check that
> args.writable is true; for s390_pci_mmio_write() that's a bug.
> Also, most of them return -EFAULT if it is not. Pull the check
> directly into follow_pfnmap_start() through another input parameter
> args.write_fault, to eliminate the need to do it in the caller.
>
> This also fixes an issue where follow_pfnmap_start() would return
> 0 for a PFN that is mapped read-only, and the caller would not
> attempt to call fixup_user_fault() on it; this can happen with
> vm_ops that set .pfn_mkwrite(), for example. Instead, now the
> caller (for example hva_to_pfn_remapped()) sees an error,
> does attempt to fix it, and only returns -EFAULT if the
> fixup was fruitless.
>
> Reported-by: Sergio Lopez <slp@redhat.com>
> Fixes: 28e3918179aa ("drm/gem-shmem: Track folio accessed/dirty status in mmap")
> Link: https://lore.kernel.org/kvm/CAAiTLFU1ALsDoJoKW3d9bUvv990AozAoX=bEHmfnG54qyBAHFg@mail.gmail.com/
> Cc: stable@vger.kernel.org
> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260804120529.1730187-1-pbonzini@redhat.com?part=5
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v2 6/6] kvm: return -EFAULT for writes to !VM_WRITE IO mappings
2026-08-04 12:05 ` [PATCH v2 6/6] kvm: return -EFAULT for writes to !VM_WRITE IO mappings Paolo Bonzini
@ 2026-08-04 12:14 ` sashiko-bot
2026-08-04 21:08 ` Sean Christopherson
1 sibling, 0 replies; 24+ messages in thread
From: sashiko-bot @ 2026-08-04 12:14 UTC (permalink / raw)
To: Paolo Bonzini
Cc: Vasily Gorbik, linux-s390, kvm, dri-devel, Heiko Carstens,
Alexander Gordeev
> KVM's behavior when the guest writes to a non-writable VMA is inconsistent.
> For regular, page-backed mappings it returns KVM_PFN_ERR_FAULT and thus
> returns -EFAULT to userspace (which is ABI, and relied upon by tests);
> for VM_IO/VM_PFNMAP mappings instead it returns KVM_PFN_ERR_RO_FAULT
> and thus exits to userspace with KVM_EXIT_MMIO.
>
> This behavior for VM_{IO,PFNMAP} was added by commit bd2fae8da794 ("KVM:
> do not assume PTE is writable after follow_pfn"), and even if it has been
> in place for five years it is unlikely that it is relied upon by userspace,
> since it is inconsistent with KVM itself. Change hva_to_pfn() to return
> KVM_PFN_ERR_FAULT for all non-writable VMAs, and restrict KVM_EXIT_MMIO
> to the case of an explicitly read-only memslot.
>
> Suggested-by: Sean Christopherson <seanjc@google.com>
> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260804120529.1730187-1-pbonzini@redhat.com?part=6
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v2 1/6] mm: export vmf_insert_pfn_prot_mkwrite(), change variants to inline
2026-08-04 12:05 ` [PATCH v2 1/6] mm: export vmf_insert_pfn_prot_mkwrite(), change variants to inline Paolo Bonzini
@ 2026-08-04 12:20 ` sashiko-bot
0 siblings, 0 replies; 24+ messages in thread
From: sashiko-bot @ 2026-08-04 12:20 UTC (permalink / raw)
To: Paolo Bonzini
Cc: dri-devel, linux-s390, kvm, Alexander Gordeev, Heiko Carstens,
Vasily Gorbik
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [Critical] The global shared zero page can be mapped as writable by insert_pfn, leading to memory corruption.
--
--- Patch [1]: [PATCH v2 1/6] mm: export vmf_insert_pfn_prot_mkwrite(), change variants to inline ---
commit 010e09346cc93d215af7040a0cfe7fd891a1bd7d
Author: Paolo Bonzini <pbonzini@redhat.com>
mm: export vmf_insert_pfn_prot_mkwrite(), change variants to inline
This commit introduces vmf_insert_pfn_prot_mkwrite() and
vmf_insert_pfn_mkwrite() to allow drivers to install writable PTEs during
faults when a mapping is under write notification. It refactors the existing
vmf_insert_pfn() family of functions into inline wrappers around this new,
more flexible variant.
> diff --git a/mm/memory.c b/mm/memory.c
> index 6637c5b13c9ba..786b114d9921a 100644
> --- a/mm/memory.c
> +++ b/mm/memory.c
[ ... ]
> -vm_fault_t vmf_insert_pfn_prot(struct vm_area_struct *vma, unsigned long addr,
> - unsigned long pfn, pgprot_t pgprot)
> +vm_fault_t vmf_insert_pfn_prot_mkwrite(struct vm_area_struct *vma,
> + unsigned long addr, unsigned long pfn, pgprot_t pgprot,
> + bool mkwrite)
> {
[ ... ]
> pfnmap_setup_cachemode_pfn(pfn, &pgprot);
>
> - return insert_pfn(vma, addr, pfn, pgprot, false);
> -}
> -EXPORT_SYMBOL(vmf_insert_pfn_prot);
> -
> -/**
> - * vmf_insert_pfn - insert single pfn into user vma
[ ... ]
> + return insert_pfn(vma, addr, pfn, pgprot, mkwrite);
> }
> -EXPORT_SYMBOL(vmf_insert_pfn);
> +EXPORT_SYMBOL(vmf_insert_pfn_prot_mkwrite);
[Severity: Critical]
This is a pre-existing issue, but by allowing mkwrite to be passed directly
through to insert_pfn(), can this result in the global shared zero page being
mapped writable?
If a driver maps the zero page via vmf_insert_pfn_mkwrite(..., true) into a
shared writable VM_PFNMAP mapping, insert_pfn() in mm/memory.c handles the
new entry like this:
/* Ok, finally just insert the thing.. */
entry = pte_mkspecial(pfn_pte(pfn, prot));
if (mkwrite) {
entry = pte_mkyoung(entry);
entry = maybe_mkwrite(pte_mkdirty(entry), vma);
}
Because there is no check verifying if the passed PFN is the zero page,
maybe_mkwrite() will explicitly make the PTE writable, bypassing the zero
page protections ordinarily afforded by vm_mixed_ok().
If the global zero page becomes writable, unprivileged userspace could write
to it, which would cause memory corruption across the system for any process
reading uninitialized memory. Should we add a defense in insert_pfn() to
explicitly reject or write-protect the zero page when mkwrite is true?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260804120529.1730187-1-pbonzini@redhat.com?part=1
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v2 3/6] drm/ttm, drm/vmwgfx: directly create writable PTEs when mkwrite is in use
2026-08-04 12:05 ` [PATCH v2 3/6] drm/ttm, drm/vmwgfx: directly create writable PTEs when mkwrite is in use Paolo Bonzini
@ 2026-08-04 12:21 ` sashiko-bot
2026-08-04 12:47 ` Paolo Bonzini
2026-08-06 23:32 ` Peter Xu
1 sibling, 1 reply; 24+ messages in thread
From: sashiko-bot @ 2026-08-04 12:21 UTC (permalink / raw)
To: Paolo Bonzini
Cc: Heiko Carstens, linux-s390, Alexander Gordeev, Vasily Gorbik,
dri-devel, kvm
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
New issues:
- [High] Missing dirty page tracking in vmw_bo_vm_fault() when prefaulting fails, leading to silent data loss.
Pre-existing issues:
- [High] Inverted error handling logic in ttm_bo_vm_fault_reserved() hides primary fault errors and propagates prefault errors.
--
--- Patch [3]: [PATCH v2 3/6] drm/ttm, drm/vmwgfx: directly create writable PTEs when mkwrite is in use ---
commit 2f0ecb57a3fc3ac93f0458269dd5ea847c608a25
Author: Paolo Bonzini <pbonzini@redhat.com>
Subject: drm/ttm, drm/vmwgfx: directly create writable PTEs when mkwrite is in use
This patch modifies TTM BO fault handling to explicitly request writable
PTEs for the primary page when mkwrite is in use. It also updates vmwgfx
to manually record write faults since the .pfn_mkwrite() callback is no
longer invoked for the primary page.
> diff --git a/drivers/gpu/drm/ttm/ttm_bo_vm.c b/drivers/gpu/drm/ttm/ttm_bo_vm.c
> index a80510489c456..3ebde936ce608 100644
> --- a/drivers/gpu/drm/ttm/ttm_bo_vm.c
> +++ b/drivers/gpu/drm/ttm/ttm_bo_vm.c
[ ... ]
> @@ -242,6 +243,7 @@ vm_fault_t ttm_bo_vm_fault_reserved(struct vm_fault *vmf,
> * Speculatively prefault a number of pages. Only error on
> * first page.
> */
> + mkwrite = !!(vmf->flags & FAULT_FLAG_WRITE);
> for (i = 0; i < num_prefault; ++i) {
> if (bo->resource->bus.is_iomem) {
> pfn = ttm_bo_io_mem_pfn(bo, page_offset);
> @@ -263,9 +265,10 @@ vm_fault_t ttm_bo_vm_fault_reserved(struct vm_fault *vmf,
> * at arbitrary times while the data is mmap'ed.
> * See vmf_insert_pfn_prot() for a discussion.
> */
> - ret = vmf_insert_pfn_prot(vma, address, pfn, prot);
> + ret = vmf_insert_pfn_prot_mkwrite(vma, address, pfn, prot, mkwrite);
>
> - /* Never error on prefaulted PTEs */
> + /* Never error on prefaulted PTEs and never map them writable */
> + mkwrite = false;
> if (unlikely((ret & VM_FAULT_ERROR))) {
> if (i == 0)
> return VM_FAULT_NOPAGE;
> else
> break;
> }
[Severity: High]
This is a pre-existing issue, but does this error handling logic for
ttm_bo_vm_fault_reserved() behave backwards?
If vmf_insert_pfn_prot_mkwrite() fails for the primary page (i == 0),
returning VM_FAULT_NOPAGE hides the error from the fault handler,
potentially causing an infinite page fault loop.
Conversely, if a speculative prefault (i > 0) fails, the loop breaks
and the error is propagated to the fault handler, which might cause
a spurious process crash even though the primary fault succeeded.
> diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_page_dirty.c b/drivers/gpu/drm/vmwgfx/vmwgfx_page_dirty.c
> index 45561bc1c9eff..3099558c0762f 100644
> --- a/drivers/gpu/drm/vmwgfx/vmwgfx_page_dirty.c
> +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_page_dirty.c
[ ... ]
> @@ -484,6 +487,9 @@ vm_fault_t vmw_bo_vm_fault(struct vm_fault *vmf)
> prot = vm_get_page_prot(vma->vm_flags);
>
> ret = ttm_bo_vm_fault_reserved(vmf, prot, num_prefault);
> + if (ret == VM_FAULT_NOPAGE && (vmf->flags & FAULT_FLAG_WRITE))
> + WARN_ON_ONCE(vmw_bo_dirty_mkwrite(vmf, bo));
> +
> if (ret == VM_FAULT_RETRY && !(vmf->flags & FAULT_FLAG_RETRY_NOWAIT))
> return ret;
>
[Severity: High]
If a speculative prefault fails inside ttm_bo_vm_fault_reserved(), will
this skip tracking the dirty page and lead to silent data loss?
Because the primary page is successfully mapped writable before the prefault
loop continues:
ttm_bo_vm_fault_reserved()
ret = vmf_insert_pfn_prot_mkwrite(vma, address, pfn, prot, mkwrite);
// ... prefault loops to next page and fails ...
break; // returns the error
The error is returned to vmw_bo_vm_fault(), meaning ret is not
VM_FAULT_NOPAGE.
This check is skipped, leaving the primary page mapped writable without the
driver marking it dirty.
If the process survives the fault (e.g., by handling SIGBUS), could it
then modify data without the driver tracking it?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260804120529.1730187-1-pbonzini@redhat.com?part=3
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v2 4/6] kvm: apply VM_READ/VM_WRITE checks to all VMA types
2026-08-04 12:05 ` [PATCH v2 4/6] kvm: apply VM_READ/VM_WRITE checks to all VMA types Paolo Bonzini
@ 2026-08-04 12:23 ` sashiko-bot
2026-08-04 12:44 ` Paolo Bonzini
2026-08-04 21:15 ` Sean Christopherson
1 sibling, 1 reply; 24+ messages in thread
From: sashiko-bot @ 2026-08-04 12:23 UTC (permalink / raw)
To: Paolo Bonzini
Cc: Vasily Gorbik, Heiko Carstens, kvm, linux-s390, Alexander Gordeev,
dri-devel
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Write faults on write-only `VM_IO | VM_PFNMAP` VMAs are incorrectly rejected, breaking guest writes to write-only device memory.
--
--- Patch [4]: [PATCH v2 4/6] kvm: apply VM_READ/VM_WRITE checks to all VMA types ---
commit 254a4090eb1ddc66d1d90350bb0e3ef36a2ee158
Author: Paolo Bonzini <pbonzini@redhat.com>
kvm: apply VM_READ/VM_WRITE checks to all VMA types
This commit moves the VM_READ and VM_WRITE checks to the beginning of the
fallback VMA lookup block in hva_to_pfn(). This standardizes the error
return codes across remapped and regular VMAs, eliminating inconsistencies
in how RO faults are handled.
> diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c
> index 45e784462ec6e..576bcb21be3a6 100644
> --- a/virt/kvm/kvm_main.c
> +++ b/virt/kvm/kvm_main.c
[ ... ]
> @@ -3008,20 +2997,29 @@ kvm_pfn_t hva_to_pfn(struct kvm_follow_pfn *kfp)
> retry:
> vma = vma_lookup(current->mm, kfp->hva);
>
> - if (vma == NULL)
> + /*
> + * GUP failed. It could be an inaccessible mapping, a pfnmap one,
> + * or the page might be absent.
> + */
> +
> + if (vma == NULL || unlikely(!(vma->vm_flags & VM_READ))) {
[Severity: High]
Does this unconditionally reject write-only mappings?
If a guest performs a memory write to a guest physical address backed by a
write-only host VMA (e.g. VM_IO or VM_PFNMAP mapped with PROT_WRITE but
without PROT_READ), get_user_pages() will fail and fall back to this lookup.
Since the write-only VMA lacks VM_READ, this check will evaluate to true and
return KVM_PFN_ERR_FAULT, skipping the VM_IO | VM_PFNMAP handler. Could this
prevent guests from writing to legitimate write-only device memory, such as
command ring buffers or write-only PCI BARs?
> pfn = KVM_PFN_ERR_FAULT;
> - else if (vma->vm_flags & (VM_IO | VM_PFNMAP)) {
> + } else if ((kfp->flags & FOLL_WRITE) && unlikely(!(vma->vm_flags & VM_WRITE))) {
> + /*
> + * Exit to userspace for PROT_READ mappings in a writable
> + * memslot, as this is part of the API.
> + */
> + pfn = vma->vm_flags & (VM_IO | VM_PFNMAP) ? KVM_PFN_ERR_RO_FAULT :
> + KVM_PFN_ERR_FAULT;
> + } else if (vma->vm_flags & (VM_IO | VM_PFNMAP)) {
> r = hva_to_pfn_remapped(vma, kfp, &pfn);
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260804120529.1730187-1-pbonzini@redhat.com?part=4
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v2 2/6] drm/shmem_helper: use vmf_insert_pfn_mkwrite()
2026-08-04 12:05 ` [PATCH v2 2/6] drm/shmem_helper: use vmf_insert_pfn_mkwrite() Paolo Bonzini
@ 2026-08-04 12:30 ` sashiko-bot
2026-08-04 14:15 ` Boris Brezillon
1 sibling, 0 replies; 24+ messages in thread
From: sashiko-bot @ 2026-08-04 12:30 UTC (permalink / raw)
To: Paolo Bonzini
Cc: dri-devel, Heiko Carstens, linux-s390, kvm, Vasily Gorbik,
Alexander Gordeev
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Pre-existing issues:
- [Critical] The `.huge_fault` handler `drm_gem_shmem_any_fault` does not verify if the PMD-sized huge page fits entirely within the bounds of the VMA, allowing a VMA boundary bypass.
- [High] Incorrect page offset calculation in DRM shmem fault handlers causes the wrong pages to be mapped.
--
--- Patch [2]: [PATCH v2 2/6] drm/shmem_helper: use vmf_insert_pfn_mkwrite() ---
commit 6ce5b52de7395aa4c0a50d399649686c52e613a9
Author: Paolo Bonzini <pbonzini@redhat.com>
drm/shmem_helper: use vmf_insert_pfn_mkwrite()
This ensures that KVM or VFIO correctly see a writable PTE when
they request one. Otherwise, a guest write to an unpopulated
PTE from a mapping backed by a DRM GEM BO triggers a VM exit
with EFAULT.
Link: https://lore.kernel.org/kvm/20260729072044.25796-1-slp@redhat.com/
> diff --git a/drivers/gpu/drm/drm_gem_shmem_helper.c b/drivers/gpu/drm/drm_gem_shmem_helper.c
> index c989459eb2159..c81be3e973173 100644
> --- a/drivers/gpu/drm/drm_gem_shmem_helper.c
> +++ b/drivers/gpu/drm/drm_gem_shmem_helper.c
> @@ -589,11 +589,25 @@ static void drm_gem_shmem_record_mkwrite(struct vm_fault *vmf)
> folio_mark_dirty(page_folio(shmem->pages[page_offset]));
[Severity: High]
This is a pre-existing issue, but does this page_offset calculation
correctly handle the requested offset within the GEM object?
When userspace mmaps a sub-range of a GEM object with a non-zero
offset, or unmaps a prefix of the VMA, the core memory management
sets vma->vm_pgoff to include the requested offset. Because
page_offset evaluates to vmf->pgoff - vma->vm_pgoff, it calculates
the offset relative to the VMA start, ignoring the requested offset
within the GEM object itself.
This can cause folio_mark_dirty() to mark the wrong folio dirty.
The same calculation is present in drm_gem_shmem_any_fault(),
which could lead to mapping the wrong physical pages.
> }
>
> +/*
> + * Because the vm_ops have a .pfn_mkwrite() callback, vma_set_page_prot()
> + * has cleared the write bit from vma->vm_page_prot. vmf_insert_pfn()
> + * would install a read-only entry even for a write fault, relying on a
> + * second fault to reach .pfn_mkwrite() and upgrade it, but that second
> + * fault never happens for fixup_user_fault() callers that directly
> + * walk the page tables with follow_pfnmap_start(). To ensure that
> + * they don't see the read-only entry, pass FAULT_FLAG_WRITE info down
> + * to install a writable entry right away. Because .pfn_mkwrite() is
> + * not invoked, record the write afterwards.
> + */
> static vm_fault_t try_insert_pfn(struct vm_fault *vmf, unsigned int order,
> unsigned long pfn)
> {
> + bool write = vmf->flags & FAULT_FLAG_WRITE;
> + vm_fault_t ret = VM_FAULT_FALLBACK;
> +
> if (!order) {
> - return vmf_insert_pfn(vmf->vma, vmf->address, pfn);
> + ret = vmf_insert_pfn_mkwrite(vmf->vma, vmf->address, pfn, write);
> #ifdef CONFIG_ARCH_SUPPORTS_PMD_PFNMAP
> } else if (order == PMD_ORDER) {
> unsigned long paddr = pfn << PAGE_SHIFT;
> @@ -601,27 +615,15 @@ static vm_fault_t try_insert_pfn(struct vm_fault *vmf, unsigned int order,
>
> if (aligned &&
> folio_test_pmd_mappable(page_folio(pfn_to_page(pfn)))) {
> - vm_fault_t ret;
> -
> pfn &= PMD_MASK >> PAGE_SHIFT;
> -
> - /* Unlike PTEs which are automatically upgraded to
> - * writeable entries, the PMD upgrades go through
> - * .huge_fault(). Make sure we pass the "write" info
> - * along in that case.
> - * This also means we have to record the write fault
> - * here, instead of in .pfn_mkwrite().
> - */
> - ret = vmf_insert_pfn_pmd(vmf, pfn,
> - vmf->flags & FAULT_FLAG_WRITE);
> - if (ret == VM_FAULT_NOPAGE && (vmf->flags & FAULT_FLAG_WRITE))
> - drm_gem_shmem_record_mkwrite(vmf);
> -
> - return ret;
> + ret = vmf_insert_pfn_pmd(vmf, pfn, write);
[Severity: Critical]
This is a pre-existing issue, but does the fault handler verify that a
PMD-sized huge page fits entirely within the bounds of the VMA?
The core __thp_vma_allowable_orders() skips VMA boundary checks by
relying on the driver's .huge_fault handler to enforce them. However,
drm_gem_shmem_any_fault() does not appear to check if
vmf->address + PMD_SIZE <= vma->vm_end before delegating to
vmf_insert_pfn_pmd() via try_insert_pfn().
Could an unprivileged user create a 4KB VMA backed by a PMD-aligned GEM
object, and fault it to install a 2MB PMD that overwrites adjacent VMA
page tables?
> }
> #endif
> }
> - return VM_FAULT_FALLBACK;
> +
> + if (ret == VM_FAULT_NOPAGE && write)
> + drm_gem_shmem_record_mkwrite(vmf);
> + return ret;
> }
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260804120529.1730187-1-pbonzini@redhat.com?part=2
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v2 4/6] kvm: apply VM_READ/VM_WRITE checks to all VMA types
2026-08-04 12:23 ` sashiko-bot
@ 2026-08-04 12:44 ` Paolo Bonzini
0 siblings, 0 replies; 24+ messages in thread
From: Paolo Bonzini @ 2026-08-04 12:44 UTC (permalink / raw)
To: sashiko-reviews
Cc: Vasily Gorbik, Heiko Carstens, kvm, linux-s390, Alexander Gordeev,
dri-devel
On Tue, Aug 4, 2026 at 2:23 PM <sashiko-bot@kernel.org> wrote:
>
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
> - [High] Write faults on write-only `VM_IO | VM_PFNMAP` VMAs are incorrectly rejected, breaking guest writes to write-only device memory.
>
> If a guest performs a memory write to a guest physical address backed by a
> write-only host VMA (e.g. VM_IO or VM_PFNMAP mapped with PROT_WRITE but
> without PROT_READ), get_user_pages() will fail and fall back to this lookup.
>
> Since the write-only VMA lacks VM_READ, this check will evaluate to true and
> return KVM_PFN_ERR_FAULT, skipping the VM_IO | VM_PFNMAP handler. Could this
> prevent guests from writing to legitimate write-only device memory, such as
> command ring buffers or write-only PCI BARs?
This is theoretical only but actually a bugfix, because KVM does not
support write-only mappings and therefore it would not be possible to
enforce !PROT_READ/PROT_WRITE.
Paolo
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v2 3/6] drm/ttm, drm/vmwgfx: directly create writable PTEs when mkwrite is in use
2026-08-04 12:21 ` sashiko-bot
@ 2026-08-04 12:47 ` Paolo Bonzini
0 siblings, 0 replies; 24+ messages in thread
From: Paolo Bonzini @ 2026-08-04 12:47 UTC (permalink / raw)
To: sashiko-reviews
Cc: Heiko Carstens, linux-s390, Alexander Gordeev, Vasily Gorbik,
dri-devel, kvm
On Tue, Aug 4, 2026 at 2:21 PM <sashiko-bot@kernel.org> wrote:
>
> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
>
> New issues:
> - [High] Missing dirty page tracking in vmw_bo_vm_fault() when prefaulting fails, leading to silent data loss.
>
> Pre-existing issues:
> - [High] Inverted error handling logic in ttm_bo_vm_fault_reserved() hides primary fault errors and propagates prefault errors.
Fixing the preexisting bug avoids the other issue - I sent a patch
separately at https://lists.freedesktop.org/archives/dri-devel/2026-August/586696.html.
Paolo
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v2 2/6] drm/shmem_helper: use vmf_insert_pfn_mkwrite()
2026-08-04 12:05 ` [PATCH v2 2/6] drm/shmem_helper: use vmf_insert_pfn_mkwrite() Paolo Bonzini
2026-08-04 12:30 ` sashiko-bot
@ 2026-08-04 14:15 ` Boris Brezillon
2026-08-04 14:18 ` Boris Brezillon
1 sibling, 1 reply; 24+ messages in thread
From: Boris Brezillon @ 2026-08-04 14:15 UTC (permalink / raw)
To: Paolo Bonzini
Cc: linux-kernel, kvm, Alex Williamson, bcm-kernel-feedback-list,
Christian Koenig, David Hildenbrand, dri-devel, Fei Li, Huang Rui,
linux-mm, linux-s390, Michal Hocko, Peter Xu, Sergio Lopez,
Sean Christopherson, Thomas Zimmermann, stable
On Tue, 4 Aug 2026 14:05:24 +0200
Paolo Bonzini <pbonzini@redhat.com> wrote:
> This ensures that KVM or VFIO correctly see a writable PTE when
> they request one. Otherwise, a guest write to an unpopulated
> PTE from a mapping backed by a DRM GEM BO triggers a VM exit
> with EFAULT.
>
> The code actually is simpler, because the same logic already
> applied to the hugepage mapping case using vmf_insert_pfn_pmd().
>
> Reported-by: Sergio Lopez <slp@redhat.com>
> Link: https://lore.kernel.org/kvm/20260729072044.25796-1-slp@redhat.com/
> Tested-by: Sergio Lopez <slp@redhat.com>
> Reviewed-by: Boris Brezillon <boris.brezillon@collabora.com>
> Fixes: 28e3918179aa ("drm/gem-shmem: Track folio accessed/dirty status in mmap")
> Cc: stable@vger.kernel.org
> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
> ---
> drivers/gpu/drm/drm_gem_shmem_helper.c | 38 ++++++++++++++------------
> 1 file changed, 20 insertions(+), 18 deletions(-)
>
> diff --git a/drivers/gpu/drm/drm_gem_shmem_helper.c b/drivers/gpu/drm/drm_gem_shmem_helper.c
> index c989459eb215..c81be3e97317 100644
> --- a/drivers/gpu/drm/drm_gem_shmem_helper.c
> +++ b/drivers/gpu/drm/drm_gem_shmem_helper.c
> @@ -589,11 +589,25 @@ static void drm_gem_shmem_record_mkwrite(struct vm_fault *vmf)
> folio_mark_dirty(page_folio(shmem->pages[page_offset]));
> }
>
> +/*
> + * Because the vm_ops have a .pfn_mkwrite() callback, vma_set_page_prot()
> + * has cleared the write bit from vma->vm_page_prot. vmf_insert_pfn()
> + * would install a read-only entry even for a write fault, relying on a
> + * second fault to reach .pfn_mkwrite() and upgrade it, but that second
> + * fault never happens for fixup_user_fault() callers that directly
> + * walk the page tables with follow_pfnmap_start(). To ensure that
> + * they don't see the read-only entry, pass FAULT_FLAG_WRITE info down
> + * to install a writable entry right away. Because .pfn_mkwrite() is
> + * not invoked, record the write afterwards.
> + */
> static vm_fault_t try_insert_pfn(struct vm_fault *vmf, unsigned int order,
> unsigned long pfn)
> {
> + bool write = vmf->flags & FAULT_FLAG_WRITE;
> + vm_fault_t ret = VM_FAULT_FALLBACK;
> +
> if (!order) {
> - return vmf_insert_pfn(vmf->vma, vmf->address, pfn);
> + ret = vmf_insert_pfn_mkwrite(vmf->vma, vmf->address, pfn, write);
> #ifdef CONFIG_ARCH_SUPPORTS_PMD_PFNMAP
> } else if (order == PMD_ORDER) {
> unsigned long paddr = pfn << PAGE_SHIFT;
> @@ -601,27 +615,15 @@ static vm_fault_t try_insert_pfn(struct vm_fault *vmf, unsigned int order,
>
> if (aligned &&
> folio_test_pmd_mappable(page_folio(pfn_to_page(pfn)))) {
> - vm_fault_t ret;
> -
> pfn &= PMD_MASK >> PAGE_SHIFT;
> -
> - /* Unlike PTEs which are automatically upgraded to
> - * writeable entries, the PMD upgrades go through
> - * .huge_fault(). Make sure we pass the "write" info
> - * along in that case.
> - * This also means we have to record the write fault
> - * here, instead of in .pfn_mkwrite().
> - */
> - ret = vmf_insert_pfn_pmd(vmf, pfn,
> - vmf->flags & FAULT_FLAG_WRITE);
> - if (ret == VM_FAULT_NOPAGE && (vmf->flags & FAULT_FLAG_WRITE))
> - drm_gem_shmem_record_mkwrite(vmf);
> -
> - return ret;
> + ret = vmf_insert_pfn_pmd(vmf, pfn, write);
> }
> #endif
> }
> - return VM_FAULT_FALLBACK;
> +
> + if (ret == VM_FAULT_NOPAGE && write)
> + drm_gem_shmem_record_mkwrite(vmf);
Actually, if we're making the drm_gem_shmem_record_mkwrite() call
unconditional (for PTE and PMD updates) in that path, can't we drop the
drm_gem_shmem_pfn_mkwrite() call living in drm_gem_shmem_pfn_mkwrite()?
Also, I'm not even sure we can end up with write=true for PTE updates,
because our pfn_mkwrite implementation returns zero, not VM_FAULT_ERROR
or VM_FAULT_NOPAGE. This means the default RO -> RW PTE upgrade
implemented in finish_mkwrite_fault() [1] will take place. If we really
want out try_insert_pfn() to be called for those RO -> RW updgrades, we
need to call try_insert_pfn() from drm_gem_shmem_pfn_mkwrite().
[1]https://elixir.bootlin.com/linux/v7.2-rc5/source/mm/memory.c#L4021
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v2 2/6] drm/shmem_helper: use vmf_insert_pfn_mkwrite()
2026-08-04 14:15 ` Boris Brezillon
@ 2026-08-04 14:18 ` Boris Brezillon
2026-08-04 14:34 ` Paolo Bonzini
0 siblings, 1 reply; 24+ messages in thread
From: Boris Brezillon @ 2026-08-04 14:18 UTC (permalink / raw)
To: Paolo Bonzini
Cc: linux-kernel, kvm, Alex Williamson, bcm-kernel-feedback-list,
Christian Koenig, David Hildenbrand, dri-devel, Fei Li, Huang Rui,
linux-mm, linux-s390, Michal Hocko, Peter Xu, Sergio Lopez,
Sean Christopherson, Thomas Zimmermann, stable
On Tue, 4 Aug 2026 16:15:49 +0200
Boris Brezillon <boris.brezillon@collabora.com> wrote:
> On Tue, 4 Aug 2026 14:05:24 +0200
> Paolo Bonzini <pbonzini@redhat.com> wrote:
>
> > This ensures that KVM or VFIO correctly see a writable PTE when
> > they request one. Otherwise, a guest write to an unpopulated
> > PTE from a mapping backed by a DRM GEM BO triggers a VM exit
> > with EFAULT.
> >
> > The code actually is simpler, because the same logic already
> > applied to the hugepage mapping case using vmf_insert_pfn_pmd().
> >
> > Reported-by: Sergio Lopez <slp@redhat.com>
> > Link: https://lore.kernel.org/kvm/20260729072044.25796-1-slp@redhat.com/
> > Tested-by: Sergio Lopez <slp@redhat.com>
> > Reviewed-by: Boris Brezillon <boris.brezillon@collabora.com>
> > Fixes: 28e3918179aa ("drm/gem-shmem: Track folio accessed/dirty status in mmap")
> > Cc: stable@vger.kernel.org
> > Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
> > ---
> > drivers/gpu/drm/drm_gem_shmem_helper.c | 38 ++++++++++++++------------
> > 1 file changed, 20 insertions(+), 18 deletions(-)
> >
> > diff --git a/drivers/gpu/drm/drm_gem_shmem_helper.c b/drivers/gpu/drm/drm_gem_shmem_helper.c
> > index c989459eb215..c81be3e97317 100644
> > --- a/drivers/gpu/drm/drm_gem_shmem_helper.c
> > +++ b/drivers/gpu/drm/drm_gem_shmem_helper.c
> > @@ -589,11 +589,25 @@ static void drm_gem_shmem_record_mkwrite(struct vm_fault *vmf)
> > folio_mark_dirty(page_folio(shmem->pages[page_offset]));
> > }
> >
> > +/*
> > + * Because the vm_ops have a .pfn_mkwrite() callback, vma_set_page_prot()
> > + * has cleared the write bit from vma->vm_page_prot. vmf_insert_pfn()
> > + * would install a read-only entry even for a write fault, relying on a
> > + * second fault to reach .pfn_mkwrite() and upgrade it, but that second
> > + * fault never happens for fixup_user_fault() callers that directly
> > + * walk the page tables with follow_pfnmap_start(). To ensure that
> > + * they don't see the read-only entry, pass FAULT_FLAG_WRITE info down
> > + * to install a writable entry right away. Because .pfn_mkwrite() is
> > + * not invoked, record the write afterwards.
> > + */
> > static vm_fault_t try_insert_pfn(struct vm_fault *vmf, unsigned int order,
> > unsigned long pfn)
> > {
> > + bool write = vmf->flags & FAULT_FLAG_WRITE;
> > + vm_fault_t ret = VM_FAULT_FALLBACK;
> > +
> > if (!order) {
> > - return vmf_insert_pfn(vmf->vma, vmf->address, pfn);
> > + ret = vmf_insert_pfn_mkwrite(vmf->vma, vmf->address, pfn, write);
> > #ifdef CONFIG_ARCH_SUPPORTS_PMD_PFNMAP
> > } else if (order == PMD_ORDER) {
> > unsigned long paddr = pfn << PAGE_SHIFT;
> > @@ -601,27 +615,15 @@ static vm_fault_t try_insert_pfn(struct vm_fault *vmf, unsigned int order,
> >
> > if (aligned &&
> > folio_test_pmd_mappable(page_folio(pfn_to_page(pfn)))) {
> > - vm_fault_t ret;
> > -
> > pfn &= PMD_MASK >> PAGE_SHIFT;
> > -
> > - /* Unlike PTEs which are automatically upgraded to
> > - * writeable entries, the PMD upgrades go through
> > - * .huge_fault(). Make sure we pass the "write" info
> > - * along in that case.
> > - * This also means we have to record the write fault
> > - * here, instead of in .pfn_mkwrite().
> > - */
> > - ret = vmf_insert_pfn_pmd(vmf, pfn,
> > - vmf->flags & FAULT_FLAG_WRITE);
> > - if (ret == VM_FAULT_NOPAGE && (vmf->flags & FAULT_FLAG_WRITE))
> > - drm_gem_shmem_record_mkwrite(vmf);
> > -
> > - return ret;
> > + ret = vmf_insert_pfn_pmd(vmf, pfn, write);
> > }
> > #endif
> > }
> > - return VM_FAULT_FALLBACK;
> > +
> > + if (ret == VM_FAULT_NOPAGE && write)
> > + drm_gem_shmem_record_mkwrite(vmf);
>
> Actually, if we're making the drm_gem_shmem_record_mkwrite() call
> unconditional (for PTE and PMD updates) in that path, can't we drop the
> drm_gem_shmem_pfn_mkwrite() call living in drm_gem_shmem_pfn_mkwrite()?
>
> Also, I'm not even sure we can end up with write=true for PTE updates,
> because our pfn_mkwrite implementation returns zero, not VM_FAULT_ERROR
> or VM_FAULT_NOPAGE. This means the default RO -> RW PTE upgrade
> implemented in finish_mkwrite_fault() [1] will take place. If we really
> want out try_insert_pfn() to be called for those RO -> RW updgrades, we
> need to call try_insert_pfn() from drm_gem_shmem_pfn_mkwrite().
Nevermind, it's all explained in the comment you've added. Sorry for
the noise. I keep wondering if we shouldn't call try_insert_pfn() from
pfn_mkwrite() though, like is done in other places.
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v2 2/6] drm/shmem_helper: use vmf_insert_pfn_mkwrite()
2026-08-04 14:18 ` Boris Brezillon
@ 2026-08-04 14:34 ` Paolo Bonzini
2026-08-04 14:42 ` Boris Brezillon
0 siblings, 1 reply; 24+ messages in thread
From: Paolo Bonzini @ 2026-08-04 14:34 UTC (permalink / raw)
To: Boris Brezillon
Cc: linux-kernel, kvm, Alex Williamson, bcm-kernel-feedback-list,
Christian Koenig, David Hildenbrand, dri-devel, Fei Li, Huang Rui,
linux-mm, linux-s390, Michal Hocko, Peter Xu, Sergio Lopez,
Sean Christopherson, Thomas Zimmermann, stable
On Tue, Aug 4, 2026 at 4:19 PM Boris Brezillon
<boris.brezillon@collabora.com> wrote:
> On Tue, 4 Aug 2026 16:15:49 +0200
> Boris Brezillon <boris.brezillon@collabora.com> wrote:
> > Actually, if we're making the drm_gem_shmem_record_mkwrite() call
> > unconditional (for PTE and PMD updates) in that path, can't we drop the
> > drm_gem_shmem_pfn_mkwrite() call living in drm_gem_shmem_pfn_mkwrite()?
> >
> > Also, I'm not even sure we can end up with write=true for PTE updates,
> > because our pfn_mkwrite implementation returns zero, not VM_FAULT_ERROR
> > or VM_FAULT_NOPAGE. This means the default RO -> RW PTE upgrade
> > implemented in finish_mkwrite_fault() [1] will take place. If we really
> > want out try_insert_pfn() to be called for those RO -> RW updgrades, we
> > need to call try_insert_pfn() from drm_gem_shmem_pfn_mkwrite().
>
> Nevermind, it's all explained in the comment you've added. Sorry for
> the noise. I keep wondering if we shouldn't call try_insert_pfn() from
> pfn_mkwrite() though, like is done in other places.
No, the .pfn_mkwrite() callback is invoked when a PTE already exists,
and mm/ already takes care of making it writable. So there's nothing
to insert, you just have to take note which you do with
drm_gem_shmem_record_mkwrite().
Paolo
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v2 2/6] drm/shmem_helper: use vmf_insert_pfn_mkwrite()
2026-08-04 14:34 ` Paolo Bonzini
@ 2026-08-04 14:42 ` Boris Brezillon
2026-08-05 6:08 ` Paolo Bonzini
0 siblings, 1 reply; 24+ messages in thread
From: Boris Brezillon @ 2026-08-04 14:42 UTC (permalink / raw)
To: Paolo Bonzini
Cc: linux-kernel, kvm, Alex Williamson, bcm-kernel-feedback-list,
Christian Koenig, David Hildenbrand, dri-devel, Fei Li, Huang Rui,
linux-mm, linux-s390, Michal Hocko, Peter Xu, Sergio Lopez,
Sean Christopherson, Thomas Zimmermann, stable
On Tue, 4 Aug 2026 16:34:10 +0200
Paolo Bonzini <pbonzini@redhat.com> wrote:
> On Tue, Aug 4, 2026 at 4:19 PM Boris Brezillon
> <boris.brezillon@collabora.com> wrote:
> > On Tue, 4 Aug 2026 16:15:49 +0200
> > Boris Brezillon <boris.brezillon@collabora.com> wrote:
> > > Actually, if we're making the drm_gem_shmem_record_mkwrite() call
> > > unconditional (for PTE and PMD updates) in that path, can't we drop the
> > > drm_gem_shmem_pfn_mkwrite() call living in drm_gem_shmem_pfn_mkwrite()?
> > >
> > > Also, I'm not even sure we can end up with write=true for PTE updates,
> > > because our pfn_mkwrite implementation returns zero, not VM_FAULT_ERROR
> > > or VM_FAULT_NOPAGE. This means the default RO -> RW PTE upgrade
> > > implemented in finish_mkwrite_fault() [1] will take place. If we really
> > > want out try_insert_pfn() to be called for those RO -> RW updgrades, we
> > > need to call try_insert_pfn() from drm_gem_shmem_pfn_mkwrite().
> >
> > Nevermind, it's all explained in the comment you've added. Sorry for
> > the noise. I keep wondering if we shouldn't call try_insert_pfn() from
> > pfn_mkwrite() though, like is done in other places.
>
> No, the .pfn_mkwrite() callback is invoked when a PTE already exists,
> and mm/ already takes care of making it writable. So there's nothing
> to insert, you just have to take note which you do with
> drm_gem_shmem_record_mkwrite().
Okay, I thought I'd ask to be sure, because of all the implementations
of pfn_mkwrite listed here [1], only drm_gem_shmem_helper.c and
kernel/events/core.c do that. The rest have their "generic" fault
handler (by generic I mean a fault handler helper that covers all the
order/WRITE_FLAG combinations) called from pfn_mkwrite(), and return a
non-zero vm_fault_t.
[1]https://elixir.bootlin.com/linux/v7.2-rc5/A/ident/pfn_mkwrite
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v2 6/6] kvm: return -EFAULT for writes to !VM_WRITE IO mappings
2026-08-04 12:05 ` [PATCH v2 6/6] kvm: return -EFAULT for writes to !VM_WRITE IO mappings Paolo Bonzini
2026-08-04 12:14 ` sashiko-bot
@ 2026-08-04 21:08 ` Sean Christopherson
1 sibling, 0 replies; 24+ messages in thread
From: Sean Christopherson @ 2026-08-04 21:08 UTC (permalink / raw)
To: Paolo Bonzini
Cc: linux-kernel, kvm, Alex Williamson, bcm-kernel-feedback-list,
Boris Brezillon, Christian Koenig, David Hildenbrand, dri-devel,
Fei Li, Huang Rui, linux-mm, linux-s390, Michal Hocko, Peter Xu,
Sergio Lopez, Thomas Zimmermann
I strongly prefer "KVM:" for the scope, and it's waaay more common.
$ git log --pretty=oneline --after 2015.01.01 | grep "kvm:" | wc -l
1298
$ git log --pretty=oneline --after 2015.01.01 | grep "KVM:" | wc -l
16249
On Tue, Aug 04, 2026, Paolo Bonzini wrote:
> KVM's behavior when the guest writes to a non-writable VMA is inconsistent.
> For regular, page-backed mappings it returns KVM_PFN_ERR_FAULT and thus
> returns -EFAULT to userspace (which is ABI, and relied upon by tests);
> for VM_IO/VM_PFNMAP mappings instead it returns KVM_PFN_ERR_RO_FAULT
> and thus exits to userspace with KVM_EXIT_MMIO.
>
> This behavior for VM_{IO,PFNMAP} was added by commit bd2fae8da794 ("KVM:
> do not assume PTE is writable after follow_pfn"), and even if it has been
> in place for five years it is unlikely that it is relied upon by userspace,
> since it is inconsistent with KVM itself. Change hva_to_pfn() to return
> KVM_PFN_ERR_FAULT for all non-writable VMAs, and restrict KVM_EXIT_MMIO
> to the case of an explicitly read-only memslot.
>
> Suggested-by: Sean Christopherson <seanjc@google.com>
> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
> ---
Shortlog nit aside,
Reviewed-by: Sean Christopherson <seanjc@google.com>
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v2 4/6] kvm: apply VM_READ/VM_WRITE checks to all VMA types
2026-08-04 12:05 ` [PATCH v2 4/6] kvm: apply VM_READ/VM_WRITE checks to all VMA types Paolo Bonzini
2026-08-04 12:23 ` sashiko-bot
@ 2026-08-04 21:15 ` Sean Christopherson
1 sibling, 0 replies; 24+ messages in thread
From: Sean Christopherson @ 2026-08-04 21:15 UTC (permalink / raw)
To: Paolo Bonzini
Cc: linux-kernel, kvm, Alex Williamson, bcm-kernel-feedback-list,
Boris Brezillon, Christian Koenig, David Hildenbrand, dri-devel,
Fei Li, Huang Rui, linux-mm, linux-s390, Michal Hocko, Peter Xu,
Sergio Lopez, Thomas Zimmermann, stable
KVM:
On Tue, Aug 04, 2026, Paolo Bonzini wrote:
> The VM_READ and VM_WRITE flags are checked only at the very end of
> hva_to_pfn(). For both the hva_to_pfn_remapped() case and for regular
> mappings, this adds unnecessary cases and inconsistent error behavior.
>
> For hva_to_pfn_remapped(), the code is relying on fixup_user_fault() to
> detect this situation. This is fragile because hva_to_pfn_remapped()
> returns different error codes for a !VM_WRITE VMA depending on whether
> the PTE happens to be mapped:
>
> * if the PTE is present, follow_pfnmap_start() sets args.writable to
> false and KVM_PFN_ERR_RO_FAULT is returned;
>
> * if no PTE is present, fixup_user_fault(FAULT_FLAG_WRITE) returns
> -EFAULT after checking vma_permits_fault(), and hva_to_pfn() ends
> up returning KVM_PFN_ERR_FAULT.
>
> With this patch KVM_PFN_ERR_RO_FAULT is returned uniformly. Likewise,
> a PROT_NONE pfnmap VMA would be mapped into the guest if the PTE was
> pte_present()[1] when the guest attempted to read it; with the patch
> instead KVM uniformly returns KVM_PFN_ERR_FAULT. Doing the check early
> avoids these special cases and also sidesteps the issue pointed out at
> https://sashiko.dev/#/patchset/20260731160514.1101989-1-pbonzini%40redhat.com.
>
> For regular mappings a PROT_READ VMA, if placed in a writable memslot,
> would return KVM_PFN_ERR_FAULT instead of KVM_PFN_ERR_RO_FAULT when
> the guest writes to it. This would cause a -EFAULT exit to userspace,
> instead of triggering emulation as the VM_IO|VM_PFNMAP arm would do;
> however it should be considered part of the KVM API because mmu_stress_test
> relies on it.
>
> Still, even with this snag about the returned pfn error code, pull the
> vm_flags checks in front so that they are done for all VMAs and the
> above inconsistency goes away for the VM_IO|VM_PFNMAP case.
>
> [1] on x86, for example, such a page would have _PAGE_PRESENT clear
> but _PAGE_PROTNONE set
>
> Fixes: 28e3918179aa ("drm/gem-shmem: Track folio accessed/dirty status in mmap")
> Cc: stable@vger.kernel.org
> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
> ---
> virt/kvm/kvm_main.c | 34 ++++++++++++++++------------------
> 1 file changed, 16 insertions(+), 18 deletions(-)
>
> diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c
> index 45e784462ec6..576bcb21be3a 100644
> --- a/virt/kvm/kvm_main.c
> +++ b/virt/kvm/kvm_main.c
> @@ -2925,17 +2925,6 @@ static int hva_to_pfn_slow(struct kvm_follow_pfn *kfp, kvm_pfn_t *pfn)
> return npages;
> }
>
> -static bool vma_is_valid(struct vm_area_struct *vma, bool write_fault)
> -{
> - if (unlikely(!(vma->vm_flags & VM_READ)))
> - return false;
> -
> - if (write_fault && (unlikely(!(vma->vm_flags & VM_WRITE))))
> - return false;
> -
> - return true;
> -}
> -
> static int hva_to_pfn_remapped(struct vm_area_struct *vma,
> struct kvm_follow_pfn *kfp, kvm_pfn_t *p_pfn)
> {
> @@ -3008,20 +2997,29 @@ kvm_pfn_t hva_to_pfn(struct kvm_follow_pfn *kfp)
> retry:
> vma = vma_lookup(current->mm, kfp->hva);
>
> - if (vma == NULL)
> + /*
> + * GUP failed. It could be an inaccessible mapping, a pfnmap one,
> + * or the page might be absent.
> + */
> +
Unnecessary newline, IMO.
> + if (vma == NULL || unlikely(!(vma->vm_flags & VM_READ))) {
> pfn = KVM_PFN_ERR_FAULT;
> - else if (vma->vm_flags & (VM_IO | VM_PFNMAP)) {
> + } else if ((kfp->flags & FOLL_WRITE) && unlikely(!(vma->vm_flags & VM_WRITE))) {
> + /*
> + * Exit to userspace for PROT_READ mappings in a writable
> + * memslot, as this is part of the API.
Can we say something along the lines of "for backwards compatibility" instead
of saying this is part of the API? Because that's definitely not KVM's documented
API, and we're hoping it's not part of KVM's undocumented API either.
> + */
> + pfn = vma->vm_flags & (VM_IO | VM_PFNMAP) ? KVM_PFN_ERR_RO_FAULT :
> + KVM_PFN_ERR_FAULT;
Please align the two branches of the ternary operators:
pfn = vma->vm_flags & (VM_IO | VM_PFNMAP) ? KVM_PFN_ERR_RO_FAULT :
KVM_PFN_ERR_FAULT;
} else if (vma->vm_flags & (VM_IO | VM_PFNMAP)) {
r = hva_to_pfn_remapped(vma, kfp, &pfn);
if (r == -EAGAIN)
goto retry;
if (r < 0)
pfn = KVM_PFN_ERR_FAULT;
} else {
pfn = kfp->flags & FOLL_NOWAIT ? KVM_PFN_ERR_NEEDS_IO :
KVM_PFN_ERR_FAULT;
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v2 2/6] drm/shmem_helper: use vmf_insert_pfn_mkwrite()
2026-08-04 14:42 ` Boris Brezillon
@ 2026-08-05 6:08 ` Paolo Bonzini
2026-08-05 8:34 ` Boris Brezillon
0 siblings, 1 reply; 24+ messages in thread
From: Paolo Bonzini @ 2026-08-05 6:08 UTC (permalink / raw)
To: Boris Brezillon
Cc: linux-kernel, kvm, Alex Williamson, bcm-kernel-feedback-list,
Christian Koenig, David Hildenbrand, dri-devel, Fei Li, Huang Rui,
linux-mm, linux-s390, Michal Hocko, Peter Xu, Sergio Lopez,
Sean Christopherson, Thomas Zimmermann, stable
On Tue, Aug 4, 2026 at 4:42 PM Boris Brezillon
<boris.brezillon@collabora.com> wrote:
> Okay, I thought I'd ask to be sure, because of all the implementations
> of pfn_mkwrite listed here [1], only drm_gem_shmem_helper.c and
> kernel/events/core.c do that.
Also the vmwgfx one in patch 3; and for the !DAX case ext4 and XFS too
(e.g. xfs_filemap_pfn_mkwrite gets to iomap_page_mkwrite, not to
filemap_fault).
> The rest have their "generic" fault
> handler (by generic I mean a fault handler helper that covers all the
> order/WRITE_FLAG combinations) called from pfn_mkwrite(), and return a
> non-zero vm_fault_t.
I see; I think you can do that, it is handled at
https://elixir.bootlin.com/linux/v7.2-rc5/source/mm/memory.c#L2681 and
indeed it returns VM_FAULT_NOPAGE. But it's more or work for no real
reason. Your .pfn_mkwrite is a single line of code and it is clearer
IMO if you can see that try_insert_pfn() is calling the same helper as
.pfn_mkwrite().
Paolo
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v2 2/6] drm/shmem_helper: use vmf_insert_pfn_mkwrite()
2026-08-05 6:08 ` Paolo Bonzini
@ 2026-08-05 8:34 ` Boris Brezillon
0 siblings, 0 replies; 24+ messages in thread
From: Boris Brezillon @ 2026-08-05 8:34 UTC (permalink / raw)
To: Paolo Bonzini
Cc: linux-kernel, kvm, Alex Williamson, bcm-kernel-feedback-list,
Christian Koenig, David Hildenbrand, dri-devel, Fei Li, Huang Rui,
linux-mm, linux-s390, Michal Hocko, Peter Xu, Sergio Lopez,
Sean Christopherson, Thomas Zimmermann, stable
On Wed, 5 Aug 2026 08:08:17 +0200
Paolo Bonzini <pbonzini@redhat.com> wrote:
> On Tue, Aug 4, 2026 at 4:42 PM Boris Brezillon
> <boris.brezillon@collabora.com> wrote:
> > Okay, I thought I'd ask to be sure, because of all the implementations
> > of pfn_mkwrite listed here [1], only drm_gem_shmem_helper.c and
> > kernel/events/core.c do that.
>
> Also the vmwgfx one in patch 3; and for the !DAX case ext4 and XFS too
> (e.g. xfs_filemap_pfn_mkwrite gets to iomap_page_mkwrite, not to
> filemap_fault).
Right, iomap_page_mkwrite() doesn't seem to update the PTE entry, it
just records the access. But there's quite a bit of locking taking place
before this recording is done, which we don't do in gem_shmem's
pkf_mkwrite implementation. This brings me back to some question I asked
in another thread where we were discussing another regression
introduced by pfn_mkwrite addition to gem_shmem [1]: do we need to have
the folio locked when recording the mkwrite? If we do, we probably need
to surround the drm_gem_shmem_record_mkwrite() call in
drm_gem_shmem_pfn_mkwrite() with a folio_lock/unlock() sequence.
For the record, the folio_mark_dirty() doc says:
* The folio may not be truncated while this function is running.
* Holding the folio lock is sufficient to prevent truncation, but some
* callers cannot acquire a sleeping lock. These callers instead hold
* the page table lock for a page table which contains at least one page
* in this folio. Truncation will block on the page table lock as it
* unmaps pages before removing the folio from its mapping.
I'm really sorry to hijack this thread like that, but now that I have
people with a bit more MM knowledge looking at this stuff, I'm taking
the opportunity to ask all the questions that were left unanswered back
then :-/.
>
> > The rest have their "generic" fault
> > handler (by generic I mean a fault handler helper that covers all the
> > order/WRITE_FLAG combinations) called from pfn_mkwrite(), and return a
> > non-zero vm_fault_t.
>
> I see; I think you can do that, it is handled at
> https://elixir.bootlin.com/linux/v7.2-rc5/source/mm/memory.c#L2681 and
> indeed it returns VM_FAULT_NOPAGE. But it's more or work for no real
> reason. Your .pfn_mkwrite is a single line of code and it is clearer
> IMO if you can see that try_insert_pfn() is calling the same helper as
> .pfn_mkwrite().
Sure. As long as what we're doing is safe, I'm fine keeping
drm_gem_shmem_pfn_mkwrite() as is and letting the core update the PTE
props. It's just that, after reading all these implementations, I was
skeptical (see the question around folio locking before mark_dirty(),
for instance).
Anyway, thanks for chiming in.
[1]https://lore.kernel.org/dri-devel/20260313111851.4c1f89f3@fedora/
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v2 3/6] drm/ttm, drm/vmwgfx: directly create writable PTEs when mkwrite is in use
2026-08-04 12:05 ` [PATCH v2 3/6] drm/ttm, drm/vmwgfx: directly create writable PTEs when mkwrite is in use Paolo Bonzini
2026-08-04 12:21 ` sashiko-bot
@ 2026-08-06 23:32 ` Peter Xu
1 sibling, 0 replies; 24+ messages in thread
From: Peter Xu @ 2026-08-06 23:32 UTC (permalink / raw)
To: Paolo Bonzini
Cc: linux-kernel, kvm, Alex Williamson, bcm-kernel-feedback-list,
Boris Brezillon, Christian Koenig, David Hildenbrand, dri-devel,
Fei Li, Huang Rui, linux-mm, linux-s390, Michal Hocko,
Sergio Lopez, Sean Christopherson, Thomas Zimmermann, stable
On Tue, Aug 04, 2026 at 02:05:25PM +0200, Paolo Bonzini wrote:
> This ensures that fixup_user_fault() users see a writable PTE when
> they request one. The flip side is that vmw_bo_vm_fault() now has
> to record by hand the write fault, because .pfn_mkwrite() is
> not invoked.
>
> Prefaulting works as before because only the first entry comes
> out writable, while the following ones still end up executing
> the .pfn_mkwrite() callback.
>
> Cc: stable@vger.kernel.org
> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
Only some quick thoughts while reading through this, as below.. even if
some of it may make sense, I think that may be more suitable as follow up.
This looks like a good fix for a regression already to me.
> ---
> drivers/gpu/drm/ttm/ttm_bo_vm.c | 7 ++--
> drivers/gpu/drm/vmwgfx/vmwgfx_page_dirty.c | 42 ++++++++++++----------
> 2 files changed, 29 insertions(+), 20 deletions(-)
>
> diff --git a/drivers/gpu/drm/ttm/ttm_bo_vm.c b/drivers/gpu/drm/ttm/ttm_bo_vm.c
> index a80510489c45..3ebde936ce60 100644
> --- a/drivers/gpu/drm/ttm/ttm_bo_vm.c
> +++ b/drivers/gpu/drm/ttm/ttm_bo_vm.c
> @@ -191,6 +191,7 @@ vm_fault_t ttm_bo_vm_fault_reserved(struct vm_fault *vmf,
> unsigned long pfn;
> struct ttm_tt *ttm = NULL;
> struct page *page;
> + bool mkwrite;
> int err;
> pgoff_t i;
> vm_fault_t ret = VM_FAULT_NOPAGE;
> @@ -242,6 +243,7 @@ vm_fault_t ttm_bo_vm_fault_reserved(struct vm_fault *vmf,
> * Speculatively prefault a number of pages. Only error on
> * first page.
> */
> + mkwrite = !!(vmf->flags & FAULT_FLAG_WRITE);
> for (i = 0; i < num_prefault; ++i) {
> if (bo->resource->bus.is_iomem) {
> pfn = ttm_bo_io_mem_pfn(bo, page_offset);
> @@ -263,9 +265,10 @@ vm_fault_t ttm_bo_vm_fault_reserved(struct vm_fault *vmf,
> * at arbitrary times while the data is mmap'ed.
> * See vmf_insert_pfn_prot() for a discussion.
> */
> - ret = vmf_insert_pfn_prot(vma, address, pfn, prot);
> + ret = vmf_insert_pfn_prot_mkwrite(vma, address, pfn, prot, mkwrite);
>
> - /* Never error on prefaulted PTEs */
> + /* Never error on prefaulted PTEs and never map them writable */
I got confused when reading 1st time, but I got it then noticing the mark
dirty was done by the caller.
Two small things I thought about here:
- Comparing to the time before introducing pfn_mkwrite(), this will cause
previously one fault (with prefaults marking all follow up ptes writable)
to be 1 writable plus N-1 read-only. May not be the most ideal if we
consider the 2nd WP faults on the rest N-1 later as slight overheads,
- Split the "mark WRITABLE" and "mark DIRTY" in code might be slightly
error prone, especially if this is a common function used by multiple
drivers, while there's only one driver that does the "mark DIRTY".
IIUC the other idea can be, do not reset @mkwrite here but instead move the
set dirty here, invoking whatever the vma's .pfn_mkwrite() is. So that we
stick two things together; maybe slightly less error prone and less dup
code when other drivers opt-in for pfn_mkwrite().
Not sure if it's a good idea, but just to raise it in case useful. Again,
I still think this is a solid fix to the problem already.
Other than that, FWIW the whole approach looks reasonable at least to me.
I agree in the fault processing we should best resolve the fault in one
shot if possible. In this context, FAULT_FLAG_WRITE is the flag showing
that a 2nd fault is required, then IMHO it's indeed better to resolve the
fault in one go, as proposed in this series.
Another thing I came to mind that may not really be relevant to this
regression alone, but maybe matters for the future to at least keep in
mnind: I wonder if there can be races happen while fixup_user_fault() is
resolving faults, causing the 2nd pfnmap follow code to fail once more,
say, some other thread modified the pgtable again (e.g. wr-protect with
write bit removed right after set).
So maybe pfnmap lookup and fixup_user_fault() should be done in a loop
until any of them hit real errors.. if any of such race may become a real
problem some day.
Looks like low possibility that threads will mess up with PFN maps.. but
just to raise this idea.
I believe currently our mm fault handler should be working like that with
handle_mm_fault(), hence neutral with such races (it'll loop a few more
rounds until race disappear). I recall there used to have thoughts adding
some n_retry_max counts to the fault handler, but we didn't really do that,
and it runs all fine over the years.
Thanks,
--
Peter Xu
^ permalink raw reply [flat|nested] 24+ messages in thread
end of thread, other threads:[~2026-08-06 23:32 UTC | newest]
Thread overview: 24+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-04 12:05 [PATCH v2 0/6] mm, drm: fix interaction of .pfn_mkwrite() with fixup_user_fault() Paolo Bonzini
2026-08-04 12:05 ` [PATCH v2 1/6] mm: export vmf_insert_pfn_prot_mkwrite(), change variants to inline Paolo Bonzini
2026-08-04 12:20 ` sashiko-bot
2026-08-04 12:05 ` [PATCH v2 2/6] drm/shmem_helper: use vmf_insert_pfn_mkwrite() Paolo Bonzini
2026-08-04 12:30 ` sashiko-bot
2026-08-04 14:15 ` Boris Brezillon
2026-08-04 14:18 ` Boris Brezillon
2026-08-04 14:34 ` Paolo Bonzini
2026-08-04 14:42 ` Boris Brezillon
2026-08-05 6:08 ` Paolo Bonzini
2026-08-05 8:34 ` Boris Brezillon
2026-08-04 12:05 ` [PATCH v2 3/6] drm/ttm, drm/vmwgfx: directly create writable PTEs when mkwrite is in use Paolo Bonzini
2026-08-04 12:21 ` sashiko-bot
2026-08-04 12:47 ` Paolo Bonzini
2026-08-06 23:32 ` Peter Xu
2026-08-04 12:05 ` [PATCH v2 4/6] kvm: apply VM_READ/VM_WRITE checks to all VMA types Paolo Bonzini
2026-08-04 12:23 ` sashiko-bot
2026-08-04 12:44 ` Paolo Bonzini
2026-08-04 21:15 ` Sean Christopherson
2026-08-04 12:05 ` [PATCH v2 5/6] mm: pull writability check to follow_pfnmap_start() Paolo Bonzini
2026-08-04 12:14 ` sashiko-bot
2026-08-04 12:05 ` [PATCH v2 6/6] kvm: return -EFAULT for writes to !VM_WRITE IO mappings Paolo Bonzini
2026-08-04 12:14 ` sashiko-bot
2026-08-04 21:08 ` Sean Christopherson
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox