[PATCH v3] KVM: arm/arm64: vgic: Prevent access to invalid SPIs
Christoffer Dall
christoffer.dall at linaro.org
Wed Nov 2 02:03:27 PDT 2016
On Tue, Nov 01, 2016 at 06:00:08PM +0000, Andre Przywara wrote:
> 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: stable at vger.kernel.org
> Signed-off-by: Andre Przywara <andre.przywara at arm.com>
> Signed-off-by: Marc Zyngier <marc.zyngier at arm.com>
Reviewed-by: Christoffer Dall <christoffer.dall at linaro.org>
> ---
> Hi,
>
> I checked that the old and new VGIC_ADDR_TO_INTID() algorithm give
> identical results by moving it into a small userland unit-test, using
> all <bits> values we use in the VGIC and calling it with quite some test
> addresses. No differences were found.
nice.
-Christoffer
>
> Changelog v2 .. v3:
> - further simplify VGIC_ADDR_TO_INTID
> - adjust VGIC_ADDR_TO_INTID comment
>
> Changelog v1 .. v2:
> - fix compilation for 32-bit ARM
>
> virt/kvm/arm/vgic/vgic-mmio.c | 41 +++++++++++++++++++++++++++--------------
> virt/kvm/arm/vgic/vgic-mmio.h | 14 +++++++-------
> 2 files changed, 34 insertions(+), 21 deletions(-)
>
> diff --git a/virt/kvm/arm/vgic/vgic-mmio.c b/virt/kvm/arm/vgic/vgic-mmio.c
> index e18b30d..ebe1b9f 100644
> --- a/virt/kvm/arm/vgic/vgic-mmio.c
> +++ b/virt/kvm/arm/vgic/vgic-mmio.c
> @@ -453,17 +453,33 @@ struct vgic_io_device *kvm_to_vgic_iodev(const struct kvm_io_device *dev)
> return container_of(dev, struct vgic_io_device, dev);
> }
>
> -static bool check_region(const struct vgic_register_region *region,
> +static bool check_region(const struct kvm *kvm,
> + const struct vgic_register_region *region,
> gpa_t addr, int len)
> {
> - if ((region->access_flags & VGIC_ACCESS_8bit) && len == 1)
> - return true;
> - if ((region->access_flags & VGIC_ACCESS_32bit) &&
> - len == sizeof(u32) && !(addr & 3))
> - return true;
> - if ((region->access_flags & VGIC_ACCESS_64bit) &&
> - len == sizeof(u64) && !(addr & 7))
> - return true;
> + int flags, nr_irqs = kvm->arch.vgic.nr_spis + VGIC_NR_PRIVATE_IRQS;
> +
> + switch (len) {
> + case sizeof(u8):
> + flags = VGIC_ACCESS_8bit;
> + break;
> + case sizeof(u32):
> + flags = VGIC_ACCESS_32bit;
> + break;
> + case sizeof(u64):
> + flags = VGIC_ACCESS_64bit;
> + break;
> + default:
> + return false;
> + }
> +
> + if ((region->access_flags & flags) && IS_ALIGNED(addr, len)) {
> + if (!region->bits_per_irq)
> + return true;
> +
> + /* Do we access a non-allocated IRQ? */
> + return VGIC_ADDR_TO_INTID(addr, region->bits_per_irq) < nr_irqs;
> + }
>
> return false;
> }
> @@ -477,7 +493,7 @@ static int dispatch_mmio_read(struct kvm_vcpu *vcpu, struct kvm_io_device *dev,
>
> region = vgic_find_mmio_region(iodev->regions, iodev->nr_regions,
> addr - iodev->base_addr);
> - if (!region || !check_region(region, addr, len)) {
> + if (!region || !check_region(vcpu->kvm, region, addr, len)) {
> memset(val, 0, len);
> return 0;
> }
> @@ -510,10 +526,7 @@ static int dispatch_mmio_write(struct kvm_vcpu *vcpu, struct kvm_io_device *dev,
>
> region = vgic_find_mmio_region(iodev->regions, iodev->nr_regions,
> addr - iodev->base_addr);
> - if (!region)
> - return 0;
> -
> - if (!check_region(region, addr, len))
> + if (!region || !check_region(vcpu->kvm, region, addr, len))
> return 0;
>
> switch (iodev->iodev_type) {
> diff --git a/virt/kvm/arm/vgic/vgic-mmio.h b/virt/kvm/arm/vgic/vgic-mmio.h
> index 4c34d39..84961b4 100644
> --- a/virt/kvm/arm/vgic/vgic-mmio.h
> +++ b/virt/kvm/arm/vgic/vgic-mmio.h
> @@ -50,15 +50,15 @@ extern struct kvm_io_device_ops kvm_io_gic_ops;
> #define VGIC_ADDR_IRQ_MASK(bits) (((bits) * 1024 / 8) - 1)
>
> /*
> - * (addr & mask) gives us the byte offset for the INT ID, so we want to
> - * divide this with 'bytes per irq' to get the INT ID, which is given
> - * by '(bits) / 8'. But we do this with fixed-point-arithmetic and
> - * take advantage of the fact that division by a fraction equals
> - * multiplication with the inverted fraction, and scale up both the
> - * numerator and denominator with 8 to support at most 64 bits per IRQ:
> + * (addr & mask) gives us the _byte_ offset for the INT ID.
> + * We multiply this by 8 the get the _bit_ offset, then divide this by
> + * the number of bits to learn the actual INT ID.
> + * But instead of a division (which requires a "long long div" implementation),
> + * we shift by the binary logarithm of <bits>.
> + * This assumes that <bits> is a power of two.
> */
> #define VGIC_ADDR_TO_INTID(addr, bits) (((addr) & VGIC_ADDR_IRQ_MASK(bits)) * \
> - 64 / (bits) / 8)
> + 8 >> ilog2(bits))
>
> /*
> * Some VGIC registers store per-IRQ information, with a different number
> --
> 2.9.0
>
More information about the linux-arm-kernel
mailing list