mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH v2] KVM: coalesced_mmio: reject zone sizes that don't fit in an int
       [not found] <1792859391.770388.v1@qq.com>
@ 2026-10-28  1:59 ` Hang Nan
  0 siblings, 0 replies; only message in thread
From: Hang Nan @ 2026-10-28  1:59 UTC (permalink / raw)
  To: Paolo Bonzini, kvm; +Cc: Sean Christopherson, Carlos Llamas, linux-kernel

kvm_vm_ioctl_register_coalesced_mmio() validates @zone->pio but not
@zone->size.  The u32 size is handed to kvm_io_bus_register_dev() as an
"int len", so any size >= 0x80000000 silently becomes negative.

coalesced_mmio_in_range() bails out immediately for len < 0, so such a
zone never matches its own range:

  - coalesced MMIO writes for the zone are no longer coalesced and are
    forwarded to userspace instead;
  - KVM_UNREGISTER_COALESCED_MMIO returns 0 but does not remove the zone,
    because the unregister path uses coalesced_mmio_in_range() for matching
    too, leaving the kvm_io_device permanently registered;
  - the leaked device occupies a slot on the MMIO/PIO bus.  After
    NR_IOBUS_DEVS - 1 (998) such zones every further
    kvm_io_bus_register_dev() on that bus fails with -ENOSPC, including
    ioeventfds and valid coalesced zones, effectively killing the VM's
    device model until the VM is destroyed.

A negative len additionally breaks the sorted-interval invariant used by
kvm_io_bus_cmp(), which computes the end of an interval as "addr + len".

ioeventfd rejects lengths outside {0, 1, 2, 4, 8}, so the same ioctl
family cannot be abused this way; coalesced MMIO has no equivalent check.

Validate the zone in kvm_vm_ioctl_unregister_coalesced_mmio() as well: it
has the same gap today and is only saved by coalesced_mmio_in_range()
rejecting negative lengths (commit 1a214246cbb4).  Factor both checks into
kvm_is_valid_io_zone() and reject with -EINVAL.

Note that this makes KVM_UNREGISTER_COALESCED_MMIO return -EINVAL for a
zone that could never have been registered, instead of the old "0 but the
zone stays" behaviour.  QEMU never registers a zone larger than the
coalesced ring window, so no in-tree user is affected.

Fixes: 743eeb0b01d2 ("KVM: Intelligent device lookup on I/O bus")
Cc: stable@vger.kernel.org
Signed-off-by: Hang Nan <2122295973@qq.com>
---
v2:
 - recipients taken from scripts/get_maintainer.pl (Paolo and the list in To:,
   you and Carlos in Cc:), sorry about that
 - dropped the comment
 - folded the validation into kvm_is_valid_io_zone() and used it from
   kvm_vm_ioctl_unregister_coalesced_mmio() as well

On the unregister side: agreed, it only survives today because
coalesced_mmio_in_range() rejects negative lengths (1a214246cbb4).  Validating
there does change the return value for a zone that can no longer be registered
at all, from 0 to -EINVAL, so I called it out in the changelog -- it is an
ABI-visible difference for anyone who relied on the old behaviour.

Verified before and after the patch on v7.3-11778-g5b0764a53235, same .config,
unprivileged /dev/kvm, same initrd:

  before: REGISTER {addr=0x1000, size=0x80000000}          -> 0
          UNREGISTER {same}                                -> 0 (zone stays)
          998 bad zones accepted, then
          REGISTER {addr=0x900000, size=0x1000}            -> -ENOSPC
  after:  REGISTER {addr=0x1000, size=0x80000000}          -> -EINVAL
          UNREGISTER {same}                                -> -EINVAL
          1200 attempts, 0 accepted
          REGISTER {addr=0x900000, size=0x1000}            -> 0
          UNREGISTER {same}                                -> 0

The reproducer is ~150 lines of static C that only opens /dev/kvm; happy to
post it if it is useful.

Hang


virt/kvm/coalesced_mmio.c | 15 +++++++++++++--
 1 file changed, 13 insertions(+), 2 deletions(-)

diff --git a/virt/kvm/coalesced_mmio.c b/virt/kvm/coalesced_mmio.c
index 3b5430036cae..4afa86b72cf6 100644
--- a/virt/kvm/coalesced_mmio.c
+++ b/virt/kvm/coalesced_mmio.c
@@ -121,13 +121,24 @@ void kvm_coalesced_mmio_free(struct kvm *kvm)
 		free_page((unsigned long)kvm->coalesced_mmio_ring);
 }
 
+static bool kvm_is_valid_io_zone(struct kvm_coalesced_mmio_zone *zone)
+{
+	if (zone->pio != 1 && zone->pio != 0)
+		return false;
+
+	if (zone->size > INT_MAX)
+		return false;
+
+	return true;
+}
+
 int kvm_vm_ioctl_register_coalesced_mmio(struct kvm *kvm,
 					 struct kvm_coalesced_mmio_zone *zone)
 {
 	int ret;
 	struct kvm_coalesced_mmio_dev *dev;
 
-	if (zone->pio != 1 && zone->pio != 0)
+	if (!kvm_is_valid_io_zone(zone))
 		return -EINVAL;
 
 	dev = kzalloc_obj(struct kvm_coalesced_mmio_dev, GFP_KERNEL_ACCOUNT);
@@ -162,7 +173,7 @@ int kvm_vm_ioctl_unregister_coalesced_mmio(struct kvm *kvm,
 	struct kvm_coalesced_mmio_dev *dev, *tmp;
 	int r;
 
-	if (zone->pio != 1 && zone->pio != 0)
+	if (!kvm_is_valid_io_zone(zone))
 		return -EINVAL;
 
 	mutex_lock(&kvm->slots_lock);


^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2026-10-28  2:00 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
     [not found] <1792859391.770388.v1@qq.com>
2026-10-28  1:59 ` [PATCH v2] KVM: coalesced_mmio: reject zone sizes that don't fit in an int Hang Nan

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®