Skip to content

Commit 112b0b8

Browse files
Andre-ARMMarc Zyngier
authored andcommitted
KVM: arm/arm64: vgic: Prevent access to invalid SPIs
In our VGIC implementation we limit the number of SPIs to a number that the userland application told us. Accordingly we limit the allocation of memory for virtual IRQs to that number. However in our MMIO dispatcher we didn't check if we ever access an IRQ beyond that limit, leading to out-of-bound accesses. Add a test against the number of allocated SPIs in check_region(). Adjust the VGIC_ADDR_TO_INT macro to avoid an actual division, which is not implemented on ARM(32). [maz: cleaned-up original patch] Cc: [email protected] Reviewed-by: Christoffer Dall <[email protected]> Signed-off-by: Andre Przywara <[email protected]> Signed-off-by: Marc Zyngier <[email protected]>
1 parent 94d0e59 commit 112b0b8

File tree

2 files changed

+34
-21
lines changed

2 files changed

+34
-21
lines changed

virt/kvm/arm/vgic/vgic-mmio.c

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -453,17 +453,33 @@ struct vgic_io_device *kvm_to_vgic_iodev(const struct kvm_io_device *dev)
453453
return container_of(dev, struct vgic_io_device, dev);
454454
}
455455

456-
static bool check_region(const struct vgic_register_region *region,
456+
static bool check_region(const struct kvm *kvm,
457+
const struct vgic_register_region *region,
457458
gpa_t addr, int len)
458459
{
459-
if ((region->access_flags & VGIC_ACCESS_8bit) && len == 1)
460-
return true;
461-
if ((region->access_flags & VGIC_ACCESS_32bit) &&
462-
len == sizeof(u32) && !(addr & 3))
463-
return true;
464-
if ((region->access_flags & VGIC_ACCESS_64bit) &&
465-
len == sizeof(u64) && !(addr & 7))
466-
return true;
460+
int flags, nr_irqs = kvm->arch.vgic.nr_spis + VGIC_NR_PRIVATE_IRQS;
461+
462+
switch (len) {
463+
case sizeof(u8):
464+
flags = VGIC_ACCESS_8bit;
465+
break;
466+
case sizeof(u32):
467+
flags = VGIC_ACCESS_32bit;
468+
break;
469+
case sizeof(u64):
470+
flags = VGIC_ACCESS_64bit;
471+
break;
472+
default:
473+
return false;
474+
}
475+
476+
if ((region->access_flags & flags) && IS_ALIGNED(addr, len)) {
477+
if (!region->bits_per_irq)
478+
return true;
479+
480+
/* Do we access a non-allocated IRQ? */
481+
return VGIC_ADDR_TO_INTID(addr, region->bits_per_irq) < nr_irqs;
482+
}
467483

468484
return false;
469485
}
@@ -477,7 +493,7 @@ static int dispatch_mmio_read(struct kvm_vcpu *vcpu, struct kvm_io_device *dev,
477493

478494
region = vgic_find_mmio_region(iodev->regions, iodev->nr_regions,
479495
addr - iodev->base_addr);
480-
if (!region || !check_region(region, addr, len)) {
496+
if (!region || !check_region(vcpu->kvm, region, addr, len)) {
481497
memset(val, 0, len);
482498
return 0;
483499
}
@@ -510,10 +526,7 @@ static int dispatch_mmio_write(struct kvm_vcpu *vcpu, struct kvm_io_device *dev,
510526

511527
region = vgic_find_mmio_region(iodev->regions, iodev->nr_regions,
512528
addr - iodev->base_addr);
513-
if (!region)
514-
return 0;
515-
516-
if (!check_region(region, addr, len))
529+
if (!region || !check_region(vcpu->kvm, region, addr, len))
517530
return 0;
518531

519532
switch (iodev->iodev_type) {

virt/kvm/arm/vgic/vgic-mmio.h

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -50,15 +50,15 @@ extern struct kvm_io_device_ops kvm_io_gic_ops;
5050
#define VGIC_ADDR_IRQ_MASK(bits) (((bits) * 1024 / 8) - 1)
5151

5252
/*
53-
* (addr & mask) gives us the byte offset for the INT ID, so we want to
54-
* divide this with 'bytes per irq' to get the INT ID, which is given
55-
* by '(bits) / 8'. But we do this with fixed-point-arithmetic and
56-
* take advantage of the fact that division by a fraction equals
57-
* multiplication with the inverted fraction, and scale up both the
58-
* numerator and denominator with 8 to support at most 64 bits per IRQ:
53+
* (addr & mask) gives us the _byte_ offset for the INT ID.
54+
* We multiply this by 8 the get the _bit_ offset, then divide this by
55+
* the number of bits to learn the actual INT ID.
56+
* But instead of a division (which requires a "long long div" implementation),
57+
* we shift by the binary logarithm of <bits>.
58+
* This assumes that <bits> is a power of two.
5959
*/
6060
#define VGIC_ADDR_TO_INTID(addr, bits) (((addr) & VGIC_ADDR_IRQ_MASK(bits)) * \
61-
64 / (bits) / 8)
61+
8 >> ilog2(bits))
6262

6363
/*
6464
* Some VGIC registers store per-IRQ information, with a different number

0 commit comments

Comments
 (0)