@samitouri / QOSamiQemu / commits / 36b37f8494

hw/char/virtio-serial-bus: fix guest-triggerable OOM in control_out()

A malicious guest can craft virtqueue descriptors with arbitrary lengths. control_out() calls iov_size() on the guest-supplied scatter-gather list and passes the result directly to g_malloc(), allowing a guest to force QEMU to attempt multi-gigabyte allocations and crash the host process. Fix this by copying at most sizeof(struct virtio_console_control) into a stack-local variable instead of allocating a buffer sized by the guest. handle_control_message() only accesses the fixed-size id, event, and value fields, so no data beyond the struct was ever needed. Cc: qemu-stable@nongnu.org Resolves: https://gitlab.com/qemu-project/qemu/-/issues/3585 Signed-off-by: Laurent Vivier <lvivier@redhat.com> Reviewed-by: Michael S. Tsirkin <mst@redhat.com> Signed-off-by: Michael S. Tsirkin <mst@redhat.com> Message-ID: <20260622161144.2883799-1-lvivier@redhat.com>

Laurent Vivier committed Jun 22, 2026 at 18:11 UTC 36b37f8494800ed9c5d4b6ef03924bd1636cb810
1 file changed +7 -27
hw/char/virtio-serial-bus.c
+7 -27
@@ -344,22 +344,16 @@ void virtio_serial_throttle_port(VirtIOSerialPort *port, bool throttle)
344 }
345
346 /* Guest wants to notify us of some event */
347 -static void handle_control_message(VirtIOSerial *vser, void *buf, size_t len)
347 +static void handle_control_message(VirtIOSerial *vser,
348 + struct virtio_console_control *gcpkt)
349 {
350 VirtIODevice *vdev = VIRTIO_DEVICE(vser);
351 struct VirtIOSerialPort *port;
352 VirtIOSerialPortClass *vsc;
352 - struct virtio_console_control cpkt, *gcpkt;
353 + struct virtio_console_control cpkt;
354 uint8_t *buffer;
355 size_t buffer_len;
356
356 - gcpkt = buf;
357 -
358 - if (len < sizeof(cpkt)) {
359 - /* The guest sent an invalid control packet */
360 - return;
361 - }
362 -
357 cpkt.event = virtio_lduw_p(vdev, &gcpkt->event);
358 cpkt.value = virtio_lduw_p(vdev, &gcpkt->value);
359
@@ -457,41 +451,27 @@ static void control_in(VirtIODevice *vdev, VirtQueue *vq)
451
452 static void control_out(VirtIODevice *vdev, VirtQueue *vq)
453 {
454 + struct virtio_console_control cpkt;
455 VirtQueueElement *elem;
456 VirtIOSerial *vser;
462 - uint8_t *buf;
457 size_t len;
458
459 vser = VIRTIO_SERIAL(vdev);
460
467 - len = 0;
468 - buf = NULL;
461 for (;;) {
470 - size_t cur_len;
471 -
462 elem = virtqueue_pop(vq, sizeof(VirtQueueElement));
463 if (!elem) {
464 break;
465 }
466
477 - cur_len = iov_size(elem->out_sg, elem->out_num);
478 - /*
479 - * Allocate a new buf only if we didn't have one previously or
480 - * if the size of the buf differs
481 - */
482 - if (cur_len > len) {
483 - g_free(buf);
484 -
485 - buf = g_malloc(cur_len);
486 - len = cur_len;
467 + len = iov_to_buf(elem->out_sg, elem->out_num, 0, &cpkt, sizeof(cpkt));
468 + if (len == sizeof(cpkt)) {
469 + handle_control_message(vser, &cpkt);
470 }
488 - iov_to_buf(elem->out_sg, elem->out_num, 0, buf, cur_len);
471
490 - handle_control_message(vser, buf, cur_len);
472 virtqueue_push(vq, elem, 0);
473 g_free(elem);
474 }
494 - g_free(buf);
475 virtio_notify(vdev, vq);
476 }
477