* [PATCH] mm: pull writability check to follow_pfnmap_start()
@ 2026-07-31 16:05 Paolo Bonzini
2026-07-31 16:20 ` sashiko-bot
0 siblings, 1 reply; 3+ messages in thread
From: Paolo Bonzini @ 2026-07-31 16:05 UTC (permalink / raw)
To: linux-kernel, kvm
Cc: Alex Williamson, Fei Li, David Hildenbrand, Peter Xu, linux-s390,
linux-mm, Sergio Lopez
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. Most of them return
-EFAULT if it is not, but because follow_pfnmap_start() returned success,
they do not attempt to upgrade the page from read-only to read-write
with fixup_user_fault().
Pull the check directly into follow_pfnmap_start() through another
input parameter args.write_fault; this eliminates the need to do it in
the caller and, for callers that do use fixup_user_fault(), lets it
attempt to upgrade the page.
The change in return code to -EFAULT is okay:
- s390 is the only one to have a functional change in this respect;
but EINVAL is only documented for "Invalid length argument", while
"The address in mmio_addr is invalid" should already return EFAULT
(https://www.man7.org/linux/man-pages/man2/s390_pci_mmio_read.2.html)
- for VFIO and ACRN, -EFAULT was returned already
- for KVM, -EFAULT is eaten
- for generic_access_phys() the caller is __access_remote_vm()
which does not care about the error code.
Reported-by: Sergio Lopez <slp@redhat.com>
Link: https://lore.kernel.org/kvm/CAAiTLFU1ALsDoJoKW3d9bUvv990AozAoX=bEHmfnG54qyBAHFg@mail.gmail.com/
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
Sergio, this is half of the fix for your issue. Please
test both patches together if possible.
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 | 22 ++++++------
6 files changed, 64 insertions(+), 52 deletions(-)
diff --git a/arch/s390/pci/pci_mmio.c b/arch/s390/pci/pci_mmio.c
index 51e7a28af899..526f80b8fb3b 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_fault = 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_fault = 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..9ee92820b4de 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_fault = 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..b057e78ef3f4 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_fault = !!(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 485df9c2dbdd..34c79b5fcb9b 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_fault: if true, fail with -EFAULT unless the mapping is
+ * writable
*/
struct vm_area_struct *vma;
unsigned long address;
+ bool write_fault;
/**
* Internals:
*
diff --git a/mm/memory.c b/mm/memory.c
index ff338c2abe92..40997a26846f 100644
--- a/mm/memory.c
+++ b/mm/memory.c
@@ -6786,12 +6786,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_fault)
+ return -EFAULT;
+
args->lock = lock;
args->ptep = ptep;
args->pfn = pfn_base + ((args->address & ~addr_mask) >> PAGE_SHIFT);
@@ -6799,6 +6802,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)
@@ -6820,8 +6824,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_fault 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
@@ -6844,7 +6849,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_fault 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)
{
@@ -6857,6 +6863,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);
@@ -6890,10 +6897,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);
@@ -6911,10 +6920,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);
@@ -6923,14 +6934,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);
@@ -6972,7 +6985,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_fault = !!(write & FOLL_WRITE)
+ };
retry:
if (follow_pfnmap_start(&args))
@@ -6982,9 +6999,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 45e784462ec6..0c4ff223162b 100644
--- a/virt/kvm/kvm_main.c
+++ b/virt/kvm/kvm_main.c
@@ -2939,8 +2939,11 @@ static bool vma_is_valid(struct vm_area_struct *vma, bool write_fault)
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_fault = !!(kfp->flags & FOLL_WRITE),
+ };
int r;
/*
@@ -2959,7 +2962,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 ? FAULT_FLAG_WRITE : 0),
&unlocked);
if (unlocked)
return -EAGAIN;
@@ -2967,17 +2970,16 @@ static int hva_to_pfn_remapped(struct vm_area_struct *vma,
return r;
r = follow_pfnmap_start(&args);
- if (r)
+ if (r) {
+ if (r == -EFAULT) {
+ *p_pfn = KVM_PFN_ERR_RO_FAULT;
+ return 0;
+ }
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] 3+ messages in thread
* Re: [PATCH] mm: pull writability check to follow_pfnmap_start()
2026-07-31 16:05 [PATCH] mm: pull writability check to follow_pfnmap_start() Paolo Bonzini
@ 2026-07-31 16:20 ` sashiko-bot
2026-07-31 16:41 ` Paolo Bonzini
0 siblings, 1 reply; 3+ messages in thread
From: sashiko-bot @ 2026-07-31 16:20 UTC (permalink / raw)
To: Paolo Bonzini
Cc: kvm, Heiko Carstens, Alexander Gordeev, Vasily Gorbik, linux-s390
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] KVM fails to return KVM_PFN_ERR_RO_FAULT on write faults to read-only PFNMAP VMAs, breaking MMIO emulation.
--
--- Patch [1]: [PATCH] mm: pull writability check to follow_pfnmap_start() ---
commit 22ed2320251e3d4544366aecf373c7a819e369d9
Author: Paolo Bonzini <pbonzini@redhat.com>
mm: pull writability check to follow_pfnmap_start()
This commit pulls the writability check directly into follow_pfnmap_start()
via a new write_fault parameter. This allows the function to check
permissions and return an error directly if the mapping is not writable
when a write fault is requested.
Link: https://lore.kernel.org/kvm/CAAiTLFU1ALsDoJoKW3d9bUvv990AozAoX=bEHmfnG54qyBAHFg@mail.gmail.com/
> diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c
> --- a/virt/kvm/kvm_main.c
> +++ b/virt/kvm/kvm_main.c
[ ... ]
> static int hva_to_pfn_remapped(struct vm_area_struct *vma,
> struct kvm_follow_pfn *kfp, kvm_pfn_t *p_pfn)
> {
[ ... ]
> bool unlocked = false;
> r = fixup_user_fault(current->mm, kfp->hva,
> - (write_fault ? FAULT_FLAG_WRITE : 0),
> + (args.write_fault ? FAULT_FLAG_WRITE : 0),
> &unlocked);
> if (unlocked)
> return -EAGAIN;
> if (r)
> return r;
[Severity: High]
Will this early return bypass the new KVM_PFN_ERR_RO_FAULT fallback logic
added below?
When handling a write fault on a read-only PFNMAP memory region,
args.write_fault is true, causing the first follow_pfnmap_start() to return
-EFAULT. This correctly triggers a call to fixup_user_fault() with
FAULT_FLAG_WRITE.
However, since the underlying VMA lacks VM_WRITE, fixup_user_fault() will
also return -EFAULT. The early error check here will immediately propagate
that -EFAULT back to the caller.
>
> r = follow_pfnmap_start(&args);
> - if (r)
> + if (r) {
> + if (r == -EFAULT) {
> + *p_pfn = KVM_PFN_ERR_RO_FAULT;
> + return 0;
> + }
> return r;
Could this cause the function to skip this new check entirely, preventing KVM
from properly returning KVM_PFN_ERR_RO_FAULT? If so, it seems like this
would crash the VM on guest writes to read-only PFNMAP memory regions
instead of emulating them.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260731160514.1101989-1-pbonzini@redhat.com?part=1
^ permalink raw reply [flat|nested] 3+ messages in thread
* Re: [PATCH] mm: pull writability check to follow_pfnmap_start()
2026-07-31 16:20 ` sashiko-bot
@ 2026-07-31 16:41 ` Paolo Bonzini
0 siblings, 0 replies; 3+ messages in thread
From: Paolo Bonzini @ 2026-07-31 16:41 UTC (permalink / raw)
To: sashiko-reviews
Cc: kvm, Heiko Carstens, Alexander Gordeev, Vasily Gorbik, linux-s390
On Fri, Jul 31, 2026 at 6:20 PM <sashiko-bot@kernel.org> wrote:
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
> - [High] KVM fails to return KVM_PFN_ERR_RO_FAULT on write faults to read-only PFNMAP VMAs, breaking MMIO emulation.
Let's look at the bright side, I only messed up the part that *I* maintain.
Yeah this is correct and fixed with something like:
/*
* A write to a VMA that isn't writable can never be fixed up, as
* fixup_user_fault() rejects it outright; report it without even
* trying follow_pfnmap_start().
*/
if (args.write_fault && !(vma->vm_flags & VM_WRITE)) {
*p_pfn = KVM_PFN_ERR_RO_FAULT;
return 0;
}
before the first
r = follow_pfnmap_start(&args);
Paolo
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-07-31 16:41 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-31 16:05 [PATCH] mm: pull writability check to follow_pfnmap_start() Paolo Bonzini
2026-07-31 16:20 ` sashiko-bot
2026-07-31 16:41 ` Paolo Bonzini
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox