DMA Engine development
 help / color / mirror / Atom feed
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

  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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox