Skip to content

Commit

Permalink
KVM: kvm_io_bus_unregister_dev() should never fail
Browse files Browse the repository at this point in the history
commit 90db104 upstream.

No caller currently checks the return value of
kvm_io_bus_unregister_dev(). This is evil, as all callers silently go on
freeing their device. A stale reference will remain in the io_bus,
getting at least used again, when the iobus gets teared down on
kvm_destroy_vm() - leading to use after free errors.

There is nothing the callers could do, except retrying over and over
again.

So let's simply remove the bus altogether, print an error and make
sure no one can access this broken bus again (returning -ENOMEM on any
attempt to access it).

Fixes: e93f8a0 ("KVM: convert io_bus to SRCU")
Reported-by: Dmitry Vyukov <[email protected]>
Reviewed-by: Cornelia Huck <[email protected]>
Signed-off-by: David Hildenbrand <[email protected]>
Signed-off-by: Paolo Bonzini <[email protected]>
[wt: no kvm_io_bus_read_cookie in 3.10, slightly different constructs]

Signed-off-by: Willy Tarreau <[email protected]>
  • Loading branch information
davidhildenbrand authored and wtarreau committed Jun 7, 2017
1 parent d8abb8b commit 211bcbc
Show file tree
Hide file tree
Showing 3 changed files with 25 additions and 20 deletions.
4 changes: 2 additions & 2 deletions include/linux/kvm_host.h
Original file line number Diff line number Diff line change
Expand Up @@ -163,8 +163,8 @@ int kvm_io_bus_read(struct kvm *kvm, enum kvm_bus bus_idx, gpa_t addr, int len,
void *val);
int kvm_io_bus_register_dev(struct kvm *kvm, enum kvm_bus bus_idx, gpa_t addr,
int len, struct kvm_io_device *dev);
int kvm_io_bus_unregister_dev(struct kvm *kvm, enum kvm_bus bus_idx,
struct kvm_io_device *dev);
void kvm_io_bus_unregister_dev(struct kvm *kvm, enum kvm_bus bus_idx,
struct kvm_io_device *dev);

#ifdef CONFIG_KVM_ASYNC_PF
struct kvm_async_pf {
Expand Down
3 changes: 2 additions & 1 deletion virt/kvm/eventfd.c
Original file line number Diff line number Diff line change
Expand Up @@ -799,7 +799,8 @@ kvm_deassign_ioeventfd(struct kvm *kvm, struct kvm_ioeventfd *args)
continue;

kvm_io_bus_unregister_dev(kvm, bus_idx, &p->dev);
kvm->buses[bus_idx]->ioeventfd_count--;
if (kvm->buses[bus_idx])
kvm->buses[bus_idx]->ioeventfd_count--;
ioeventfd_release(p);
ret = 0;
break;
Expand Down
38 changes: 21 additions & 17 deletions virt/kvm/kvm_main.c
Original file line number Diff line number Diff line change
Expand Up @@ -608,7 +608,8 @@ static void kvm_destroy_vm(struct kvm *kvm)
raw_spin_unlock(&kvm_lock);
kvm_free_irq_routing(kvm);
for (i = 0; i < KVM_NR_BUSES; i++) {
kvm_io_bus_destroy(kvm->buses[i]);
if (kvm->buses[i])
kvm_io_bus_destroy(kvm->buses[i]);
kvm->buses[i] = NULL;
}
kvm_coalesced_mmio_free(kvm);
Expand Down Expand Up @@ -2887,6 +2888,8 @@ int kvm_io_bus_write(struct kvm *kvm, enum kvm_bus bus_idx, gpa_t addr,
};

bus = srcu_dereference(kvm->buses[bus_idx], &kvm->srcu);
if (!bus)
return -ENOMEM;
idx = kvm_io_bus_get_first_dev(bus, addr, len);
if (idx < 0)
return -EOPNOTSUPP;
Expand Down Expand Up @@ -2915,6 +2918,8 @@ int kvm_io_bus_read(struct kvm *kvm, enum kvm_bus bus_idx, gpa_t addr,
};

bus = srcu_dereference(kvm->buses[bus_idx], &kvm->srcu);
if (!bus)
return -ENOMEM;
idx = kvm_io_bus_get_first_dev(bus, addr, len);
if (idx < 0)
return -EOPNOTSUPP;
Expand All @@ -2936,6 +2941,9 @@ int kvm_io_bus_register_dev(struct kvm *kvm, enum kvm_bus bus_idx, gpa_t addr,
struct kvm_io_bus *new_bus, *bus;

bus = kvm->buses[bus_idx];
if (!bus)
return -ENOMEM;

/* exclude ioeventfd which is limited by maximum fd */
if (bus->dev_count - bus->ioeventfd_count > NR_IOBUS_DEVS - 1)
return -ENOSPC;
Expand All @@ -2955,45 +2963,41 @@ int kvm_io_bus_register_dev(struct kvm *kvm, enum kvm_bus bus_idx, gpa_t addr,
}

/* Caller must hold slots_lock. */
int kvm_io_bus_unregister_dev(struct kvm *kvm, enum kvm_bus bus_idx,
struct kvm_io_device *dev)
void kvm_io_bus_unregister_dev(struct kvm *kvm, enum kvm_bus bus_idx,
struct kvm_io_device *dev)
{
int i, r;
int i;
struct kvm_io_bus *new_bus, *bus;

bus = kvm->buses[bus_idx];

/*
* It's possible the bus being released before hand. If so,
* we're done here.
*/
if (!bus)
return 0;
return;

r = -ENOENT;
for (i = 0; i < bus->dev_count; i++)
if (bus->range[i].dev == dev) {
r = 0;
break;
}

if (r)
return r;
if (i == bus->dev_count)
return;

new_bus = kzalloc(sizeof(*bus) + ((bus->dev_count - 1) *
sizeof(struct kvm_io_range)), GFP_KERNEL);
if (!new_bus)
return -ENOMEM;
if (!new_bus) {
pr_err("kvm: failed to shrink bus, removing it completely\n");
goto broken;
}

memcpy(new_bus, bus, sizeof(*bus) + i * sizeof(struct kvm_io_range));
new_bus->dev_count--;
memcpy(new_bus->range + i, bus->range + i + 1,
(new_bus->dev_count - i) * sizeof(struct kvm_io_range));

broken:
rcu_assign_pointer(kvm->buses[bus_idx], new_bus);
synchronize_srcu_expedited(&kvm->srcu);
kfree(bus);
return r;
return;
}

static struct notifier_block kvm_cpu_notifier = {
Expand Down

0 comments on commit 211bcbc

Please sign in to comment.