libvhost-user: Support across-memory-boundary access

The sg list/indirect descriptor table may be contigious
in GPA but not in HVA address space. But libvhost-user
wasn't aware of that. This would cause out-of-bounds
access. Even a malicious guest could use it to get
information from the vhost-user backend.

Introduce a plen parameter in vu_gpa_to_va() so we can
handle this case, returning the actual mapped length.

Signed-off-by: Yongji Xie <xieyongji@baidu.com>
Reviewed-by: Michael S. Tsirkin <mst@redhat.com>
Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
Reviewed-by: Maxime Coquelin <maxime.coquelin@redhat.com>
This commit is contained in:
Yongji Xie 2018-01-19 00:04:05 +08:00 committed by Michael S. Tsirkin
parent bb102d1da1
commit 293084a719
2 changed files with 122 additions and 14 deletions

View File

@ -118,15 +118,22 @@ vu_panic(VuDev *dev, const char *msg, ...)
/* Translate guest physical address to our virtual address. */ /* Translate guest physical address to our virtual address. */
void * void *
vu_gpa_to_va(VuDev *dev, uint64_t guest_addr) vu_gpa_to_va(VuDev *dev, uint64_t *plen, uint64_t guest_addr)
{ {
int i; int i;
if (*plen == 0) {
return NULL;
}
/* Find matching memory region. */ /* Find matching memory region. */
for (i = 0; i < dev->nregions; i++) { for (i = 0; i < dev->nregions; i++) {
VuDevRegion *r = &dev->regions[i]; VuDevRegion *r = &dev->regions[i];
if ((guest_addr >= r->gpa) && (guest_addr < (r->gpa + r->size))) { if ((guest_addr >= r->gpa) && (guest_addr < (r->gpa + r->size))) {
if ((guest_addr + *plen) > (r->gpa + r->size)) {
*plen = r->gpa + r->size - guest_addr;
}
return (void *)(uintptr_t) return (void *)(uintptr_t)
guest_addr - r->gpa + r->mmap_addr + r->mmap_offset; guest_addr - r->gpa + r->mmap_addr + r->mmap_offset;
} }
@ -1116,6 +1123,37 @@ virtqueue_get_head(VuDev *dev, VuVirtq *vq,
return true; return true;
} }
static int
virtqueue_read_indirect_desc(VuDev *dev, struct vring_desc *desc,
uint64_t addr, size_t len)
{
struct vring_desc *ori_desc;
uint64_t read_len;
if (len > (VIRTQUEUE_MAX_SIZE * sizeof(struct vring_desc))) {
return -1;
}
if (len == 0) {
return -1;
}
while (len) {
read_len = len;
ori_desc = vu_gpa_to_va(dev, &read_len, addr);
if (!ori_desc) {
return -1;
}
memcpy(desc, ori_desc, read_len);
len -= read_len;
addr += read_len;
desc += read_len;
}
return 0;
}
enum { enum {
VIRTQUEUE_READ_DESC_ERROR = -1, VIRTQUEUE_READ_DESC_ERROR = -1,
VIRTQUEUE_READ_DESC_DONE = 0, /* end of chain */ VIRTQUEUE_READ_DESC_DONE = 0, /* end of chain */
@ -1162,8 +1200,10 @@ vu_queue_get_avail_bytes(VuDev *dev, VuVirtq *vq, unsigned int *in_bytes,
} }
while ((rc = virtqueue_num_heads(dev, vq, idx)) > 0) { while ((rc = virtqueue_num_heads(dev, vq, idx)) > 0) {
unsigned int max, num_bufs, indirect = 0; unsigned int max, desc_len, num_bufs, indirect = 0;
uint64_t desc_addr, read_len;
struct vring_desc *desc; struct vring_desc *desc;
struct vring_desc desc_buf[VIRTQUEUE_MAX_SIZE];
unsigned int i; unsigned int i;
max = vq->vring.num; max = vq->vring.num;
@ -1187,8 +1227,24 @@ vu_queue_get_avail_bytes(VuDev *dev, VuVirtq *vq, unsigned int *in_bytes,
/* loop over the indirect descriptor table */ /* loop over the indirect descriptor table */
indirect = 1; indirect = 1;
max = desc[i].len / sizeof(struct vring_desc); desc_addr = desc[i].addr;
desc = vu_gpa_to_va(dev, desc[i].addr); desc_len = desc[i].len;
max = desc_len / sizeof(struct vring_desc);
read_len = desc_len;
desc = vu_gpa_to_va(dev, &read_len, desc_addr);
if (unlikely(desc && read_len != desc_len)) {
/* Failed to use zero copy */
desc = NULL;
if (!virtqueue_read_indirect_desc(dev, desc_buf,
desc_addr,
desc_len)) {
desc = desc_buf;
}
}
if (!desc) {
vu_panic(dev, "Invalid indirect buffer table");
goto err;
}
num_bufs = i = 0; num_bufs = i = 0;
} }
@ -1386,9 +1442,24 @@ virtqueue_map_desc(VuDev *dev,
return; return;
} }
iov[num_sg].iov_base = vu_gpa_to_va(dev, pa); while (sz) {
iov[num_sg].iov_len = sz; uint64_t len = sz;
num_sg++;
if (num_sg == max_num_sg) {
vu_panic(dev, "virtio: too many descriptors in indirect table");
return;
}
iov[num_sg].iov_base = vu_gpa_to_va(dev, &len, pa);
if (iov[num_sg].iov_base == NULL) {
vu_panic(dev, "virtio: invalid address for buffers");
return;
}
iov[num_sg].iov_len = len;
num_sg++;
sz -= len;
pa += len;
}
*p_num_sg = num_sg; *p_num_sg = num_sg;
} }
@ -1420,10 +1491,12 @@ virtqueue_alloc_element(size_t sz,
void * void *
vu_queue_pop(VuDev *dev, VuVirtq *vq, size_t sz) vu_queue_pop(VuDev *dev, VuVirtq *vq, size_t sz)
{ {
unsigned int i, head, max; unsigned int i, head, max, desc_len;
uint64_t desc_addr, read_len;
VuVirtqElement *elem; VuVirtqElement *elem;
unsigned out_num, in_num; unsigned out_num, in_num;
struct iovec iov[VIRTQUEUE_MAX_SIZE]; struct iovec iov[VIRTQUEUE_MAX_SIZE];
struct vring_desc desc_buf[VIRTQUEUE_MAX_SIZE];
struct vring_desc *desc; struct vring_desc *desc;
int rc; int rc;
@ -1464,8 +1537,24 @@ vu_queue_pop(VuDev *dev, VuVirtq *vq, size_t sz)
} }
/* loop over the indirect descriptor table */ /* loop over the indirect descriptor table */
max = desc[i].len / sizeof(struct vring_desc); desc_addr = desc[i].addr;
desc = vu_gpa_to_va(dev, desc[i].addr); desc_len = desc[i].len;
max = desc_len / sizeof(struct vring_desc);
read_len = desc_len;
desc = vu_gpa_to_va(dev, &read_len, desc_addr);
if (unlikely(desc && read_len != desc_len)) {
/* Failed to use zero copy */
desc = NULL;
if (!virtqueue_read_indirect_desc(dev, desc_buf,
desc_addr,
desc_len)) {
desc = desc_buf;
}
}
if (!desc) {
vu_panic(dev, "Invalid indirect buffer table");
return NULL;
}
i = 0; i = 0;
} }
@ -1541,7 +1630,9 @@ vu_log_queue_fill(VuDev *dev, VuVirtq *vq,
unsigned int len) unsigned int len)
{ {
struct vring_desc *desc = vq->vring.desc; struct vring_desc *desc = vq->vring.desc;
unsigned int i, max, min; unsigned int i, max, min, desc_len;
uint64_t desc_addr, read_len;
struct vring_desc desc_buf[VIRTQUEUE_MAX_SIZE];
unsigned num_bufs = 0; unsigned num_bufs = 0;
max = vq->vring.num; max = vq->vring.num;
@ -1553,8 +1644,24 @@ vu_log_queue_fill(VuDev *dev, VuVirtq *vq,
} }
/* loop over the indirect descriptor table */ /* loop over the indirect descriptor table */
max = desc[i].len / sizeof(struct vring_desc); desc_addr = desc[i].addr;
desc = vu_gpa_to_va(dev, desc[i].addr); desc_len = desc[i].len;
max = desc_len / sizeof(struct vring_desc);
read_len = desc_len;
desc = vu_gpa_to_va(dev, &read_len, desc_addr);
if (unlikely(desc && read_len != desc_len)) {
/* Failed to use zero copy */
desc = NULL;
if (!virtqueue_read_indirect_desc(dev, desc_buf,
desc_addr,
desc_len)) {
desc = desc_buf;
}
}
if (!desc) {
vu_panic(dev, "Invalid indirect buffer table");
return;
}
i = 0; i = 0;
} }

View File

@ -327,11 +327,12 @@ bool vu_dispatch(VuDev *dev);
/** /**
* vu_gpa_to_va: * vu_gpa_to_va:
* @dev: a VuDev context * @dev: a VuDev context
* @plen: guest memory size
* @guest_addr: guest address * @guest_addr: guest address
* *
* Translate a guest address to a pointer. Returns NULL on failure. * Translate a guest address to a pointer. Returns NULL on failure.
*/ */
void *vu_gpa_to_va(VuDev *dev, uint64_t guest_addr); void *vu_gpa_to_va(VuDev *dev, uint64_t *plen, uint64_t guest_addr);
/** /**
* vu_get_queue: * vu_get_queue: