diff options
author | Stefan Hajnoczi <stefanha@redhat.com> | 2016-09-21 16:52:19 +0100 |
---|---|---|
committer | Michael S. Tsirkin <mst@redhat.com> | 2016-09-23 19:03:55 +0300 |
commit | f5ed36635d8fa73feb66fe12b3b9c2ed90a1adbe (patch) | |
tree | a9086bb7f609570c31bf5503c89e81c947adc890 /hw | |
parent | 8275e2f6be8b10c2b3da0fe6927d0ce7ad438c80 (diff) | |
download | qemu-f5ed36635d8fa73feb66fe12b3b9c2ed90a1adbe.zip |
virtio: stop virtqueue processing if device is broken
QEMU prints an error message and exits when the device enters an invalid
state. Terminating the process is heavy-handed. The guest may still be
able to function even if there is a bug in a virtio guest driver.
Moreover, exiting is a bug in nested virtualization where a nested guest
could DoS other nested guests by killing a pass-through virtio device.
I don't think this configuration is possible today but it is likely in
the future.
If the broken flag is set, do not process virtqueues or write back used
descriptors. The broken flag can be cleared again by resetting the
device.
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
Reviewed-by: Cornelia Huck <cornelia.huck@de.ibm.com>
Reviewed-by: Michael S. Tsirkin <mst@redhat.com>
Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
Reviewed-by: Cornelia Huck <cornelia.huck@de.ibm.com>
Diffstat (limited to 'hw')
-rw-r--r-- | hw/virtio/virtio.c | 39 |
1 files changed, 39 insertions, 0 deletions
diff --git a/hw/virtio/virtio.c b/hw/virtio/virtio.c index 1199149a18..1671ea8451 100644 --- a/hw/virtio/virtio.c +++ b/hw/virtio/virtio.c @@ -303,6 +303,10 @@ void virtqueue_fill(VirtQueue *vq, const VirtQueueElement *elem, virtqueue_unmap_sg(vq, elem, len); + if (unlikely(vq->vdev->broken)) { + return; + } + idx = (idx + vq->used_idx) % vq->vring.num; uelem.id = elem->index; @@ -313,6 +317,12 @@ void virtqueue_fill(VirtQueue *vq, const VirtQueueElement *elem, void virtqueue_flush(VirtQueue *vq, unsigned int count) { uint16_t old, new; + + if (unlikely(vq->vdev->broken)) { + vq->inuse -= count; + return; + } + /* Make sure buffer is written before we update index. */ smp_wmb(); trace_virtqueue_flush(vq, count); @@ -583,6 +593,9 @@ void *virtqueue_pop(VirtQueue *vq, size_t sz) struct iovec iov[VIRTQUEUE_MAX_SIZE]; VRingDesc desc; + if (unlikely(vdev->broken)) { + return NULL; + } if (virtio_queue_empty(vq)) { return NULL; } @@ -747,6 +760,10 @@ static void virtio_notify_vector(VirtIODevice *vdev, uint16_t vector) BusState *qbus = qdev_get_parent_bus(DEVICE(vdev)); VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus); + if (unlikely(vdev->broken)) { + return; + } + if (k->notify) { k->notify(qbus->parent, vector); } @@ -830,6 +847,7 @@ void virtio_reset(void *opaque) k->reset(vdev); } + vdev->broken = false; vdev->guest_features = 0; vdev->queue_sel = 0; vdev->status = 0; @@ -1137,6 +1155,10 @@ static void virtio_queue_notify_vq(VirtQueue *vq) if (vq->vring.desc && vq->handle_output) { VirtIODevice *vdev = vq->vdev; + if (unlikely(vdev->broken)) { + return; + } + trace_virtio_queue_notify(vdev, vq - vdev->vq, vq); vq->handle_output(vdev, vq); } @@ -1758,6 +1780,7 @@ void virtio_init(VirtIODevice *vdev, const char *name, vdev->config_vector = VIRTIO_NO_VECTOR; vdev->vq = g_malloc0(sizeof(VirtQueue) * VIRTIO_QUEUE_MAX); vdev->vm_running = runstate_is_running(); + vdev->broken = false; for (i = 0; i < VIRTIO_QUEUE_MAX; i++) { vdev->vq[i].vector = VIRTIO_NO_VECTOR; vdev->vq[i].vdev = vdev; @@ -1944,6 +1967,22 @@ void virtio_device_set_child_bus_name(VirtIODevice *vdev, char *bus_name) vdev->bus_name = g_strdup(bus_name); } +void GCC_FMT_ATTR(2, 3) virtio_error(VirtIODevice *vdev, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + error_vreport(fmt, ap); + va_end(ap); + + vdev->broken = true; + + if (virtio_vdev_has_feature(vdev, VIRTIO_F_VERSION_1)) { + virtio_set_status(vdev, vdev->status | VIRTIO_CONFIG_S_NEEDS_RESET); + virtio_notify_config(vdev); + } +} + static void virtio_device_realize(DeviceState *dev, Error **errp) { VirtIODevice *vdev = VIRTIO_DEVICE(dev); |