virtio: fix callback synchronization and avq cleanup on reset
Michael S. Tsirkin
maintainerv3 cover virtio: fix callback synchronization and avq cleanup on reset
Two issues with virtio device reset:
1. Karl Mehltretter reported that virtio_reset_device() promises callbacks are not in progress after reset, but only PCI transports actually synchronize callbacks - other transports leave a window where a handler already executing keeps running while the driver tears down state.
2. sashiko reported a race in virtio_pci_modern: the avq interrupt handler calls virtqueue_get_buf concurrently with virtqueue_detach_unused_buf in vp_modern_avq_cleanup, and there is no synchronize_irq between reset and cleanup.
Fix 1 by adding virtio_synchronize_cbs in the core after reset, then dropping the now-redundant per-transport sync calls. Fix 2 by moving avq cleanup from vp_reset to vp_del_vqs, which runs after callbacks have been synchronized - and is where buffer teardown conceptually belongs.
Changes v2->v3:
patch 1: unchanged
patch 2: split from v2 patch 2 - legacy part only patch 3: new - v2 just dropped the sync from modern vp_reset,
leaving avq_cleanup there before the removed sync. v3 moves avq_cleanup out of vp_reset entirely into vp_del_vqs, fixing the race. Adds NULL check for admin_vq.info needed because find_vqs error paths call vp_del_vqs before it is allocated.
Michael S. Tsirkin (3):
virtio: synchronize callbacks after device reset
virtio_pci_legacy: drop callback sync on reset
virtio_pci_modern: move avq cleanup from reset to del_vqs
drivers/virtio/virtio.c | 2 ++ drivers/virtio/virtio_pci_common.c | 2 ++ drivers/virtio/virtio_pci_common.h | 1 + drivers/virtio/virtio_pci_legacy.c | 2 -- drivers/virtio/virtio_pci_modern.c | 10 ++++------ 5 files changed, 9 insertions(+), 8 deletions(-)
Signature · 1 line
MST
Michael S. Tsirkin
maintainerPATCH v3 1/3 virtio: synchronize callbacks after device reset
virtio_reset_device says:
Note: this guarantees that vq callbacks are not in progress
but in practice, only virtio pci correctly synchronizes the cbs.
On other transports, a callback that is already executing, keeps running while the driver tears down the state it uses.
Add virtio_synchronize_cbs to virtio_reset_device fixing this for all transports that correctly implement synchronize_cbs().
---
Changes v2->v3: unchanged
drivers/virtio/virtio.c | 2 ++ 1 file changed, 2 insertions(+)
drivers/virtio/virtio.c +2 -0
@@ -264,6 +264,8 @@ void virtio_reset_device(struct virtio_device *dev)#endif dev->config->reset (dev); +/* Flush pending VQ/configuration callbacks. */ +virtio_synchronize_cbs (dev); }EXPORT_SYMBOL_GPL (virtio_reset_device);
Signature · 1 line
MST
[email protected]
AI reviewRe: [PATCH v3 1/3] virtio: synchronize callbacks after device reset
Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 7 lines
virtio_reset_devicesays:
Note: this guarantees that vq callbacks are not in progress
but in practice, only virtio pci correctly synchronizes the cbs.On other transports, a callback that is already executing, keeps running while the driver tears down the state it uses.
Quoted 7 lines
Add
virtio_synchronize_cbstovirtio_reset_devicefixing this for all transports that correctly implementsynchronize_cbs().Reported-by Karl Mehltretter <[email protected]>Fixes c46eccdaadab ("virtio: document virtio_reset_device")Signed-off-by Michael S. Tsirkin <[email protected]>
Sashiko has reviewed this patch and found no issues. It looks great!
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1
Re: [PATCH v3 1/3] virtio: synchronize callbacks after device reset
In reply to Michael S. TsirkinQuoted 3 lines · Michael S. Tsirkin
Add
virtio_synchronize_cbstovirtio_reset_devicefixing this for all transports that correctly implementsynchronize_cbs().
I ran 1,200 instrumented virtio-input reset cycles on x86-64 PCI and arm64 virtio-mmio under KASAN, KCSAN and lockdep. All 173 callback/reset overlaps completed before reset returned.
Thanks,
Karl
Michael S. Tsirkin
maintainerPATCH v3 2/3 virtio_pci_legacy: drop callback sync on reset
In reply to Michael S. TsirkinThe virtio core now synchronizes callbacks after reset, so the legacy PCI transport no longer needs to do it itself.
---
Changes v2->v3: split from v2 patch 2 (legacy part only)
drivers/virtio/virtio_pci_legacy.c | 2 -- 1 file changed, 2 deletions(-)
drivers/virtio/virtio_pci_legacy.c +0 -2
@@ -98,8 +98,6 @@ static void vp_reset(struct virtio_device *vdev)/* Flush out the status write, and flush in device writes, * including MSi-X interrupts, if any. */ vp_legacy_get_status (&vp_dev->ldev); -/* Flush pending VQ/configuration callbacks. */ -vp_synchronize_vectors (vdev); }static u16 vp_config_vector (struct virtio_pci_device *vp_dev,u16 vector)
Signature · 1 line
MST
[email protected]
AI reviewRe: [PATCH v3 2/3] virtio_pci_legacy: drop callback sync on reset
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [High] Race condition during system shutdown due to missing callback synchronization after device reset…
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Race condition during system shutdown due to missing callback synchronization after device reset, leading to potential use-after-free or system crash. --
commit 78e24f18ba4e25cf07c789fb8728c72626386f80
Author: Michael S. Tsirkin <[email protected]>
virtio_pci_legacy: drop callback sync on reset
The virtio core now synchronizes callbacks after reset, so the legacy PCI transport no longer needs to do it itself.
drivers/virtio/virtio_pci_legacy.c static void vp_reset(struct virtio_device *vdev) · line 102
Quoted 10 lines
drivers/virtio/virtio_pci_legacy.c +0
-2@@ -98,8 +98,6 @@ static void vp_reset(struct virtio_device *vdev)/* Flush out the status write, and flush in device writes, * including MSi-X interrupts, if any. */ vp_legacy_get_status (&vp_dev->ldev); -/* Flush pending VQ/configuration callbacks. */ -vp_synchronize_vectors (vdev);
[Severity: High]
Does removing this internal synchronization introduce a race condition during system shutdown?
Unlike virtio_reset_device(), virtio_device_shutdown() in drivers/virtio/virtio.c does not synchronize callbacks after resetting the device:
drivers/virtio/virtio.c:virtio_device_shutdown() {
...
virtio_synchronize_cbs(dev);
/*
* As IOMMUs are reset on shutdown, this will block device access to memory.
* Some devices get wedged if this happens, so reset to make sure it does not.
*/
dev->config->reset(dev);
}When dev->config->reset(dev) calls vp_reset(), the status read can trigger a final in-flight MSI-X interrupt.
Without vp_synchronize_vectors(), vp_reset() now returns immediately while the interrupt handler could still execute drv->config_changed(dev) concurrently on another CPU.
Can this lead to a fatal bus error, use-after-free, or kernel crash as the system teardown sequence proceeds?
Quoted 3 lines
drivers/virtio/virtio_pci_legacy.c +0
-0}static u16 vp_config_vector (struct virtio_pci_device *vp_dev,u16 vector)
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=2
Re: [PATCH v3 2/3] virtio_pci_legacy: drop callback sync on reset
In reply to Michael S. TsirkinQuoted 3 lines · Michael S. Tsirkin
The virtio core now synchronizes callbacks after reset, so the legacy PCI transport no longer needs to do it itself.
I ran 200 instrumented virtio-input reset cycles over legacy INTx under KASAN and lockdep. All 19 callback/reset overlaps completed before reset returned.
Thanks,
Karl
Michael S. Tsirkin
maintainerPATCH v3 3/3 virtio_pci_modern: move avq cleanup from reset to del_vqs
In reply to Michael S. Tsirkinvp_modern_avq_cleanup() detaches unused buffers from the admin virtqueue and completes pending commands with -EIO. Calling it from vp_reset() is incorrect: virtqueue_get_buf in the avq interrupt handler can race with virtqueue_detach_unused_buf in cleanup, and get_buf after detach is not documented as valid.
The root cause is that detaching buffers does not belong in reset at all - reset quiesces the device, while cleanup belongs where the virtqueue is about to be destroyed, in del_vqs.
Move the call to vp_del_vqs(), which runs after virtio_synchronize_cbs() has already guaranteed that no interrupt handler is in progress, eliminating the race.
---
New in v3. v2 dropped sync from modern vp_reset in one combined patch, leaving avq_cleanup in vp_reset. v3 moves avq_cleanup out of vp_reset entirely into vp_del_vqs, fixing the race. Adds NULL check for admin_vq.info for find_vqs error paths.
drivers/virtio/virtio_pci_common.c | 2 ++ drivers/virtio/virtio_pci_common.h | 1 + drivers/virtio/virtio_pci_modern.c | 10 ++++------ 3 files changed, 7 insertions(+), 6 deletions(-)
drivers/virtio/virtio_pci_common.c +2 -0
@@ -270,6 +270,8 @@ void vp_del_vqs(struct virtio_device *vdev)struct virtqueue *vq, *n;int i; +vp_modern_avq_cleanup (vdev); +list_for_each_entry_safe (vq, n, &vdev->vqs, list) { info =vp_is_avq (vdev, vq->index) ? vp_dev->admin_vq.info : vp_dev->vqs[vq->index];
drivers/virtio/virtio_pci_common.h +1 -0
@@ -194,6 +194,7 @@ struct virtio_device *virtio_pci_vf_get_pf_dev(struct pci_dev *pdev);#endif bool vp_is_avq (struct virtio_device *vdev,unsigned int index); +void vp_modern_avq_cleanup (struct virtio_device *vdev);void vp_modern_avq_done (struct virtqueue *vq);int vp_modern_admin_cmd_exec (struct virtio_device *vdev,struct virtio_admin_cmd *cmd);
drivers/virtio/virtio_pci_modern.c +4 -6
@@ -345,7 +345,7 @@ static void vp_modern_avq_activate(struct virtio_device *vdev)virtio_pci_admin_cmd_cap_init (vdev); } -static void vp_modern_avq_cleanup (struct virtio_device *vdev) +void vp_modern_avq_cleanup (struct virtio_device *vdev) {struct virtio_pci_device *vp_dev =to_vp_device (vdev);struct virtio_admin_cmd *cmd;
@@ -354,6 +354,9 @@ static void vp_modern_avq_cleanup(struct virtio_device *vdev)if (!virtio_has_feature (vdev,VIRTIO_F_ADMIN_VQ ))return ; +if (!vp_dev->admin_vq.info) +return ; + vq = vp_dev->admin_vq.info->vq;if (!vq)return ;
@@ -557,11 +560,6 @@ static void vp_reset(struct virtio_device *vdev)*/ while (vp_modern_get_status (mdev))msleep (1 ); - -vp_modern_avq_cleanup (vdev); - -/* Flush pending VQ/configuration callbacks. */ -vp_synchronize_vectors (vdev); }static int vp_active_vq (struct virtqueue *vq,u16 msix_vec)
Signature · 1 line
MST
[email protected]
AI reviewRe: [PATCH v3 3/3] virtio_pci_modern: move avq cleanup from reset to del_vqs
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: - [High] Moving `vp_modern_avq_cleanup()` out of `vp_reset()` causes admin command waiters to hang…
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] Moving vp_modern_avq_cleanup() out of vp_reset() causes admin command waiters to hang indefinitely during system shutdown.
- [High] Use-After-Free in vp_modern_avq_cleanup() triggered during find_vqs error paths or virtio driver re-binds due to a dangling admin_vq.info pointer. --
commit 9f596701bb244c686f63dc19e6844b130e778230
Author: Michael S. Tsirkin <[email protected]>
virtio_pci_modern: move avq cleanup from reset to del_vqs
vp_modern_avq_cleanup() detaches unused buffers from the admin virtqueue and completes pending commands with -EIO. Calling it from vp_reset() is incorrect because of a race condition. This commit moves the call to vp_del_vqs() to eliminate the race.
drivers/virtio/virtio_pci_common.c void vp_del_vqs(struct virtio_device *vdev) · line 274
Quoted 10 lines
drivers/virtio/virtio_pci_common.c +2
-0@@ -270,6 +270,8 @@ void vp_del_vqs(struct virtio_device *vdev)struct virtqueue *vq, *n;int i; +vp_modern_avq_cleanup (vdev); +
[Severity: High]
Could moving the cleanup here leave admin command waiters hanging indefinitely during system shutdown?
If a device reset is initiated (e.g., via virtio_device_shutdown() or power management freeze) while an admin command is executing, vp_reset() is called. Previously, this safely aborted pending commands with -EIO.
With the cleanup moved to vp_del_vqs(), which is not called during system shutdown or suspend, will any thread blocked on wait_for_completion() in virtqueue_exec_admin_cmd() hang forever since the commands are never aborted?
Quoted 2 lines
drivers/virtio/virtio_pci_common.c +0
-0list_for_each_entry_safe (vq, n, &vdev->vqs, list) { info = vp_is_avq(vdev, vq->index) ? vp_dev->admin_vq.info :
[ ... ]
Quoted 4 lines
drivers/virtio/virtio_pci_modern.c +0
-0
[ ... ]
drivers/virtio/virtio_pci_modern.c static void vp_modern_avq_cleanup(struct virtio_device *vdev) · line 359
Quoted 7 lines
drivers/virtio/virtio_pci_modern.c +3
-0@@ -354,6 +354,9 @@ static void vp_modern_avq_cleanup(struct virtio_device *vdev)if (!virtio_has_feature (vdev,VIRTIO_F_ADMIN_VQ ))return ; +if (!vp_dev->admin_vq.info) +return ; +
[Severity: High]
Does this check fail to prevent a use-after-free if the pointer is left dangling?
When queue initialization fails (e.g., IRQ allocation in vp_find_one_vq_msix()), the error path frees the info structure via vp_del_vq() but fails to clear vp_dev->admin_vq.info.
Later, the error rollback path in vp_find_vqs_msix() calls vp_del_vqs(), which immediately calls vp_modern_avq_cleanup():
vp_find_vqs_msix() error path:error_find:
vp_del_vqs()
vp_modern_avq_cleanup()
if (!vp_dev->admin_vq.info)Will this result in dereferencing the freed pointer when accessing vp_dev->admin_vq.info->vq on the next line?
Quoted 3 lines
drivers/virtio/virtio_pci_modern.c +0
-0vq = vp_dev->admin_vq.info->vq;if (!vq)return ;
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=3
Re: [PATCH v3 3/3] virtio_pci_modern: move avq cleanup from reset to del_vqs
In reply to Michael S. TsirkinQuoted 5 lines · Michael S. Tsirkin
New in v3. v2 dropped sync from modern
vp_resetin one combined patch, leavingavq_cleanupinvp_reset. v3 movesavq_cleanupout ofvp_resetentirely intovp_del_vqs, fixing the race. Adds NULL check for admin_vq.info forfind_vqserror paths.
I think patch 3 introduces a use-after-free in the MSI-X setup error path.
With VP_VQ_VECTOR_POLICY_EACH, vp_setup_vq() stores the admin queue's info pointer before request_irq(). If request_irq() fails, vp_del_vq() frees info but leaves admin_vq.info set.
vp_find_vqs_msix() then calls vp_del_vqs(). vp_del_vqs() now calls vp_modern_avq_cleanup() first and dereferences the freed pointer:
vq = vp_dev->admin_vq.info->vq;
No interrupt needs to fire.
I reproduced this on v3 with KASAN by returning -ENOMEM only for the admin queue's request_irq():
BUG: KASAN: slab-use-after-free in vp_modern_avq_cleanup+0xf4/0x110
KASAN shows the allocation in vp_setup_vq(), the free in vp_find_one_vq_msix(), and the access in vp_modern_avq_cleanup(). Adding *p_info = NULL after vp_del_vq() made the same test fall back and boot without a KASAN report.
Thanks,
Karl
Michael S. Tsirkin
maintainerRe: [PATCH v3 3/3] virtio_pci_modern: move avq cleanup from reset to del_vqs
Quoted 26 lines · Karl Mehltretter
Quoted 5 lines · Michael S. Tsirkin
New in v3. v2 dropped sync from modern
vp_resetin one combined patch, leavingavq_cleanupinvp_reset. v3 movesavq_cleanupout ofvp_resetentirely intovp_del_vqs, fixing the race. Adds NULL check for admin_vq.info forfind_vqserror paths.I think patch 3 introduces a use-after-free in the MSI-X setup error path.
With
VP_VQ_VECTOR_POLICY_EACH,vp_setup_vq()stores the admin queue's info pointer beforerequest_irq(). Ifrequest_irq()fails,vp_del_vq()frees info but leaves admin_vq.info set.
vp_find_vqs_msix()then callsvp_del_vqs().vp_del_vqs()now callsvp_modern_avq_cleanup()first and dereferences the freed pointer:vq = vp_dev->admin_vq.info->vq;No interrupt needs to fire.
I reproduced this on v3 with KASAN by returning
-ENOMEMonly for the admin queue'srequest_irq():BUG: KASAN: slab-use-after-free in vp_modern_avq_cleanup+0xf4/0x110
Quoted 7 lines
KASAN shows the allocation in
vp_setup_vq(), the free invp_find_one_vq_msix(), and the access invp_modern_avq_cleanup(). Adding*p_info= NULL aftervp_del_vq()made the same test fall back and boot without a KASAN report.Thanks,
Karl
Right. Besides, poking at modern from common is ugly. I'll rework this, thanks a lot for the testing!
Signature · 1 line
MST