Linux virtualization list
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Yaohan Li" <shunsheng1218@gmail.com>
Cc: virtualization@lists.linux.dev,
	"Michael S. Tsirkin" <mst@redhat.com>,
	"Eugenio Perez" <eperezma@redhat.com>
Subject: Re: [PATCH] virtio_console: guard read-side virtqueue access during unplug
Date: Wed, 04 Nov 2026 09:57:25 +0000	[thread overview]
Message-ID: <sashiko-outbox-197011@kernel.org> (raw)
In-Reply-To: <179378543196.71890.7555371342727156729@gmail.com>

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Critical] virtio_console: double free and UAF of port->inbuf in fill_readbuf()
- [Medium] virtio_console: lock gap in unplug_port() leaks port->inbuf on read race
--

commit a643b15852e7b766cdcc04cfb5d89a66c569bc43
Author: Yaohan Li <shunsheng1218@gmail.com>

virtio_console: guard read-side virtqueue access during unplug

This commit adds port->portdev checks under port->inbuf_lock in
port_has_data() and fill_readbuf(), and clears port->portdev under
port->inbuf_lock in unplug_port() to prevent read-side access to input
virtqueues during unplug.

> diff --git a/drivers/char/virtio_console.c b/drivers/char/virtio_console.c
> index 019bcae81af5c..e97e0daa34395 100644
> --- a/drivers/char/virtio_console.c
> +++ b/drivers/char/virtio_console.c

[ ... ]

> @@ -725,6 +731,16 @@ static ssize_t fill_readbuf(struct port *port, u8 __user *out_buf,
>  		spin_lock_irqsave(&port->inbuf_lock, flags);
>  		port->inbuf = NULL;
>  
> +		/*
> +		 * The port may have been unplugged while we were copying
> +		 * data out of the buffer: the vqs are then already gone.
> +		 */
> +		if (!port->portdev) {
> +			spin_unlock_irqrestore(&port->inbuf_lock, flags);
> +			free_buf(buf, true);
> +			return out_count;
> +		}

[Severity: Critical]
Can this free_buf(buf, true) call cause a double-free of buf when
fill_readbuf() races with unplug_port()?

In fill_readbuf(), port_has_data(port) acquires port->inbuf_lock, sets
port->inbuf = get_inbuf(port), and drops port->inbuf_lock while leaving
port->inbuf pointing to buf during the unlocked copy_to_user() or memcpy():

drivers/char/virtio_console.c:fill_readbuf() {
	if (!out_count || !port_has_data(port))
		return 0;

	buf = port->inbuf;
	out_count = min(out_count, buf->len - buf->offset);

	if (to_user) {
		ssize_t ret;

		ret = copy_to_user(out_buf, buf->buf + buf->offset, out_count);
		...
	}
	...
	buf->offset += out_count;
}

If unplug_port() runs concurrently while fill_readbuf() is unlocked, it calls
remove_port_data(port) -> discard_port_data(port) under port->inbuf_lock
before setting port->portdev = NULL:

drivers/char/virtio_console.c:discard_port_data() {
	if (!port->portdev) {
		/* Device has been unplugged.  vqs are already gone. */
		return;
	}
	buf = get_inbuf(port);

	err = 0;
	while (buf) {
		port->stats.bytes_discarded += buf->len - buf->offset;
		if (add_inbuf(port->in_vq, buf) < 0) {
			err++;
			free_buf(buf, false);
		}
		port->inbuf = NULL;
		buf = get_inbuf(port);
	}
}

Because port->inbuf still holds buf during the copy, get_inbuf(port) in
discard_port_data() returns that same active buf and either frees it via
free_buf(buf, false) (when port->in_vq is broken during device removal) or
re-queues it to port->in_vq (where remove_vqs() detaches and frees it), while
copy_to_user() is still reading from buf->buf or before fill_readbuf() loads
buf = port->inbuf (which is now NULL).

When fill_readbuf() finishes copying the buffer, it re-acquires
port->inbuf_lock, sees !port->portdev, and calls free_buf(buf, true) on the
same buf a second time.

> @@ -1568,6 +1584,18 @@ static void unplug_port(struct port *port)
>  	 * a close on an open port later from sending a stale control
>  	 * message.
>  	 */
> +	/*
> +	 * Null out portdev under inbuf_lock as well, so the read path
> +	 * (port_has_data(), fill_readbuf()) cannot race with the vq
> +	 * teardown: it checks port->portdev inside the same lock and
> +	 * bails out if NULL, preventing any virtqueue access to an
> +	 * already torn-down virtqueue — mirroring the outvq_lock fence
> +	 * below for the write path.
> +	 */
> +	spin_lock_irq(&port->inbuf_lock);
> +	port->portdev = NULL;
> +	spin_unlock_irq(&port->inbuf_lock);

[Severity: Medium]
Does clearing port->portdev in a separate lock acquisition after
remove_port_data(port) leave a race window that can leak port->inbuf?

In unplug_port(), remove_port_data(port) acquires port->inbuf_lock, runs
discard_port_data(port), and drops port->inbuf_lock before unplug_port()
re-acquires port->inbuf_lock here to set port->portdev = NULL:

drivers/char/virtio_console.c:remove_port_data() {
	spin_lock_irq(&port->inbuf_lock);
	/* Remove unused data this port might have received. */
	discard_port_data(port);
	spin_unlock_irq(&port->inbuf_lock);

	spin_lock_irq(&port->outvq_lock);
	reclaim_consumed_buffers(port);
	spin_unlock_irq(&port->outvq_lock);
}

If a concurrent reader calls port_has_data(port) in the gap after
discard_port_data(port) unlocks port->inbuf_lock and before port->portdev is
set to NULL, port_has_data(port) sees port->portdev != NULL and detaches a
buffer from port->in_vq into port->inbuf.

Once port->portdev is set to NULL, if port_fops_read() exits on
!port->guest_connected or fill_readbuf() only partially consumes the buffer
(or fails with -EFAULT), that detached buffer remains in port->inbuf and
discard_port_data() in port_fops_release() will bail out on !port->portdev
without freeing it.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/179378543196.71890.7555371342727156729@gmail.com?part=1

      reply	other threads:[~2026-11-04  9:57 UTC|newest]

Thread overview: 2+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-04  9:43 [PATCH] virtio_console: guard read-side virtqueue access during unplug Yaohan Li
2026-11-04  9:57 ` sashiko-bot [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=sashiko-outbox-197011@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=eperezma@redhat.com \
    --cc=mst@redhat.com \
    --cc=sashiko-reviews@lists.linux.dev \
    --cc=shunsheng1218@gmail.com \
    --cc=virtualization@lists.linux.dev \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox