Skip to content

Commit a221b63

Browse files
authored
Merge pull request #49 from sysprog21/bound-check
Bound-check guest buffer lengths
2 parents decff82 + 33b400e commit a221b63

4 files changed

Lines changed: 57 additions & 22 deletions

File tree

src/virtio-blk.c

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ static void virtio_blk_complete_request(struct virtq *vq)
103103
struct vring_packed_desc *used_desc = desc;
104104
ssize_t io_bytes = 0;
105105

106-
void *hdr = vm_guest_to_host(v, desc->addr);
106+
void *hdr = vm_guest_buf(v, desc->addr, hdr_sz);
107107
if (!hdr || desc->len < hdr_sz)
108108
return;
109109
memcpy(&req, hdr, hdr_sz);
@@ -112,33 +112,48 @@ static void virtio_blk_complete_request(struct virtq *vq)
112112
return;
113113
desc = virtq_get_avail(vq);
114114
req.data_size = desc->len;
115-
req.data = vm_guest_to_host(v, desc->addr);
116-
if (!req.data)
117-
return;
118-
119-
if (req.type == VIRTIO_BLK_T_IN)
120-
io_bytes = virtio_blk_read(dev, req.data, req.sector << 9,
121-
req.data_size);
122-
else
123-
io_bytes = virtio_blk_write(dev, req.data, req.sector << 9,
124-
req.data_size);
125-
126-
status = io_bytes < 0 ? VIRTIO_BLK_S_IOERR : VIRTIO_BLK_S_OK;
115+
req.data = vm_guest_buf(v, desc->addr, req.data_size);
116+
117+
/* Validate that the request fits in the backing store. Both the
118+
* shift (sector*512) and the addition (offset+data_size) must not
119+
* overflow, and the end must be within diskimg->size. Any failure
120+
* yields VIRTIO_BLK_S_IOERR with no data transferred. */
121+
uint64_t off, end;
122+
bool io_ok = false;
123+
if (req.data && !__builtin_mul_overflow(req.sector, 512, &off) &&
124+
!__builtin_add_overflow(off, req.data_size, &end) &&
125+
end <= (uint64_t) dev->diskimg->size) {
126+
if (req.type == VIRTIO_BLK_T_IN)
127+
io_bytes = virtio_blk_read(dev, req.data, (off_t) off,
128+
req.data_size);
129+
else
130+
io_bytes = virtio_blk_write(dev, req.data, (off_t) off,
131+
req.data_size);
132+
/* A short read/write leaves part of the guest buffer stale,
133+
* so treat anything less than the full request as IOERR. */
134+
io_ok = io_bytes >= 0 && (size_t) io_bytes == req.data_size;
135+
}
136+
status = io_ok ? VIRTIO_BLK_S_OK : VIRTIO_BLK_S_IOERR;
127137
} else {
128138
status = VIRTIO_BLK_S_UNSUPP;
129139
}
130140
if (!virtq_check_next(desc))
131141
return;
132142
desc = virtq_get_avail(vq);
133-
req.status = vm_guest_to_host(v, desc->addr);
143+
/* The status descriptor must advertise at least one device-writable
144+
* byte; otherwise we'd clobber memory the guest did not offer. */
145+
if (desc->len < 1)
146+
return;
147+
req.status = vm_guest_buf(v, desc->addr, 1);
134148
if (!req.status)
135149
return;
136150
*req.status = status;
137151
/* used.len is total bytes the device wrote into device-writable
138152
* buffers across the chain: the 1-byte status is always written, plus
139-
* io_bytes of data on a successful IN. */
153+
* the data buffer on a successful IN. On any error we report only the
154+
* status byte so the guest does not consume stale data. */
140155
size_t written = 1;
141-
if (req.type == VIRTIO_BLK_T_IN && io_bytes > 0)
156+
if (status == VIRTIO_BLK_S_OK && req.type == VIRTIO_BLK_T_IN)
142157
written += (size_t) io_bytes;
143158
used_desc->len = (uint32_t) written;
144159
used_desc->flags ^= (1ULL << VRING_PACKED_DESC_F_USED);

src/virtio-net.c

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -153,16 +153,24 @@ void virtio_net_complete_request_rx(struct virtq *vq)
153153
struct vring_packed_desc *desc;
154154

155155
while ((desc = virtq_get_avail(vq)) != NULL) {
156-
uint8_t *data = vm_guest_to_host(v, desc->addr);
156+
size_t virtio_header_len = sizeof(struct virtio_net_hdr_v1);
157+
/* desc lives in guest-writable memory; snapshot the length we'll
158+
* validate and use so a concurrent guest write cannot widen the
159+
* access past the bounds check. */
160+
uint32_t buf_len = desc->len;
161+
uint8_t *data = vm_guest_buf(v, desc->addr, buf_len);
162+
if (!data || buf_len < virtio_header_len) {
163+
vq->guest_event->flags = VRING_PACKED_EVENT_FLAG_DISABLE;
164+
return;
165+
}
157166
struct virtio_net_hdr_v1 *virtio_hdr =
158167
(struct virtio_net_hdr_v1 *) data;
159168
memset(virtio_hdr, 0, sizeof(struct virtio_net_hdr_v1));
160169

161170
virtio_hdr->num_buffers = 1;
162171

163-
size_t virtio_header_len = sizeof(struct virtio_net_hdr_v1);
164172
ssize_t read_bytes = read(dev->tapfd, data + virtio_header_len,
165-
desc->len - virtio_header_len);
173+
buf_len - virtio_header_len);
166174
if (read_bytes < 0) {
167175
vq->guest_event->flags = VRING_PACKED_EVENT_FLAG_DISABLE;
168176
return;
@@ -183,16 +191,18 @@ void virtio_net_complete_request_tx(struct virtq *vq)
183191
vm_t *v = container_of(dev, vm_t, virtio_net_dev);
184192
struct vring_packed_desc *desc;
185193
while ((desc = virtq_get_avail(vq)) != NULL) {
186-
uint8_t *data = vm_guest_to_host(v, desc->addr);
187194
size_t virtio_header_len = sizeof(struct virtio_net_hdr_v1);
195+
/* See rx path: snapshot len before bounds check to defeat TOCTOU. */
196+
uint32_t buf_len = desc->len;
197+
uint8_t *data = vm_guest_buf(v, desc->addr, buf_len);
188198

189-
if (desc->len < virtio_header_len) {
199+
if (!data || buf_len < virtio_header_len) {
190200
vq->guest_event->flags = VRING_PACKED_EVENT_FLAG_DISABLE;
191201
return;
192202
}
193203

194204
uint8_t *actual_data = data + virtio_header_len;
195-
size_t actual_data_len = desc->len - virtio_header_len;
205+
size_t actual_data_len = buf_len - virtio_header_len;
196206

197207
struct iovec iov[1];
198208
iov[0].iov_base = actual_data;

src/vm.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,15 @@ void *vm_guest_to_host(vm_t *v, uint64_t guest)
176176
return (void *) ((uintptr_t) v->mem + guest - RAM_BASE);
177177
}
178178

179+
void *vm_guest_buf(vm_t *v, uint64_t guest, size_t len)
180+
{
181+
uint64_t end;
182+
if (guest < RAM_BASE || __builtin_add_overflow(guest, len, &end) ||
183+
end > RAM_BASE + RAM_SIZE)
184+
return NULL;
185+
return (void *) ((uintptr_t) v->mem + guest - RAM_BASE);
186+
}
187+
179188
void vm_irqfd_register(vm_t *v, int fd, int gsi, int flags)
180189
{
181190
struct kvm_irqfd irqfd = {

src/vm.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ int vm_enable_net(vm_t *v);
3535
int vm_run(vm_t *v);
3636
int vm_irq_line(vm_t *v, int irq, int level);
3737
void *vm_guest_to_host(vm_t *v, uint64_t guest);
38+
void *vm_guest_buf(vm_t *v, uint64_t guest, size_t len);
3839
void vm_irqfd_register(vm_t *v, int fd, int gsi, int flags);
3940
void vm_ioeventfd_register(vm_t *v,
4041
int fd,

0 commit comments

Comments
 (0)