From: Karl Mehltretter <hidden> Date: 2026-09-08 05:39:40
This is v3 of the callback synchronization series. It is based on
Michael S. Tsirkin's linux-next branch at f49e6cf91942 ("virtio:
synchronize callbacks after device reset"), which already contains the
core change and the virtio_input teardown reorder from v2.
Patches 1 and 2 fix two bugs in virtio-ccw's existing
synchronize_cbs() hook. After a fallback from adapter to classic
interrupts it selects the wrong lock, and the classic interrupt handler
only takes the matching lock when notification hardening is enabled.
Patch 3 adds SRCU tracking for remoteproc callbacks, which can sleep.
Patches 4 and 5 replace the RCU fallback with synchronization against
the UML IRQ and the TmFIFO callback locks. The fallback already covers
these IRQ handlers and spinlock sections. Patch 6 adds SRCU tracking
in virtio_vdpa so callback synchronization does not depend on the
context in which the vDPA driver invokes the callback.
The UML, TmFIFO and remoteproc reset paths do not themselves prevent
new virtqueue callbacks. UML and TmFIFO only clear a status field, and
remoteproc does not wait for the remote side to acknowledge the reset.
Without notification hardening or driver-specific teardown protection,
callbacks can still start after reset. The new synchronization hooks
do not fix that. The TmFIFO hook also does not synchronize with the
rest of the FIFO work item outside the callback locks.
The virtio_input loop change from v2 (continue instead of break, so
events the device already completed are still delivered) and the
event buffer leak are sent separately.
Changes in v3:
- Rebased on Michael's linux-next branch. Dropped the core change and
the virtio_input patch, which are there already.
- Split the virtio-ccw fixes out of the core patch, one per bug, and
the transport patch into one patch per transport. The CCW and TmFIFO
changes are functionally unchanged from v2.
- Patch 2: added a Fixes tag and described the existing shutdown
case. Removed the dependency note on the core reset change.
- remoteproc: read the queue pointer once in rproc_vq_interrupt(), and
synchronize with callbacks in __rproc_virtio_del_vqs() before
freeing the queues (Sashiko). Place vq_srcu next to rvdevs so the
hunk also applies to mainline, which added attach_work after index.
- virtio_uml: compare against UM_IRQ_ALLOC instead of a bare negative
check.
- virtio_vdpa: use SRCU instead of a per-device rwlock, so the
callback tracking uses per-CPU counters, and cover the config
callback (Sashiko).
- Rewrote the commit messages. Corrected the RCU fallback description
for UML, TmFIFO and the vDPA simulators. Dropped the claimed simulator
reset race: the simulators disable bottom halves around virtqueue
callbacks and serialize reset with the worker's mutex.
Changes in v2:
- Moved callback synchronization from virtio-pci into the core, as
Michael suggested, and added the missing synchronize_cbs() hooks.
Testing: the changed objects build with W=1 without warnings, with
clang on arm64, x86-64 and s390 and with gcc on SMP UML, and the
patches also apply to current mainline and linux-next. The runtime
tests from v2 were not repeated on this version: the input, rebind and
shutdown checks on arm64 MMIO and x86 PCI covered code that is
unchanged here, and the remoteproc and virtio_vdpa hooks have only
been build-tested. No remoteproc, TmFIFO or s390 hardware was
available.
v2: https://lore.kernel.org/r/20260905152059.89560-1-kmehltretter@gmail.com
v1: https://lore.kernel.org/r/20260818040433.66986-1-kmehltretter@gmail.com
Karl Mehltretter (6):
virtio_ccw: fix synchronize_cbs() after interrupt fallback
virtio_ccw: always take irq_lock in the classic interrupt handler
remoteproc: implement synchronize_cbs() for virtio devices
um: virtio_uml: implement synchronize_cbs()
platform/mellanox: mlxbf-tmfifo: implement synchronize_cbs()
virtio_vdpa: implement synchronize_cbs()
arch/um/drivers/virtio_uml.c | 10 +++++++
drivers/platform/mellanox/mlxbf-tmfifo.c | 15 ++++++++++
drivers/remoteproc/remoteproc_core.c | 12 ++++++++
drivers/remoteproc/remoteproc_virtio.c | 37 +++++++++++++++++++-----
drivers/s390/virtio/virtio_ccw.c | 6 +---
drivers/virtio/virtio_vdpa.c | 34 ++++++++++++++++++++--
include/linux/remoteproc.h | 3 ++
7 files changed, 102 insertions(+), 15 deletions(-)
base-commit: f49e6cf919425cc55f10fd7cda7e0fc895df4cc0
--
2.39.5 (Apple Git-154)
From: Karl Mehltretter <hidden> Date: 2026-09-08 05:39:41
virtio_ccw_synchronize_cbs() assumes the device uses adapter interrupts
when vcdev->airq_info is not NULL. If registering adapter indicators
fails, virtio_ccw_find_vqs() falls back to classic interrupts but can
leave airq_info set.
With VIRTIO_HARDEN_NOTIFICATION enabled,
virtio_ccw_int_handler() invokes callbacks under vcdev->irq_lock.
synchronize_cbs() instead takes the indicator area lock, so it does
not synchronize with those callbacks.
Use vcdev->is_thinint, which the fallback clears, to select the lock.
Keep the airq_info check because is_thinint can be set before the
first find_vqs() call, when airq_info is still NULL.
Fixes: 3a232277c1ac ("virtio-ccw: implement synchronize_cbs()")
Assisted-by: LLM
Signed-off-by: Karl Mehltretter <redacted>
---
drivers/s390/virtio/virtio_ccw.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
From: Karl Mehltretter <hidden> Date: 2026-09-08 05:39:43
virtio_ccw_int_handler() takes vcdev->irq_lock around its
vring_interrupt() calls only when VIRTIO_HARDEN_NOTIFICATION is enabled.
The virtio shutdown path calls synchronize_cbs() even when notification
hardening is disabled. virtio_reset_device() now does so after the
transport reset too.
For classic interrupts, synchronize_cbs() takes vcdev->irq_lock for
writing, but the interrupt handler does not take the matching read
lock in that configuration. synchronize_cbs() can return while a
callback is still running.
Take the read lock unconditionally so the write side can synchronize
with the callbacks.
Fixes: 8bd2fa086a04 ("virtio: break and reset virtio devices on device_shutdown()")
Assisted-by: LLM
Signed-off-by: Karl Mehltretter <redacted>
---
drivers/s390/virtio/virtio_ccw.c | 4 ----
1 file changed, 4 deletions(-)
@@ -1204,13 +1204,11 @@ static void virtio_ccw_int_handler(struct ccw_device *cdev,vcdev->err=-EIO;}virtio_ccw_check_activity(vcdev,activity);-#ifdef CONFIG_VIRTIO_HARDEN_NOTIFICATION/**Pairedwithvirtio_ccw_synchronize_cbs()andinterruptsare*disabledhere.*/read_lock(&vcdev->irq_lock);-#endiffor_each_set_bit(i,indicators(vcdev),sizeof(*indicators(vcdev))*BITS_PER_BYTE){/* The bit clear must happen before the vring kick. */
From: Karl Mehltretter <hidden> Date: 2026-09-08 05:39:45
Platform drivers invoke rproc_vq_interrupt() from hard-IRQ handlers,
threaded handlers, and work items. Because rpmsg callbacks may sleep,
the virtio core's synchronize_rcu() fallback does not synchronize with
callbacks across all these contexts. A device reset can therefore
complete while a callback is still running.
Add an SRCU domain per rproc. Protect both the queue lookup and
vring_interrupt() with it, and synchronize the domain in the new hook.
__rproc_virtio_del_vqs() can race with rproc_vq_interrupt() too. Clear
all queue pointers and synchronize the SRCU domain before freeing the
queues, so callers that already found a queue can finish using it.
Read rvring->vq once to avoid a second load after deletion starts.
The SRCU domain has the same lifetime as struct rproc. Its cleanup can
sleep, so document that rproc_free() and rproc_put() must not drop the
last reference from atomic context.
Assisted-by: LLM
Signed-off-by: Karl Mehltretter <redacted>
---
drivers/remoteproc/remoteproc_core.c | 12 ++++++++
drivers/remoteproc/remoteproc_virtio.c | 37 +++++++++++++++++++++-----
include/linux/remoteproc.h | 3 +++
3 files changed, 45 insertions(+), 7 deletions(-)
@@ -165,14 +174,20 @@ static struct virtqueue *rp_find_vq(struct virtio_device *vdev,staticvoid__rproc_virtio_del_vqs(structvirtio_device*vdev){+structrproc*rproc=vdev_to_rproc(vdev);structvirtqueue*vq,*n;structrproc_vring*rvring;-list_for_each_entry_safe(vq,n,&vdev->vqs,list){+list_for_each_entry(vq,&vdev->vqs,list){rvring=vq->priv;-rvring->vq=NULL;-vring_del_virtqueue(vq);+WRITE_ONCE(rvring->vq,NULL);}++/* Synchronize with rproc_vq_interrupt() callers that found a queue. */+synchronize_srcu(&rproc->vq_srcu);++list_for_each_entry_safe(vq,n,&vdev->vqs,list)+vring_del_virtqueue(vq);}staticvoidrproc_virtio_del_vqs(structvirtio_device*vdev)
@@ -242,6 +257,13 @@ static void rproc_virtio_reset(struct virtio_device *vdev)dev_dbg(&vdev->dev,"reset !\n");}+staticvoidrproc_virtio_synchronize_cbs(structvirtio_device*vdev)+{+structrproc*rproc=vdev_to_rproc(vdev);++synchronize_srcu(&rproc->vq_srcu);+}+/* provide the vdev features as retrieved from the firmware */staticu64rproc_virtio_get_features(structvirtio_device*vdev){
From: Karl Mehltretter <hidden> Date: 2026-09-08 05:39:47
virtio_uml invokes virtqueue callbacks from vu_interrupt() and
vu_req_interrupt(), which share vu_dev->irq. The core's
synchronize_rcu() fallback already covers these IRQ handlers.
Implement synchronize_cbs() with synchronize_irq() to synchronize with
that IRQ instead of waiting for an RCU grace period.
Assisted-by: LLM
Signed-off-by: Karl Mehltretter <redacted>
---
arch/um/drivers/virtio_uml.c | 10 ++++++++++
1 file changed, 10 insertions(+)
From: Karl Mehltretter <hidden> Date: 2026-09-08 05:39:48
TmFIFO invokes virtqueue callbacks from its FIFO work item under
spin_lock_irqsave(). The core's synchronize_rcu() fallback already
covers those sections.
Implement synchronize_cbs() using the existing per-direction locks
instead of waiting for an RCU grace period. fifo->spin_lock[0] covers
the transmit callbacks, including the callback from the timeout
handler. fifo->spin_lock[1] covers the receive callbacks. Take and
release each lock in turn to synchronize with callbacks running
under that lock.
Assisted-by: LLM
Signed-off-by: Karl Mehltretter <redacted>
---
drivers/platform/mellanox/mlxbf-tmfifo.c | 15 +++++++++++++++
1 file changed, 15 insertions(+)
@@ -1135,6 +1135,20 @@ static void mlxbf_tmfifo_virtio_reset(struct virtio_device *vdev)tm_vdev->status=0;}+/* Synchronize with callbacks running in the FIFO work item. */+staticvoidmlxbf_tmfifo_virtio_synchronize_cbs(structvirtio_device*vdev)+{+structmlxbf_tmfifo_vdev*tm_vdev=mlxbf_vdev_to_tmfifo(vdev);+structmlxbf_tmfifo*fifo=tm_vdev->vrings[0].fifo;+unsignedlongflags;+inti;++for(i=0;i<ARRAY_SIZE(fifo->spin_lock);i++){+spin_lock_irqsave(&fifo->spin_lock[i],flags);+spin_unlock_irqrestore(&fifo->spin_lock[i],flags);+}+}+/* Read the value of a configuration field. */staticvoidmlxbf_tmfifo_virtio_get(structvirtio_device*vdev,unsignedintoffset,
From: Karl Mehltretter <hidden> Date: 2026-09-08 05:39:50
virtio_vdpa relies on the core's synchronize_rcu() fallback for callback
synchronization. That covers IRQ handlers and sections with interrupts,
preemption or bottom halves disabled. virtio_vdpa does not enforce any
of those contexts when a vDPA driver invokes a callback.
Add an SRCU domain per device and enter it around the virtqueue and
config callbacks. Implement synchronize_cbs() with synchronize_srcu()
so it covers the callbacks regardless of the calling context.
SRCU uses per-CPU reader accounting, avoiding a single callback lock
shared by all queues. Cover the config callback too, as virtio-pci
does for its config vector.
Assisted-by: LLM
Signed-off-by: Karl Mehltretter <redacted>
---
drivers/virtio/virtio_vdpa.c | 34 +++++++++++++++++++++++++++++++---
1 file changed, 31 insertions(+), 3 deletions(-)
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
Pre-existing issues:
- [Critical] virtio_ccw_reset() silently fails to reset the device if ccw allocation fails, risking use-after-free and DMA corruption.
- [High] virtio_ccw_del_vqs silently aborts on memory allocation failure, permanently leaking all virtqueues and their DMA memory.
- [High] Silent allocation failure in virtio_ccw_drop_indicator leaves stale virtqueue pointers in global routing tables, leading to UAF when adapter interrupts fire.
--
commit bd43fb4d1f3ef8003538155a318dcf69a27e4625
Author: Karl Mehltretter [off-list ref]
virtio_ccw: fix synchronize_cbs() after interrupt fallback
virtio_ccw_synchronize_cbs() assumes the device uses adapter interrupts
when vcdev->airq_info is not NULL. If registering adapter indicators
fails, virtio_ccw_find_vqs() falls back to classic interrupts but can
leave airq_info set. This patch uses vcdev->is_thinint to properly
select the lock.
[Severity: High]
This is a pre-existing issue, but returning early here on allocation failure
skips deregistering indicators and removing stale pointers from the global
airq_areas array.
If ccw_device_dma_zalloc() fails, the caller virtio_ccw_del_vqs() continues
and frees all virtqueues, while the global airq_areas array retains pointers
to them.
A subsequent adapter interrupt in virtio_airq_handler() could then fetch the
stale pointer:
vring_interrupt(0, (void *)airq_iv_get_ptr(info->aiv, ai));
and trigger a use-after-free on the freed virtqueue memory.
Does this early return need to gracefully handle indicator deregistration
when allocation fails?
[ ... ]
[Severity: High]
This isn't a bug introduced by this patch, but returning early on allocation
failure skips the teardown loop entirely.
This leaks all virtqueues and their DMA memory. Since the virtqueues are not
deregistered from the host or removed from the global airq_areas routing
array, the virtio core will subsequently free the parent virtio_ccw_device.
A later adapter interrupt could invoke vring_interrupt() on the leaked
virtqueue, holding a stale pointer to the freed device and potentially
causing a use-after-free.
Should this fallback to a safe teardown path if ccw allocation fails?
[ ... ]
[Severity: Critical]
This is a pre-existing issue, but this silent return bypasses sending the
CCW_CMD_VDEV_RESET command to the host if allocation fails.
The virtio core assumes the device is fully quiesced after virtio_ccw_reset()
returns and proceeds to free virtqueues and data buffers. Since the device on
the host was never actually reset, the hardware state machine remains active
and may continue to execute pending DMA operations into the now-freed memory,
causing critical data corruption.
How should we ensure the device is safely quiesced if ccw allocation fails?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260908053817.26065-1-kmehltretter@gmail.com?part=1
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Pre-existing issues:
- [High] Premature clearing of dynamically allocated IRQ bit in um_free_irq() causes unintended IRQ sharing and race conditions.
- [High] Resource leak and potential Use-After-Free on vhost_user_init() error path.
--
commit 88d671cc22ee3a67541ca7191122aa3e1f6d8c9f
Author: Karl Mehltretter [off-list ref]
um: virtio_uml: implement synchronize_cbs()
This commit implements synchronize_cbs() for virtio_uml by using
synchronize_irq() instead of waiting for an RCU grace period. This
synchronizes with the IRQ handlers used by virtqueue callbacks.
[Severity: High]
This isn't a bug introduced by this patch, but it is a pre-existing issue in
the codebase.
When a virtqueue is deleted during teardown, vu_del_vq() calls
um_free_irq(vu_dev->irq, vq) to remove the callback for that specific queue.
However, looking at um_free_irq() in arch/um/kernel/irq.c, it lacks reference
counting and unconditionally clears the dynamically allocated IRQ bit:
arch/um/kernel/irq.c:um_free_irq() {
free_irq(irq, dev);
clear_bit(irq, irqs_allocated);
}
Since virtio_uml shares a single dynamically allocated IRQ (vu_dev->irq)
across multiple virtqueues and the slave request channel using IRQF_SHARED,
could this premature clearing of the IRQ bit mark it as free for global
reallocation while still in use?
It seems concurrent requests for a new dynamic IRQ from other devices could be
assigned this prematurely freed IRQ, resulting in unintended forced sharing or
-EBUSY probe failures.
[ ... ]
[Severity: High]
This is also a pre-existing issue, but there appears to be a resource leak
and potential use-after-free in the probe error path of virtio_uml.
In virtio_uml_probe(), vhost_user_init() is called. If the slave request
channel is negotiated, vhost_user_init_slave_req() creates a pipe, assigns
req_fds[0] to vu_dev->req_fd, and requests an IRQ handler via
um_request_irq_tt():
arch/um/drivers/virtio_uml.c:vhost_user_init() {
if (vu_dev->protocol_features &
BIT_ULL(VHOST_USER_PROTOCOL_F_SLAVE_REQ)) {
rc = vhost_user_init_slave_req(vu_dev);
if (rc)
return rc;
}
if (vu_dev->protocol_features &
BIT_ULL(VHOST_USER_PROTOCOL_F_MQ)) {
rc = vhost_user_get_queue_num(vu_dev, &vu_dev->max_vqs);
if (rc)
return rc;
}
If a subsequent step like vhost_user_get_queue_num() fails, it returns the
error code directly. Back in virtio_uml_probe(), the error path is taken:
arch/um/drivers/virtio_uml.c:virtio_uml_probe() {
error_init:
os_close_file(vu_dev->sock);
error_free:
kfree(vu_dev);
return rc;
}
Does this error path free vu_dev without unregistering the active IRQ
handler or closing the vu_dev->req_fd file descriptor?
If an untrusted userspace backend forces the probe to fail, could the leaked
IRQ handler subsequently fire and access the freed vu_dev memory, causing a
use-after-free?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260908053817.26065-1-kmehltretter@gmail.com?part=4
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Pre-existing issues:
- [High] The virtio interrupt handlers pass retrieved virtqueue pointers directly to `vring_interrupt()` without a NULL check. A malicious or buggy host can trigger a kernel panic by providing an invalid indicator or interrupt vector.
- [High] Lost configuration change notifications due to clearing the indicator bit after processing.
--
commit 40545ed73d4381a07a7934fddf3ac60ecd1f90d7
Author: Karl Mehltretter [off-list ref]
Subject: virtio_ccw: always take irq_lock in the classic interrupt handler
This commit unconditionally takes the read lock on vcdev->irq_lock in
virtio_ccw_int_handler() so the write side can properly synchronize with
the callbacks even when notification hardening is disabled.
[Severity: High]
This is a pre-existing issue, but is it possible for virtio_ccw_vq_by_ind()
to return a NULL virtqueue pointer if a malicious or buggy host provides an
invalid indicator?
Passing an unvalidated NULL pointer directly to vring_interrupt() can cause a
kernel panic when it is dereferenced inside more_used():
drivers/virtio/virtio_ring.c:vring_interrupt() {
...
if (!more_used(vq)) {
pr_debug("virtqueue interrupt with no work for %p\n", vq);
...
}
Similarly, in virtio_airq_handler(), airq_iv_get_ptr() can return NULL which
is unconditionally passed to vring_interrupt().
Could we add a NULL check before calling the interrupt handler?
[Severity: High]
This isn't a bug introduced by this patch, but could this sequence lead to
lost configuration change notifications?
Because indicators2 is shared DMA memory updated by the host, if the host
triggers another configuration change in the window between test_bit() and
clear_bit(), it sets the bit in memory and raises a new interrupt.
However, the guest will then blindly execute clear_bit() and wipe out the
newly set notification flag. When the newly queued interrupt handler runs,
it will find the bit cleared and do nothing, permanently losing the event.
Would using test_and_clear_bit() before calling virtio_config_changed()
close this race window?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260908053817.26065-1-kmehltretter@gmail.com?part=2
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] Possible Use-After-Free of virtqueue memory in callback during find_vqs error handling
--
commit 05a7e5947030bc4c942e05c96bf1d657ed426d56
Author: Karl Mehltretter [off-list ref]
virtio_vdpa: implement synchronize_cbs()
virtio_vdpa relies on the core's synchronize_rcu() fallback for callback
synchronization. This commit adds an SRCU domain per device and implements
synchronize_cbs() with synchronize_srcu() to cover the callbacks regardless
of the calling context.
[Severity: High]
This is a pre-existing issue, but could this callback be invoked
concurrently on a freed vq, leading to a use-after-free when
dereferencing vq->vdev?
During driver probe, if virtio_vdpa_find_vqs() successfully creates some
virtqueues but fails on a subsequent one, it jumps to err_setup_vq and
calls virtio_vdpa_del_vqs():
drivers/virtio/virtio_vdpa.c:virtio_vdpa_find_vqs() {
...
err_setup_vq:
virtio_vdpa_del_vqs(vdev);
...
}
This tears down the previously created virtqueues. However, the callback is
not cleared with set_vq_cb(..., NULL), nor does it synchronize with pending
or concurrently running interrupt handlers (such as vp_vdpa_vq_handler).
Could a spurious or device-initiated interrupt occurring right before or
during vring_del_virtqueue(vq) race and cause virtio_vdpa_virtqueue_cb()
to dereference the freed vq structure when accessing vq->vdev?
quoted hunk
+ irqreturn_t ret;+ int idx;++ idx = srcu_read_lock(&vd_dev->cb_srcu);+ ret = vring_interrupt(0, vq);+ srcu_read_unlock(&vd_dev->cb_srcu, idx);++ return ret;+}+
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [Critical] Device reset and `synchronize_cbs()` fail to stop the background work handler, leading to a race condition and Use-After-Free during virtqueue removal.
--
commit 7ff4c967b176976015826d77ef33286ed4bdfbba
Author: Karl Mehltretter [off-list ref]
platform/mellanox: mlxbf-tmfifo: implement synchronize_cbs()
This commit implements synchronize_cbs() for the mlxbf-tmfifo driver. It uses
the existing per-direction spinlocks to synchronize with virtqueue callbacks
running in the FIFO work item instead of relying on synchronize_rcu().
@@ -1135,6 +1135,20 @@ static void mlxbf_tmfifo_virtio_reset(struct virtio_device *vdev)tm_vdev->status=0;}+/* Synchronize with callbacks running in the FIFO work item. */+staticvoidmlxbf_tmfifo_virtio_synchronize_cbs(structvirtio_device*vdev)+{+structmlxbf_tmfifo_vdev*tm_vdev=mlxbf_vdev_to_tmfifo(vdev);+structmlxbf_tmfifo*fifo=tm_vdev->vrings[0].fifo;+unsignedlongflags;+inti;++for(i=0;i<ARRAY_SIZE(fifo->spin_lock);i++){+spin_lock_irqsave(&fifo->spin_lock[i],flags);+spin_unlock_irqrestore(&fifo->spin_lock[i],flags);+}+}
[Severity: Critical]
This is a pre-existing issue, but does toggling the spinlocks here actually
guarantee that the work handler has finished and won't execute again?
If a privileged user unbinds the virtio device via sysfs while traffic is
actively processing, the driver core calls virtio_reset_device(), which
invokes mlxbf_tmfifo_virtio_reset() and mlxbf_tmfifo_virtio_synchronize_cbs().
Because mlxbf_tmfifo_virtio_reset() fails to halt or cancel the background
work item, the core proceeds to free the virtqueues in
mlxbf_tmfifo_virtio_del_vqs().
The concurrently running work handler (mlxbf_tmfifo_work_rxtx()) can then
check the virtqueue pointer outside the spinlocks and proceed to use it:
drivers/platform/mellanox/mlxbf-tmfifo.c:mlxbf_tmfifo_work_rxtx() {
...
vring = &tm_vdev->vrings[queue_id];
if (vring->vq)
mlxbf_tmfifo_rxtx(vring, is_rx);
...
}
And finally dereference the freed virtqueue in mlxbf_tmfifo_rxtx(), causing a
use-after-free:
drivers/platform/mellanox/mlxbf-tmfifo.c:mlxbf_tmfifo_rxtx() {
...
/* Notify upper layer that packet is done. */
spin_lock_irqsave(&fifo->spin_lock[is_rx], flags);
vring_interrupt(0, vring->vq);
spin_unlock_irqrestore(&fifo->spin_lock[is_rx], flags);
...
}
This issue wasn't introduced by this patch, as the former synchronize_rcu()
fallback also failed to protect the non-RCU work handler, but this patch fails
to correct the vulnerability.
Should the background work item be canceled during reset to prevent this?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260908053817.26065-1-kmehltretter@gmail.com?part=5
Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
Pre-existing issues:
- [High] idr_find() is called without rcu_read_lock() in preemptible contexts, leading to potential use-after-free of radix tree nodes.
- [High] The virtqueue pointer is published before its initialization is complete and without necessary memory barriers, exposing uninitialized memory to concurrent readers.
- [High] The added SRCU synchronization leaves the rvring structure vulnerable to Use-After-Free during device teardown.
- [High] Error path in rp_find_vq() erroneously removes the vring IDR entry without clearing it, permanently breaking the vring and causing a double-free on teardown.
- [High] rproc_add() error path causes a use-after-free and a double-free of the rproc structure.
- [High] Missing cancel_work_sync() for the crash handler work item before freeing the rproc struct.
--
commit c12071a358959f8d763c10c9e9b78779109dae3f
Author: Karl Mehltretter [off-list ref]
remoteproc: implement synchronize_cbs() for virtio devices
Platform drivers invoke rproc_vq_interrupt() from hard-IRQ handlers,
threaded handlers, and work items. Because rpmsg callbacks may sleep,
the virtio core's synchronize_rcu() fallback does not synchronize with
callbacks across all these contexts. A device reset can therefore
complete while a callback is still running.
[Severity: High]
This isn't a bug introduced by this patch, but is there a risk of a
use-after-free here if the crash handler work item is still pending?
rproc_alloc() initializes the rproc->crash_handler work item. If the remote
processor is deleted and freed, neither rproc_del() nor rproc_type_release()
calls cancel_work_sync(&rproc->crash_handler). If the work executes after this
structure is freed, the thread will dereference freed memory.
[Severity: High]
This is a pre-existing issue, but can the error path in rproc_add() cause a
use-after-free and a subsequent double-free?
If device_add(dev) fails, the error path erroneously calls put_device(dev):
drivers/remoteproc/remoteproc_core.c:rproc_add() {
...
ret = device_add(dev);
if (ret < 0) {
put_device(dev);
goto rproc_remove_cdev;
}
...
rproc_remove_cdev:
rproc_char_device_remove(rproc);
return ret;
}
Since the initial refcount is 1, put_device() drops it to 0, immediately
freeing the rproc structure. Calling rproc_char_device_remove(rproc) then
accesses the cdev within the already freed memory. Returning the error to the
caller typically results in rproc_free() being called, causing a double-free.
idr_destroy(&rproc->notifyids);
if (rproc->index >= 0)
@@ -88,15 +89,23 @@ static bool rproc_virtio_notify(struct virtqueue *vq) */ irqreturn_t rproc_vq_interrupt(struct rproc *rproc, int notifyid) {+ irqreturn_t ret = IRQ_NONE; struct rproc_vring *rvring;+ struct virtqueue *vq;+ int idx; dev_dbg(&rproc->dev, "vq index %d is interrupted\n", notifyid);+ idx = srcu_read_lock(&rproc->vq_srcu);+ rvring = idr_find(&rproc->notifyids, notifyid);
[Severity: High]
This is a pre-existing issue, but might calling idr_find() here without
standard RCU read-side protection risk a use-after-free of internal radix tree
nodes?
This function runs in a preemptible context (threaded IRQ handler). idr_find()
relies on rcu_read_lock() for safe lockless traversal. While this patch wraps
it in srcu_read_lock(), SRCU does not block standard RCU grace periods.
If a concurrent idr_remove() runs (e.g., during device teardown) and a standard
RCU grace period completes while the thread is preempted inside idr_find(), the
internal nodes could be freed and then dereferenced when the thread resumes.
[Severity: High]
This isn't a bug introduced by this patch, but does the added SRCU
synchronization leave the rvring structure vulnerable to a use-after-free
during device teardown?
While rproc_vq_interrupt() is now protected by an SRCU critical section, there
does not appear to be a corresponding synchronize_srcu() in the platform device
teardown path:
drivers/remoteproc/remoteproc_virtio.c:rproc_virtio_remove() {
...
for (id = 0; id < ARRAY_SIZE(rvdev->vring); id++) {
rvring = &rvdev->vring[id];
rproc_free_vring(rvring);
}
...
}
The IDR entries are removed, but the function returns without waiting for SRCU
readers. The core driver then cleans up devres, instantly freeing the rvdev
(which embeds rvring). If rproc_vq_interrupt() is preempted after locating
rvring but before accessing rvring->vq, it will dereference freed memory upon
resumption.
quoted hunk
+ if (vq)+ ret = vring_interrupt(0, vq);- return vring_interrupt(0, rvring->vq);+ srcu_read_unlock(&rproc->vq_srcu, idx);++ return ret; } EXPORT_SYMBOL(rproc_vq_interrupt);
[Severity: High]
This isn't a bug introduced by this patch, but would publishing the virtqueue
pointer here without a release barrier expose uninitialized memory to
concurrent readers?
In vring_new_virtqueue(), vq is allocated and initialized, but vq->priv is not
set. The pointer is then published to concurrent readers via WRITE_ONCE.
Since WRITE_ONCE does not provide release semantics on weakly ordered
architectures, a concurrent rproc_vq_interrupt() could observe a non-NULL vq
before vq->priv is set or before the internal state of vq is visible. The
handler would then call vring_interrupt(0, vq) on an improperly initialized
structure.
[Severity: High]
This is a pre-existing issue, but could the error path in rp_find_vq()
permanently break the vring and cause a double-free on teardown?
If vring_new_virtqueue() fails, the error path calls rproc_free_vring(rvring),
which executes idr_remove(), but it does not reset rvring->notifyid:
drivers/remoteproc/remoteproc_virtio.c:rp_find_vq() {
...
vq = vring_new_virtqueue(...);
if (!vq) {
dev_err(dev, "vring_new_virtqueue %s failed\n", name);
rproc_free_vring(rvring);
return ERR_PTR(-ENOMEM);
}
...
}
If the driver successfully binds later, rp_find_vq() might succeed, but the
notifyid is no longer in the IDR, silently breaking interrupts. Furthermore,
when the device is unbound, rproc_virtio_remove() loops over all vrings and
calls rproc_free_vring() again, executing a second idr_remove() for the same
notifyid. If the ID was reused by another vring, its entry will be erroneously
deleted.
From: "Michael S. Tsirkin" <mst@redhat.com> Date: 2026-09-08 08:06:48
On Tue, Sep 08, 2026 at 07:38:11AM +0200, Karl Mehltretter wrote:
This is v3 of the callback synchronization series. It is based on
Michael S. Tsirkin's linux-next branch at f49e6cf91942 ("virtio:
synchronize callbacks after device reset"), which already contains the
core change and the virtio_input teardown reorder from v2.
Pls note I dropped the core change for now.
That one needs more work:
- transport changes should be separate patches from core
changes
- shutdown generally needs more work, it should disable
config in particular just like remove does.
- drivers that queue work must have a shutdown callback to
sync it - that part can be separate patchsets
Patches 1 and 2 fix two bugs in virtio-ccw's existing
synchronize_cbs() hook. After a fallback from adapter to classic
interrupts it selects the wrong lock, and the classic interrupt handler
only takes the matching lock when notification hardening is enabled.
Patch 3 adds SRCU tracking for remoteproc callbacks, which can sleep.
Patches 4 and 5 replace the RCU fallback with synchronization against
the UML IRQ and the TmFIFO callback locks. The fallback already covers
these IRQ handlers and spinlock sections. Patch 6 adds SRCU tracking
in virtio_vdpa so callback synchronization does not depend on the
context in which the vDPA driver invokes the callback.
The UML, TmFIFO and remoteproc reset paths do not themselves prevent
new virtqueue callbacks. UML and TmFIFO only clear a status field, and
remoteproc does not wait for the remote side to acknowledge the reset.
Without notification hardening or driver-specific teardown protection,
callbacks can still start after reset. The new synchronization hooks
do not fix that. The TmFIFO hook also does not synchronize with the
rest of the FIFO work item outside the callback locks.
The virtio_input loop change from v2 (continue instead of break, so
events the device already completed are still delivered) and the
event buffer leak are sent separately.
Changes in v3:
- Rebased on Michael's linux-next branch. Dropped the core change and
the virtio_input patch, which are there already.
- Split the virtio-ccw fixes out of the core patch, one per bug, and
the transport patch into one patch per transport. The CCW and TmFIFO
changes are functionally unchanged from v2.
- Patch 2: added a Fixes tag and described the existing shutdown
case. Removed the dependency note on the core reset change.
- remoteproc: read the queue pointer once in rproc_vq_interrupt(), and
synchronize with callbacks in __rproc_virtio_del_vqs() before
freeing the queues (Sashiko). Place vq_srcu next to rvdevs so the
hunk also applies to mainline, which added attach_work after index.
- virtio_uml: compare against UM_IRQ_ALLOC instead of a bare negative
check.
- virtio_vdpa: use SRCU instead of a per-device rwlock, so the
callback tracking uses per-CPU counters, and cover the config
callback (Sashiko).
- Rewrote the commit messages. Corrected the RCU fallback description
for UML, TmFIFO and the vDPA simulators. Dropped the claimed simulator
reset race: the simulators disable bottom halves around virtqueue
callbacks and serialize reset with the worker's mutex.
Changes in v2:
- Moved callback synchronization from virtio-pci into the core, as
Michael suggested, and added the missing synchronize_cbs() hooks.
Testing: the changed objects build with W=1 without warnings, with
clang on arm64, x86-64 and s390 and with gcc on SMP UML, and the
patches also apply to current mainline and linux-next. The runtime
tests from v2 were not repeated on this version: the input, rebind and
shutdown checks on arm64 MMIO and x86 PCI covered code that is
unchanged here, and the remoteproc and virtio_vdpa hooks have only
been build-tested. No remoteproc, TmFIFO or s390 hardware was
available.
v2: https://lore.kernel.org/r/20260905152059.89560-1-kmehltretter@gmail.com
v1: https://lore.kernel.org/r/20260818040433.66986-1-kmehltretter@gmail.com
Karl Mehltretter (6):
virtio_ccw: fix synchronize_cbs() after interrupt fallback
virtio_ccw: always take irq_lock in the classic interrupt handler
remoteproc: implement synchronize_cbs() for virtio devices
um: virtio_uml: implement synchronize_cbs()
platform/mellanox: mlxbf-tmfifo: implement synchronize_cbs()
virtio_vdpa: implement synchronize_cbs()
arch/um/drivers/virtio_uml.c | 10 +++++++
drivers/platform/mellanox/mlxbf-tmfifo.c | 15 ++++++++++
drivers/remoteproc/remoteproc_core.c | 12 ++++++++
drivers/remoteproc/remoteproc_virtio.c | 37 +++++++++++++++++++-----
drivers/s390/virtio/virtio_ccw.c | 6 +---
drivers/virtio/virtio_vdpa.c | 34 ++++++++++++++++++++--
include/linux/remoteproc.h | 3 ++
7 files changed, 102 insertions(+), 15 deletions(-)
base-commit: f49e6cf919425cc55f10fd7cda7e0fc895df4cc0
--
2.39.5 (Apple Git-154)
From: "Michael S. Tsirkin" <mst@redhat.com> Date: 2026-09-08 08:14:34
On Tue, Sep 08, 2026 at 05:51:48AM +0000, sashiko-bot@kernel.org wrote:
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Pre-existing issues:
- [High] The virtio interrupt handlers pass retrieved virtqueue pointers directly to `vring_interrupt()` without a NULL check. A malicious or buggy host can trigger a kernel panic by providing an invalid indicator or interrupt vector.
Buggy host crashing guest is not something we worry about.
Host denying guest service at will is exactly how cloud providers get paid.
- [High] Lost configuration change notifications due to clearing the indicator bit after processing.
--
commit 40545ed73d4381a07a7934fddf3ac60ecd1f90d7
Author: Karl Mehltretter [off-list ref]
Subject: virtio_ccw: always take irq_lock in the classic interrupt handler
This commit unconditionally takes the read lock on vcdev->irq_lock in
virtio_ccw_int_handler() so the write side can properly synchronize with
the callbacks even when notification hardening is disabled.
[Severity: High]
This is a pre-existing issue, but is it possible for virtio_ccw_vq_by_ind()
to return a NULL virtqueue pointer if a malicious or buggy host provides an
invalid indicator?
Passing an unvalidated NULL pointer directly to vring_interrupt() can cause a
kernel panic when it is dereferenced inside more_used():
drivers/virtio/virtio_ring.c:vring_interrupt() {
...
if (!more_used(vq)) {
pr_debug("virtqueue interrupt with no work for %p\n", vq);
...
}
Similarly, in virtio_airq_handler(), airq_iv_get_ptr() can return NULL which
is unconditionally passed to vring_interrupt().
Could we add a NULL check before calling the interrupt handler?
[Severity: High]
This isn't a bug introduced by this patch, but could this sequence lead to
lost configuration change notifications?
Because indicators2 is shared DMA memory updated by the host, if the host
triggers another configuration change in the window between test_bit() and
clear_bit(), it sets the bit in memory and raises a new interrupt.
However, the guest will then blindly execute clear_bit() and wipe out the
newly set notification flag. When the newly queued interrupt handler runs,
it will find the bit cleared and do nothing, permanently losing the event.
Would using test_and_clear_bit() before calling virtio_config_changed()
close this race window?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260908053817.26065-1-kmehltretter@gmail.com?part=2
From: "Michael S. Tsirkin" <mst@redhat.com> Date: 2026-09-08 08:26:02
On Tue, Sep 08, 2026 at 07:38:11AM +0200, Karl Mehltretter wrote:
This is v3 of the callback synchronization series. It is based on
Michael S. Tsirkin's linux-next branch at f49e6cf91942 ("virtio:
synchronize callbacks after device reset"), which already contains the
core change and the virtio_input teardown reorder from v2.
Thanks I applied since this looks like a net improvement.
Pls keep iterating on the core change.
If you want to take a stab at some of the pre-existing issues -
will be welcome.
Pls do note which patches were tested and which were not.
Patches 1 and 2 fix two bugs in virtio-ccw's existing
synchronize_cbs() hook. After a fallback from adapter to classic
interrupts it selects the wrong lock, and the classic interrupt handler
only takes the matching lock when notification hardening is enabled.
Patch 3 adds SRCU tracking for remoteproc callbacks, which can sleep.
Patches 4 and 5 replace the RCU fallback with synchronization against
the UML IRQ and the TmFIFO callback locks. The fallback already covers
these IRQ handlers and spinlock sections. Patch 6 adds SRCU tracking
in virtio_vdpa so callback synchronization does not depend on the
context in which the vDPA driver invokes the callback.
The UML, TmFIFO and remoteproc reset paths do not themselves prevent
new virtqueue callbacks. UML and TmFIFO only clear a status field, and
remoteproc does not wait for the remote side to acknowledge the reset.
Without notification hardening or driver-specific teardown protection,
callbacks can still start after reset. The new synchronization hooks
do not fix that. The TmFIFO hook also does not synchronize with the
rest of the FIFO work item outside the callback locks.
The virtio_input loop change from v2 (continue instead of break, so
events the device already completed are still delivered) and the
event buffer leak are sent separately.
Changes in v3:
- Rebased on Michael's linux-next branch. Dropped the core change and
the virtio_input patch, which are there already.
- Split the virtio-ccw fixes out of the core patch, one per bug, and
the transport patch into one patch per transport. The CCW and TmFIFO
changes are functionally unchanged from v2.
- Patch 2: added a Fixes tag and described the existing shutdown
case. Removed the dependency note on the core reset change.
- remoteproc: read the queue pointer once in rproc_vq_interrupt(), and
synchronize with callbacks in __rproc_virtio_del_vqs() before
freeing the queues (Sashiko). Place vq_srcu next to rvdevs so the
hunk also applies to mainline, which added attach_work after index.
- virtio_uml: compare against UM_IRQ_ALLOC instead of a bare negative
check.
- virtio_vdpa: use SRCU instead of a per-device rwlock, so the
callback tracking uses per-CPU counters, and cover the config
callback (Sashiko).
- Rewrote the commit messages. Corrected the RCU fallback description
for UML, TmFIFO and the vDPA simulators. Dropped the claimed simulator
reset race: the simulators disable bottom halves around virtqueue
callbacks and serialize reset with the worker's mutex.
Changes in v2:
- Moved callback synchronization from virtio-pci into the core, as
Michael suggested, and added the missing synchronize_cbs() hooks.
Testing: the changed objects build with W=1 without warnings, with
clang on arm64, x86-64 and s390 and with gcc on SMP UML, and the
patches also apply to current mainline and linux-next. The runtime
tests from v2 were not repeated on this version: the input, rebind and
shutdown checks on arm64 MMIO and x86 PCI covered code that is
unchanged here, and the remoteproc and virtio_vdpa hooks have only
been build-tested. No remoteproc, TmFIFO or s390 hardware was
available.
v2: https://lore.kernel.org/r/20260905152059.89560-1-kmehltretter@gmail.com
v1: https://lore.kernel.org/r/20260818040433.66986-1-kmehltretter@gmail.com
Karl Mehltretter (6):
virtio_ccw: fix synchronize_cbs() after interrupt fallback
virtio_ccw: always take irq_lock in the classic interrupt handler
remoteproc: implement synchronize_cbs() for virtio devices
um: virtio_uml: implement synchronize_cbs()
platform/mellanox: mlxbf-tmfifo: implement synchronize_cbs()
virtio_vdpa: implement synchronize_cbs()
arch/um/drivers/virtio_uml.c | 10 +++++++
drivers/platform/mellanox/mlxbf-tmfifo.c | 15 ++++++++++
drivers/remoteproc/remoteproc_core.c | 12 ++++++++
drivers/remoteproc/remoteproc_virtio.c | 37 +++++++++++++++++++-----
drivers/s390/virtio/virtio_ccw.c | 6 +---
drivers/virtio/virtio_vdpa.c | 34 ++++++++++++++++++++--
include/linux/remoteproc.h | 3 ++
7 files changed, 102 insertions(+), 15 deletions(-)
base-commit: f49e6cf919425cc55f10fd7cda7e0fc895df4cc0
--
2.39.5 (Apple Git-154)
From: "Michael S. Tsirkin" <mst@redhat.com> Date: 2026-09-08 08:31:15
On Tue, Sep 08, 2026 at 05:51:59AM +0000, sashiko-bot@kernel.org wrote:
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] Possible Use-After-Free of virtqueue memory in callback during find_vqs error handling
--
commit 05a7e5947030bc4c942e05c96bf1d657ed426d56
Author: Karl Mehltretter [off-list ref]
virtio_vdpa: implement synchronize_cbs()
virtio_vdpa relies on the core's synchronize_rcu() fallback for callback
synchronization. This commit adds an SRCU domain per device and implements
synchronize_cbs() with synchronize_srcu() to cover the callbacks regardless
of the calling context.
[Severity: High]
This is a pre-existing issue, but could this callback be invoked
concurrently on a freed vq, leading to a use-after-free when
dereferencing vq->vdev?
During driver probe, if virtio_vdpa_find_vqs() successfully creates some
virtqueues but fails on a subsequent one, it jumps to err_setup_vq and
calls virtio_vdpa_del_vqs():
drivers/virtio/virtio_vdpa.c:virtio_vdpa_find_vqs() {
...
err_setup_vq:
virtio_vdpa_del_vqs(vdev);
...
}
This tears down the previously created virtqueues. However, the callback is
not cleared with set_vq_cb(..., NULL), nor does it synchronize with pending
or concurrently running interrupt handlers (such as vp_vdpa_vq_handler).
Could a spurious or device-initiated interrupt occurring right before or
during vring_del_virtqueue(vq) race and cause virtio_vdpa_virtqueue_cb()
to dereference the freed vq structure when accessing vq->vdev?
IRQs are not yet allocated then are they?
Looks like a false positive to me.
quoted
+ irqreturn_t ret;+ int idx;++ idx = srcu_read_lock(&vd_dev->cb_srcu);+ ret = vring_interrupt(0, vq);+ srcu_read_unlock(&vd_dev->cb_srcu, idx);++ return ret;+}+