From: Marc-André Lureau <[email protected]>
When the host chardev disconnects while the output queue is throttled,
virtio_serial_close() calls discard_throttle_data() which detaches the
in-flight element with virtqueue_detach_element(). This silently removes
the descriptor without placing it on the used ring, so the guest never
sees the write complete and the descriptor leaks.
Add return_throttle_data(), which pushes the element back via
virtqueue_push() instead of detaching it, and call it from
virtio_serial_close().
Fixes: d4c19cdeeb2f ("virtio-serial: add missing virtio_detach_element() call")
Signed-off-by: Marc-André Lureau <[email protected]>
---
hw/char/virtio-serial-bus.c | 16 +++++++++++++++-
1 file changed, 15 insertions(+), 1 deletion(-)
diff --git a/hw/char/virtio-serial-bus.c b/hw/char/virtio-serial-bus.c
index 623e9f17a83e..008cad1b625c 100644
--- a/hw/char/virtio-serial-bus.c
+++ b/hw/char/virtio-serial-bus.c
@@ -158,6 +158,20 @@ static void discard_throttle_data(VirtIOSerialPort *port)
}
}
+/*
+ * Return a partial element held back by throttling to the guest via
+ * the used ring, so the guest driver can reclaim the descriptor.
+ * Unlike discard_throttle_data() this must be used when the virtqueue
+ * stays live (port close) rather than being reset.
+ */
+static void return_throttle_data(VirtIOSerialPort *port)
+{
+ if (port->elem) {
+ virtqueue_push(port->ovq, port->elem, 0);
+ g_clear_pointer(&port->elem, g_free);
+ }
+}
+
static void do_flush_queued_data(VirtIOSerialPort *port, VirtQueue *vq,
VirtIODevice *vdev)
{
@@ -283,7 +297,7 @@ int virtio_serial_close(VirtIOSerialPort *port)
* consume, reset the throttling flag and discard the data.
*/
port->throttled = false;
- discard_throttle_data(port);
+ return_throttle_data(port);
discard_vq_data(port->ovq, VIRTIO_DEVICE(port->vser));
send_control_event(port->vser, port->id, VIRTIO_CONSOLE_PORT_OPEN, 0);
--
2.56.0.rc0.29.g47ce80527c56