From: sashiko-bot@kernel.org
To: "Daniel Byrne" <daniel1.byrne@intel.com>
Cc: dmaengine@vger.kernel.org, vkoul@kernel.org, Frank.Li@kernel.org
Subject: Re: [PATCH 09/17] dmaengine: idxd: quarantine terminate_all's descriptor slots until the device has drained
Date: Mon, 19 Oct 2026 17:26:33 +0000 [thread overview]
Message-ID: <sashiko-outbox-178035@kernel.org> (raw)
In-Reply-To: <20261019150630.4026341-10-daniel1.byrne@intel.com>
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] dmaengine: idxd: missing wq_lock in idxd_dma_synchronize() causes UAF
- [High] dmaengine: idxd: missing idxd_wq_thread() sync on channel termination
--
commit 9c6985db5bd50b1fe55f86734ee2fd37bc6a5f05
Author: Daniel Byrne <daniel1.byrne@intel.com>
dmaengine: idxd: quarantine terminate_all's descriptor slots until the device has drained
This commit prevents premature descriptor slot and DMA unmap data reuse on
idxd_dma_terminate_all() by moving in-flight aborted descriptors to a
per-queue quarantine list. The quarantined slots and unmap data are then
released in idxd_dma_synchronize() after draining the workqueue, or upon
device halt or workqueue resource teardown.
> diff --git a/drivers/dma/idxd/device.c b/drivers/dma/idxd/device.c
> index 41223d2e20af0..09475f5ea026d 100644
> --- a/drivers/dma/idxd/device.c
> +++ b/drivers/dma/idxd/device.c
[ ... ]
> @@ -170,10 +172,18 @@ EXPORT_SYMBOL_NS_GPL(idxd_wq_alloc_resources, "IDXD");
> void idxd_wq_free_resources(struct idxd_wq *wq)
> {
> struct device *dev = &wq->idxd->pdev->dev;
> + LIST_HEAD(dead_descs);
>
> if (wq->type != IDXD_WQT_KERNEL)
> return;
>
> + /*
> + * The disable frees the pool only after its drain and its reset
> + * completed, and a failed enable has run nothing, so the device no
> + * longer reaches what a quarantined descriptor maps.
> + */
> + idxd_wq_quarantine_take(wq, &dead_descs);
> + idxd_wq_quarantine_free(wq, &dead_descs);
> free_hw_descs(wq);
> free_descs(wq);
> dma_free_coherent(dev, wq->compls_size, wq->compls, wq->compls_addr);
[ ... ]
> @@ -1476,16 +1563,38 @@ void idxd_wq_free_irq(struct idxd_wq *wq)
> ie->pasid = IOMMU_PASID_INVALID;
> }
>
> -void idxd_wq_flush_descs(struct idxd_wq *wq)
> +/*
> + * @quarantine: the device keeps running, so the slots of the descriptors
> + * it has not finished stay out of use until a drain covers them. A halt
> + * has killed them and frees their slots at once.
> + */
> +void idxd_wq_flush_descs(struct idxd_wq *wq, bool quarantine)
> {
> struct idxd_irq_entry *ie = &wq->ie;
> + bool killed = false;
>
> guard(mutex)(&wq->wq_lock);
>
> if (wq->state != IDXD_WQ_ENABLED || wq->type != IDXD_WQT_KERNEL)
> return;
>
> - idxd_flush_pending_descs(ie);
> + /*
> + * A submitter between its list add and its portal write holds
> + * wq_active. Waiting for the reference to die puts every listed
> + * descriptor in the device, where the drain that frees the
> + * quarantine covers it. A halt flush has done this itself.
> + */
> + if (quarantine && !percpu_ref_is_dying(&wq->wq_active)) {
> + reinit_completion(&wq->wq_resurrect);
> + percpu_ref_kill(&wq->wq_active);
> + wait_for_completion(&wq->wq_dead);
> + killed = true;
> + }
> + idxd_flush_pending_descs(ie, quarantine);
> + if (killed) {
> + percpu_ref_reinit(&wq->wq_active);
> + complete_all(&wq->wq_resurrect);
> + }
> }
[Severity: High]
How are idxd_wq_flush_descs() and idxd_dma_synchronize() synchronized
against the per-WQ MSI-X threaded interrupt handler idxd_wq_thread()?
In drivers/dma/idxd/device.c, idxd_wq_flush_descs() kills wq->wq_active to
wait for submitters before calling idxd_flush_pending_descs(), which drains
ie->pending_llist and ie->work_list under ie->list_lock:
spin_lock(&ie->list_lock);
head = llist_del_all(&ie->pending_llist);
if (head) {
llist_for_each_entry_safe(desc, itr, head, llnode)
list_add_tail(&desc->list, &ie->work_list);
}
list_for_each_entry_safe(desc, itr, &ie->work_list, list)
list_move_tail(&desc->list, &flist);
spin_unlock(&ie->list_lock);
However, idxd_wq_thread() remains active during both idxd_dma_terminate_all()
and idxd_dma_synchronize().
First, at this commit, irq_process_pending_llist() in drivers/dma/idxd/irq.c
detaches ie->pending_llist outside ie->list_lock and re-queues incomplete
descriptors one at a time:
head = llist_del_all(&irq_entry->pending_llist);
if (!head)
return;
llist_for_each_entry_safe(desc, t, head, llnode) {
u8 status = desc->completion->status & DSA_COMP_STATUS_MASK;
if (status) {
...
} else {
spin_lock(&irq_entry->list_lock);
list_add_tail(&desc->list,
&irq_entry->work_list);
spin_unlock(&irq_entry->list_lock);
}
}
If idxd_flush_pending_descs() runs while irq_process_pending_llist() holds
the detached list in head, those descriptors are missed by the flush and
subsequently added back onto ie->work_list un-aborted and un-quarantined.
(A later commit in this series, "dmaengine: idxd: fix the double free of a
descriptor whose submission failed", moves llist_del_all() and the work_list
insertion under ie->list_lock, which closes that window for incomplete
descriptors.)
Second, even with that later change, both irq_process_work_list() and
irq_process_pending_llist() move completed descriptors onto a local flist
under ie->list_lock, drop ie->list_lock, and then invoke
idxd_desc_complete() -> idxd_dma_complete_txd() outside the lock. Any
descriptor moved to idxd_wq_thread()'s local flist before
idxd_flush_pending_descs() takes ie->list_lock will not have its callback
cleared.
Because idxd_dma_synchronize() only drains the hardware queue via
idxd_wq_drain(wq) and does not synchronize with the threaded IRQ handler
(for example via synchronize_irq(wq->ie.vector)), could
idxd_dma_synchronize() return while idxd_wq_thread() is still running
dma_descriptor_unmap() and dmaengine_desc_get_callback_invoke() on
previously submitted descriptors, leading to a use-after-free if the client
frees its callback context after dmaengine_synchronize()?
> diff --git a/drivers/dma/idxd/dma.c b/drivers/dma/idxd/dma.c
> index defd1af74c0a9..196045cb9ef2d 100644
> --- a/drivers/dma/idxd/dma.c
> +++ b/drivers/dma/idxd/dma.c
> @@ -199,7 +199,8 @@ static int idxd_dma_terminate_all(struct dma_chan *c)
> {
> struct idxd_wq *wq = to_idxd_wq(c);
>
> - idxd_wq_flush_descs(wq);
> + /* The device keeps running what the lists held. See synchronize. */
> + idxd_wq_flush_descs(wq, true);
>
> return 0;
> }
>
> static void idxd_dma_synchronize(struct dma_chan *c)
> {
> struct idxd_wq *wq = to_idxd_wq(c);
> + LIST_HEAD(quarantined);
>
> + /*
> + * dmaengine_synchronize() promises the client that its memory may
> + * be freed afterwards. The drain keeps that promise, and nothing
> + * the device held before it is left to write a completion record,
> + * so the slots terminate_all quarantined are free again.
> + */
> + idxd_wq_quarantine_take(wq, &quarantined);
> idxd_wq_drain(wq);
> + idxd_wq_quarantine_free(wq, &quarantined);
> }
[Severity: High]
Can idxd_dma_synchronize() race with workqueue teardown and cause a
use-after-free on wq->descs and wq->sbq?
idxd_wq_halt_release() in drivers/dma/idxd/device.c acquires wq->wq_lock and
checks wq->state and wq->type before touching the quarantine:
void idxd_wq_halt_release(struct idxd_wq *wq)
{
LIST_HEAD(dead_descs);
guard(mutex)(&wq->wq_lock);
if (wq->state != IDXD_WQ_ENABLED || wq->type != IDXD_WQT_KERNEL)
return;
idxd_wq_quarantine_take(wq, &dead_descs);
idxd_wq_quarantine_free(wq, &dead_descs);
}
In contrast, idxd_dma_synchronize() splices wq->quarantine onto its local
stack list under only wq->ie.list_lock, sleeps in idxd_wq_drain() ->
idxd_cmd_exec(), and then frees the descriptors without holding wq->wq_lock.
If workqueue teardown runs concurrently (such as via sysfs unbind, PCI
removal in idxd_remove(), or FLR recovery in idxd_reset_done() calling
idxd_unbind() -> idxd_device_drv_remove() -> idxd_dmaengine_drv_remove() ->
idxd_drv_disable_wq() -> idxd_wq_free_resources()), idxd_wq_free_resources()
sees wq->quarantine empty because the descriptors were already moved onto
idxd_dma_synchronize()'s stack list, and proceeds to free wq->descs[i] and
wq->sbq:
idxd_wq_quarantine_take(wq, &dead_descs);
idxd_wq_quarantine_free(wq, &dead_descs);
free_hw_descs(wq);
free_descs(wq);
dma_free_coherent(dev, wq->compls_size, wq->compls, wq->compls_addr);
sbitmap_queue_free(&wq->sbq);
When idxd_wq_drain() returns in idxd_dma_synchronize(),
idxd_wq_quarantine_free() (or idxd_wq_quarantine_put_back() in the later
commit "dmaengine: idxd: return -ETIMEDOUT from drains and resets and leak
what the device may still write") will dereference the freed struct idxd_desc
objects and call idxd_free_desc() -> sbitmap_queue_clear() on the freed
wq->sbq. Should idxd_dma_synchronize() also serialize under wq->wq_lock and
verify the workqueue state before taking and freeing quarantined descriptors?
>
> int idxd_register_dma_device(struct idxd_device *idxd)
[ ... ]
>
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261019150630.4026341-1-daniel1.byrne@intel.com?part=9
next prev parent reply other threads:[~2026-10-19 17:26 UTC|newest]
Thread overview: 29+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-19 15:06 [PATCH 00/17] [PATCH 00/17] dmaengine: idxd: fixes found by an in-kernel polling client Daniel Byrne
2026-10-19 15:06 ` [PATCH 01/17] dmaengine: idxd: don't queue EVL fault work for kernel WQs Daniel Byrne
2026-10-19 15:06 ` [PATCH 02/17] dmaengine: idxd: detach the user fault workqueue under the event log lock Daniel Byrne
2026-10-19 15:06 ` [PATCH 03/17] dmaengine: idxd: refuse a memcpy longer than the WQ max transfer size Daniel Byrne
2026-10-19 21:19 ` Frank Li
2026-10-19 15:06 ` [PATCH 04/17] dmaengine: idxd: list a new channel only once it is complete Daniel Byrne
2026-10-19 15:06 ` [PATCH 05/17] dmaengine: idxd: unbind the device driver before its work queues go away Daniel Byrne
2026-10-19 15:06 ` [PATCH 06/17] dmaengine: idxd: keep the interrupt handle across terminate_all Daniel Byrne
2026-10-19 15:06 ` [PATCH 07/17] dmaengine: idxd: bound device command waits and fail fast after a timeout Daniel Byrne
2026-10-19 17:05 ` sashiko-bot
2026-10-19 15:06 ` [PATCH 08/17] dmaengine: idxd: compare the device disable status code whole Daniel Byrne
2026-10-19 15:06 ` [PATCH 09/17] dmaengine: idxd: quarantine terminate_all's descriptor slots until the device has drained Daniel Byrne
2026-10-19 17:26 ` sashiko-bot [this message]
2026-10-19 15:06 ` [PATCH 10/17] dmaengine: idxd: return -ETIMEDOUT from drains and resets and leak what the device may still write Daniel Byrne
2026-10-19 17:50 ` sashiko-bot
2026-10-19 15:06 ` [PATCH 11/17] dmaengine: idxd: disable the device before its work queues are unregistered Daniel Byrne
2026-10-19 18:09 ` sashiko-bot
2026-10-19 15:06 ` [PATCH 12/17] dmaengine: idxd: hold device commands off while the reset re-creates its interrupts Daniel Byrne
2026-10-19 18:19 ` sashiko-bot
2026-10-19 15:06 ` [PATCH 13/17] dmaengine: idxd: fix the double free of a descriptor whose submission failed Daniel Byrne
2026-10-19 15:06 ` [PATCH 14/17] dmaengine: idxd: complete an iaa_crypto descriptor through its own driver on abort and flush Daniel Byrne
2026-10-19 18:56 ` sashiko-bot
2026-10-19 15:06 ` [PATCH 15/17] dmaengine: idxd: flush the work queues on every halt type Daniel Byrne
2026-10-19 19:17 ` sashiko-bot
2026-10-19 15:06 ` [PATCH 16/17] dmaengine: idxd: keep client callbacks on a halt flush and cancel resubmissions after a terminate Daniel Byrne
2026-10-19 19:36 ` sashiko-bot
2026-10-19 15:06 ` [PATCH 17/17] dmaengine: idxd: run the interrupt-handle revoke under the queue lock Daniel Byrne
2026-10-19 19:53 ` sashiko-bot
2026-10-19 19:09 ` [PATCH 00/17] [PATCH 00/17] dmaengine: idxd: fixes found by an in-kernel polling client Frank Li
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-178035@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=Frank.Li@kernel.org \
--cc=daniel1.byrne@intel.com \
--cc=dmaengine@vger.kernel.org \
--cc=sashiko-reviews@lists.linux.dev \
--cc=vkoul@kernel.org \
/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.