summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJia Jia <physicalmtea@gmail.com>2026-08-19 10:12:30 +0800
committerMichael S. Tsirkin <mst@redhat.com>2026-09-07 18:54:02 -0400
commit894f98e73983f37354214a89a3a7fd35bf9e3072 (patch)
treea8a4a55fa8d850aaaf247e30c438c58486d9e5ea
parent3f9a0fceb730f5107d52421ead5568eae25a0049 (diff)
virtio_console: do not free control-out buffers on remove
__send_control_msg() publishes &portdev->cpkt as the control-out virtqueue cookie. remove_vqs() walks every virtqueue and passes leftover cookies to free_buf(), which treats them as struct port_buffer and reads sgpages. If a control message is still on c_ovq when the device is unbound, free_buf() reads past the ports_device object. KASAN reported slab-out-of-bounds in free_buf(): free_buf remove_vqs virtcons_remove unbind_store The object was the ports_device allocated in virtcons_probe(). Drain c_ovq without freeing. The packet lives in portdev and is released with it. Fixes: a7a69ec0d8e4 ("virtio_console: free buffers after reset") Signed-off-by: Jia Jia <physicalmtea@gmail.com> Signed-off-by: Michael S. Tsirkin <mst@redhat.com> Message-ID: <20260819021230.292696-1-physicalmtea@gmail.com>
-rw-r--r--drivers/char/virtio_console.c21
1 files changed, 18 insertions, 3 deletions
diff --git a/drivers/char/virtio_console.c b/drivers/char/virtio_console.c
index 7f6cbe851d1e..019bcae81af5 100644
--- a/drivers/char/virtio_console.c
+++ b/drivers/char/virtio_console.c
@@ -1964,13 +1964,28 @@ static const struct file_operations portdev_fops = {
static void remove_vqs(struct ports_device *portdev)
{
struct virtqueue *vq;
+ bool multiport = use_multiport(portdev);
virtio_device_for_each_vq(portdev->vdev, vq) {
struct port_buffer *buf;
+ unsigned int len;
- flush_bufs(vq, true);
- while ((buf = virtqueue_detach_unused_buf(vq)))
- free_buf(buf, true);
+ /*
+ * c_ovq cookies are &portdev->cpkt, not port_buffer.
+ * Detach them but do not free_buf().
+ */
+ if (multiport && vq == portdev->c_ovq) {
+ spin_lock(&portdev->c_ovq_lock);
+ while (virtqueue_get_buf(vq, &len))
+ ;
+ while (virtqueue_detach_unused_buf(vq))
+ ;
+ spin_unlock(&portdev->c_ovq_lock);
+ } else {
+ flush_bufs(vq, true);
+ while ((buf = virtqueue_detach_unused_buf(vq)))
+ free_buf(buf, true);
+ }
cond_resched();
}
portdev->vdev->config->del_vqs(portdev->vdev);