From: sashiko-bot@kernel.org
To: "Brian Daniels" <briandaniels@google.com>
Cc: virtualization@lists.linux.dev, media-ci@linuxtv.org,
"Michael S. Tsirkin" <mst@redhat.com>,
"Eugenio Perez" <eperezma@redhat.com>
Subject: Re: [PATCH v10 1/5] media: virtio: Add core driver and session management
Date: Thu, 29 Oct 2026 15:49:29 +0000 [thread overview]
Message-ID: <sashiko-outbox-189640@kernel.org> (raw)
In-Reply-To: <20261029153038.3800747-2-briandaniels@google.com>
Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
- [Critical] media: virtio: UAF, NULL deref, and deadlock in virtio_media_remove()
- [Critical] media: virtio: stack UAF and vq race in commandq_callback()
- [Critical] media: virtio: dangling stack cb_param on virtqueue_kick() failure
- [Critical] media: virtio: unvalidated status negation in virtio_media_kick_command()
- [High] media: virtio: missing reset and caps check in virtio_media_probe()
- [Low] media: virtio: missing le32 conversions on virtio_media protocol fields
--
commit a1ad1972645a51f0e49e27a507fa954d0bc8dd41
Author: Alexandre Courbot <gnurou@gmail.com>
media: virtio: Add core driver and session management
This commit adds the initial virtio-media driver core, build system
integration, and V4L2 session lifecycle management via VIRTIO_MEDIA_CMD_OPEN
and VIRTIO_MEDIA_CMD_CLOSE over the command virtqueue.
> diff --git a/drivers/media/virtio/virtio_media_driver.c b/drivers/media/virtio/virtio_media_driver.c
> new file mode 100644
> index 0000000000000..10073d863b574
> --- /dev/null
> +++ b/drivers/media/virtio/virtio_media_driver.c
> @@ -0,0 +1,503 @@
[ ... ]
> +static int virtio_media_session_close(struct virtio_media *vv,
> + struct virtio_media_session *session)
> +{
> + struct virtio_media_cmd_close *cmd_close = &session->cmd.close;
> + struct scatterlist cmd_sg = {};
> + struct scatterlist *sgs[1] = { &cmd_sg };
> + int ret;
> +
> + mutex_lock(&vv->vlock);
> +
> + cmd_close->hdr.cmd = VIRTIO_MEDIA_CMD_CLOSE;
> + cmd_close->hdr.__reserved = 0;
> + cmd_close->session_id = session->id;
> + cmd_close->__reserved = 0;
[Severity: Low]
In virtio_media_session_close(), virtio_media_device_open(), and
virtio_media_kick_command(), the protocol fields hdr.cmd, hdr.status, and
session_id are declared as __le32 in include/uapi/linux/virtio_media.h, but
they are read and written directly as host integers.
Should these accesses use cpu_to_le32() and le32_to_cpu() to avoid sparse
endianness warnings when building with C=1?
[ ... ]
> +struct virtio_media_cmd_callback_param {
> + struct virtio_media *vv;
> + bool done;
> + size_t resp_len;
> +};
> +
> +/**
> + * commandq_callback() - Callback for the command queue.
> + * @queue: command virtqueue.
> + *
> + * This just wakes up the thread that was waiting on the command to complete.
> + */
> +static void commandq_callback(struct virtqueue *queue)
> +{
> + unsigned int len;
> + struct virtio_media_cmd_callback_param *param;
> +
> +process_bufs:
> + while ((param = virtqueue_get_buf(queue, &len))) {
> + param->resp_len = len;
> + param->done = true;
> + wake_up(¶m->vv->wq);
> + }
> +
> + if (!virtqueue_enable_cb(queue)) {
> + virtqueue_disable_cb(queue);
> + goto process_bufs;
> + }
> +}
> +
> +/**
> + * virtio_media_kick_command() - send a command to the commandq.
> + * @vv: virtio-media device in use.
> + * @sgs: descriptor chain to send.
> + * @out_sgs: number of device-readable descriptors in @sgs.
> + * @in_sgs: number of device-writable descriptors in @sgs.
> + * @resp_len: output parameter. Upon success, contains the size of the response
> + * in bytes.
> + */
> +static int virtio_media_kick_command(struct virtio_media *vv,
> + struct scatterlist **sgs,
> + const size_t out_sgs, const size_t in_sgs,
> + size_t *resp_len)
> +{
> + struct virtio_media_cmd_callback_param cb_param = {
> + .vv = vv,
> + .done = false,
> + .resp_len = 0,
> + };
> + struct virtio_media_resp_header *resp_header;
> + int ret = virtqueue_add_sgs(vv->commandq, sgs, out_sgs, in_sgs,
> + &cb_param, GFP_ATOMIC);
> + if (ret) {
> + v4l2_err(&vv->v4l2_dev,
> + "failed to add sgs to command virtqueue\n");
> + return ret;
> + }
> +
> + if (!virtqueue_kick(vv->commandq)) {
> + v4l2_err(&vv->v4l2_dev, "failed to kick command virtqueue\n");
> + return -EINVAL;
> + }
[Severity: Critical]
If virtqueue_kick() returns false here in virtio_media_kick_command(), the
stack-allocated cb_param token and the DMA-mapped caller scatterlists have
already been added to vv->commandq by virtqueue_add_sgs().
When virtio_media_kick_command() returns -EINVAL without detaching the unused
buffer or resetting the device, cb_param goes out of scope while still queued
in vv->commandq, and in the close path:
virtio_media_device_close()
virtio_media_session_close() -> fails with -EINVAL
virtio_media_session_remove()
virtio_media_session_free() -> kfree(session)
session->cmd.close is freed while still queued in the virtqueue. Could a
subsequent completion in commandq_callback() dereference the dangling stack
pointer param and access freed session memory?
> +
> + /* Wait for the response. */
> + wait_event(vv->wq, cb_param.done);
[Severity: Critical]
Can commandq_callback() race with virtio_media_kick_command() and trigger a
stack use-after-return or vring state corruption?
In virtio_media_kick_command(), cb_param lives on the stack, and wait_event()
checks cb_param.done locklessly in its fast path before taking the waitqueue
lock. In commandq_callback():
param->resp_len = len;
param->done = true;
wake_up(¶m->vv->wq);
As soon as param->done = true becomes visible on another CPU,
virtio_media_kick_command() can finish wait_event() and return from the
function before commandq_callback() evaluates ¶m->vv->wq on the next
line, dereferencing the popped stack frame (for example, queue->vdev->priv
could be used to reach vv instead of param->vv, or a struct completion could
be used). In addition, param->resp_len and param->done are written and read
without memory barriers (such as smp_store_release() and smp_load_acquire())
when wait_event() takes its lockless fast path.
Furthermore, callers serialize access to vv->commandq only with the mutex
vv->vlock, whereas commandq_callback() runs in interrupt context without a
spinlock. Once a woken caller returns, releases vv->vlock, and issues a new
virtqueue_add_sgs() and virtqueue_kick() (or virtio_media_remove() detaches
buffers), can that run concurrently with commandq_callback() still executing
virtqueue_get_buf() and virtqueue_enable_cb() on vv->commandq?
> +
> + if (resp_len)
> + *resp_len = cb_param.resp_len;
> +
> + if (in_sgs > 0) {
> + /*
> + * If we expect a response, make sure we have at least a
> + * response header - anything shorter is invalid.
> + */
> + if (cb_param.resp_len < sizeof(*resp_header)) {
> + v4l2_err(&vv->v4l2_dev,
> + "received response header is too short\n");
> + return -EINVAL;
> + }
> +
> + resp_header = sg_virt(sgs[out_sgs]);
> + if (resp_header->status)
> + /* Host returns a positive error code. */
> + return -resp_header->status;
[Severity: Critical]
What happens if the host writes an out-of-range value into
resp_header->status (which is a 32-bit unsigned __le32)?
If resp_header->status has bit 31 set (0x80000001..0xffffffff, such as a
negative errno cast to u32), negating it yields a positive int when returned
from virtio_media_kick_command(). Because virtio_media_send_command() only
checks:
if (ret < 0)
return ret;
it treats positive return values as success and returns 0, causing
virtio_media_device_open() to succeed using an uninitialized or stale
resp_open->session_id.
Conversely, if resp_header->status is in (MAX_ERRNO, 0x80000000],
virtio_media_kick_command() returns a negative value smaller than -MAX_ERRNO.
When propagated from virtio_media_device_open() through v4l2_open() and
do_dentry_open() to path_openat(), ERR_PTR(ret) will not satisfy IS_ERR(),
which can cause VFS open to install an invalid pointer into the process file
descriptor table. Should status values outside 1..MAX_ERRNO be mapped to a
fallback error such as -EINVAL or -EIO?
[ ... ]
> +static int virtio_media_probe(struct virtio_device *virtio_dev)
> +{
[ ... ]
> + virtio_cread_le(virtio_dev, struct virtio_media_config, device_caps,
> + &vd->device_caps);
> + if (vd->device_caps & (V4L2_CAP_VIDEO_M2M | V4L2_CAP_VIDEO_M2M_MPLANE))
> + vd->vfl_dir = VFL_DIR_M2M;
> + else if (vd->device_caps &
> + (V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_VIDEO_OUTPUT_MPLANE))
> + vd->vfl_dir = VFL_DIR_TX;
> + else
> + vd->vfl_dir = VFL_DIR_RX;
> +
> + virtio_device_ready(virtio_dev);
> +
> + __u32 device_type;
> +
> + virtio_cread_le(virtio_dev, struct virtio_media_config, device_type,
> + &device_type);
> + if (device_type >= VFL_TYPE_MAX) {
> + dev_err(&virtio_dev->dev, "Invalid device_type: %u",
> + device_type);
> + ret = -EINVAL;
> + goto err_del_vqs;
> + }
> +
> + ret = video_register_device(vd, device_type, -1);
> + if (ret)
> + goto err_del_vqs;
> +
> + return 0;
> +
> +err_del_vqs:
> + virtio_dev->config->del_vqs(virtio_dev);
> +err_v4l2_device_unregister:
> + v4l2_device_unregister(&vv->v4l2_dev);
> +err_free_virtio_media:
> + kfree(vv);
> + return ret;
> +}
[Severity: High]
In virtio_media_probe(), virtio_device_ready(virtio_dev) is called before
validating device_type and before video_register_device(). If either check
fails and jumps to err_del_vqs, virtio_dev->config->del_vqs(virtio_dev) and
kfree(vv) are called without calling virtio_reset_device(virtio_dev) first,
leaving VIRTIO_CONFIG_S_DRIVER_OK set while the virtqueues and driver state
are freed.
In addition, vd->device_caps is read from config space without checking that
it is non-zero; if device_type != VFL_TYPE_SUBDEV and vd->device_caps == 0,
__video_register_device() triggers a WARN_ON(!vdev->device_caps) and fails
into err_del_vqs.
Should vd->device_caps and device_type be validated before
virtio_device_ready(), and should err_del_vqs reset the virtio device before
deleting the virtqueues?
> +
> +static void virtio_media_remove(struct virtio_device *virtio_dev)
> +{
> + struct virtio_media *vv = virtio_dev->priv;
> + struct list_head *p, *n;
> +
> + /* Remove the video device to stop accessing the commandq virtqueue */
> + video_unregister_device(&vv->video_dev);
> +
> + /*
> + * Acquire vlock to ensure no concurrent accesses to the commandq
> + * virtqueue
> + */
> + mutex_lock(&vv->vlock);
> +
> + /* Stop all virtqueue callbacks */
> + virtio_reset_device(virtio_dev);
> +
> + /*
> + * Only detach the unused buffers to allow internal virtqueue clean up.
> + * The actual buffers are freed in virtio_media_release()
> + */
> + while (virtqueue_detach_unused_buf(vv->commandq))
> + ;
> +
> + /* Delete the virtqueues */
> + virtio_dev->config->del_vqs(virtio_dev);
> +
> + mutex_unlock(&vv->vlock);
> +
> + list_for_each_safe(p, n, &vv->sessions) {
> + struct virtio_media_session *s =
> + list_entry(p, struct virtio_media_session, list);
> +
> + virtio_media_session_remove(vv, s);
> + virtio_media_session_free(s);
> + }
> +
> + v4l2_device_put(&vv->v4l2_dev);
> +}
[Severity: Critical]
What happens in virtio_media_remove() if the virtio device is unbound or
hot-unplugged while userspace still has an open file descriptor (or an
in-flight open/close)?
First, video_unregister_device() only clears V4L2_FL_REGISTERED and unlinks
the device node; it does not close open struct file instances or wait for
in-flight file operations. In virtio_media_remove(), the loop over
vv->sessions (which also iterates without holding vv->sessions_lock) frees
every active session via virtio_media_session_free(), which calls:
virtio_media_session_fh_del(session)
v4l2_fh_del(&session->fh, session->file)
filp->private_data = NULL;
kfree(session);
and virtio_dev->config->del_vqs(virtio_dev) frees vv->commandq. When
userspace later closes its open file descriptor, v4l2_release() still invokes
virtio_media_device_close():
virtio_media_device_close(file)
session = fh_to_session(file->private_data); -> NULL (or freed session)
virtio_media_session_close(vv, session)
cmd_close->session_id = session->id; -> NULL dereference / UAF
virtio_media_send_command(vv, ...) -> uses deleted vv->commandq (UAF)
virtio_media_session_remove(vv, session);
virtio_media_session_free(session); -> double free
Similarly, a concurrent virtio_media_device_open() or
virtio_media_device_close() waiting on mutex_lock(&vv->vlock) during
virtio_media_remove() will acquire vv->vlock after del_vqs() has already
destroyed vv->commandq and attempt to submit a command on the freed
virtqueue.
Second, if a command is in flight in virtio_media_kick_command() waiting in
uninterruptible wait_event(vv->wq, cb_param.done) while holding vv->vlock and
the device stops responding during hot-unplug, won't virtio_media_remove()
deadlock forever on mutex_lock(&vv->vlock) before it can reach
virtio_reset_device()?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261029153038.3800747-1-briandaniels@google.com?part=1
next prev parent reply other threads:[~2026-10-29 15:49 UTC|newest]
Thread overview: 11+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-29 15:30 [PATCH v10 0/5] media: add virtio-media driver Brian Daniels
2026-10-29 15:30 ` [PATCH v10 1/5] media: virtio: Add core driver and session management Brian Daniels
2026-10-29 15:49 ` sashiko-bot [this message]
2026-10-29 15:30 ` [PATCH v10 2/5] media: virtio: Add V4L2 ioctl forwarding infrastructure Brian Daniels
2026-10-29 16:15 ` sashiko-bot
2026-10-29 15:30 ` [PATCH v10 3/5] media: virtio: Add extended controls support Brian Daniels
2026-10-29 16:37 ` sashiko-bot
2026-10-29 15:30 ` [PATCH v10 4/5] media: virtio: Add buffer streaming, event handling, and poll Brian Daniels
2026-10-29 17:00 ` sashiko-bot
2026-10-29 15:30 ` [PATCH v10 5/5] media: virtio: Add MMAP buffer mapping support Brian Daniels
2026-10-29 17:19 ` sashiko-bot
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-189640@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=briandaniels@google.com \
--cc=eperezma@redhat.com \
--cc=media-ci@linuxtv.org \
--cc=mst@redhat.com \
--cc=sashiko-reviews@lists.linux.dev \
--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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.