From: Jia Jia <physicalmtea@gmail.com> mainline inclusion from mainline-v7.3-rc3 commit 894f98e73983f37354214a89a3a7fd35bf9e3072 category: bugfix bugzilla: NA Reference: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?i... -------------------------------- __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> Signed-off-by: Yao Yiqi <yaoyiqi3@huawei.com> --- drivers/char/virtio_console.c | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/drivers/char/virtio_console.c b/drivers/char/virtio_console.c index b6c966516946..6b926fd30a69 100644 --- a/drivers/char/virtio_console.c +++ b/drivers/char/virtio_console.c @@ -1961,13 +1961,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); + } } portdev->vdev->config->del_vqs(portdev->vdev); kfree(portdev->in_vqs); -- 2.34.1