virtio-blk: handle virtio_blk_handle_request() errors

All these errors are caused by a buggy guest: QEMU should not exit.

With this patch, if virtio_blk_handle_request() detects a buggy request, it
marks the device as broken and returns an error to the caller so it takes
appropriate action.

In the case of virtio_blk_handle_vq(), we detach the request from the
virtqueue, free its allocated memory and stop popping new requests.
We don't need to bother about multireq since virtio_blk_handle_request()
errors out early and mrb.num_reqs == 0.

In the case of virtio_blk_dma_restart_bh(), we need to detach and free all
queued requests as well.

Signed-off-by: Greg Kurz <groug@kaod.org>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
Reviewed-by: Michael S. Tsirkin <mst@redhat.com>
Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
This commit is contained in:
Greg Kurz 2016-09-30 17:13:07 +02:00 committed by Michael S. Tsirkin
parent d3d74d6fe0
commit 20ea686a0c
1 changed files with 28 additions and 10 deletions

View File

@ -468,30 +468,32 @@ static bool virtio_blk_sect_range_ok(VirtIOBlock *dev,
return true;
}
static void virtio_blk_handle_request(VirtIOBlockReq *req, MultiReqBuffer *mrb)
static int virtio_blk_handle_request(VirtIOBlockReq *req, MultiReqBuffer *mrb)
{
uint32_t type;
struct iovec *in_iov = req->elem.in_sg;
struct iovec *iov = req->elem.out_sg;
unsigned in_num = req->elem.in_num;
unsigned out_num = req->elem.out_num;
VirtIOBlock *s = req->dev;
VirtIODevice *vdev = VIRTIO_DEVICE(s);
if (req->elem.out_num < 1 || req->elem.in_num < 1) {
error_report("virtio-blk missing headers");
exit(1);
virtio_error(vdev, "virtio-blk missing headers");
return -1;
}
if (unlikely(iov_to_buf(iov, out_num, 0, &req->out,
sizeof(req->out)) != sizeof(req->out))) {
error_report("virtio-blk request outhdr too short");
exit(1);
virtio_error(vdev, "virtio-blk request outhdr too short");
return -1;
}
iov_discard_front(&iov, &out_num, sizeof(req->out));
if (in_iov[in_num - 1].iov_len < sizeof(struct virtio_blk_inhdr)) {
error_report("virtio-blk request inhdr too short");
exit(1);
virtio_error(vdev, "virtio-blk request inhdr too short");
return -1;
}
/* We always touch the last byte, so just see how big in_iov is. */
@ -529,7 +531,7 @@ static void virtio_blk_handle_request(VirtIOBlockReq *req, MultiReqBuffer *mrb)
block_acct_invalid(blk_get_stats(req->dev->blk),
is_write ? BLOCK_ACCT_WRITE : BLOCK_ACCT_READ);
virtio_blk_free_request(req);
return;
return 0;
}
block_acct_start(blk_get_stats(req->dev->blk),
@ -576,6 +578,7 @@ static void virtio_blk_handle_request(VirtIOBlockReq *req, MultiReqBuffer *mrb)
virtio_blk_req_complete(req, VIRTIO_BLK_S_UNSUPP);
virtio_blk_free_request(req);
}
return 0;
}
void virtio_blk_handle_vq(VirtIOBlock *s, VirtQueue *vq)
@ -586,7 +589,11 @@ void virtio_blk_handle_vq(VirtIOBlock *s, VirtQueue *vq)
blk_io_plug(s->blk);
while ((req = virtio_blk_get_request(s, vq))) {
virtio_blk_handle_request(req, &mrb);
if (virtio_blk_handle_request(req, &mrb)) {
virtqueue_detach_element(req->vq, &req->elem, 0);
virtio_blk_free_request(req);
break;
}
}
if (mrb.num_reqs) {
@ -625,7 +632,18 @@ static void virtio_blk_dma_restart_bh(void *opaque)
while (req) {
VirtIOBlockReq *next = req->next;
virtio_blk_handle_request(req, &mrb);
if (virtio_blk_handle_request(req, &mrb)) {
/* Device is now broken and won't do any processing until it gets
* reset. Already queued requests will be lost: let's purge them.
*/
while (req) {
next = req->next;
virtqueue_detach_element(req->vq, &req->elem, 0);
virtio_blk_free_request(req);
req = next;
}
break;
}
req = next;
}