From: Daniel Byrne <daniel1.byrne@intel.com>
To: Vinod Koul <vkoul@kernel.org>, Dave Jiang <dave.jiang@intel.com>
Cc: Fenghua Yu <fenghua.yu@intel.com>, Dan Williams <djbw@kernel.org>,
Vinicius Costa Gomes <vinicius.gomes@intel.com>,
Frank Li <Frank.Li@kernel.org>,
Herbert Xu <herbert@gondor.apana.org.au>,
Tom Zanussi <tom.zanussi@linux.intel.com>,
Kristen Accardi <kristen.c.accardi@intel.com>,
dmaengine@vger.kernel.org, linux-crypto@vger.kernel.org,
linux-kernel@vger.kernel.org, stable@vger.kernel.org
Subject: [PATCH 05/17] dmaengine: idxd: unbind the device driver before its work queues go away
Date: Mon, 19 Oct 2026 08:06:18 -0700 [thread overview]
Message-ID: <20261019150630.4026341-6-daniel1.byrne@intel.com> (raw)
In-Reply-To: <20261019150630.4026341-1-daniel1.byrne@intel.com>
idxd_remove() unregisters the work queue conf_devs first, and each
queue is freed by its conf_dev release. Only then does it unregister
the device's own conf_dev, whose driver remove, idxd_device_drv_remove(),
walks idxd->wqs and reads wq->state to disable the queues still
active. On a PCI unbind of an enabled device that is a read of freed
memory for every queue, which KASAN reports as a slab-use-after-free.
The usual administrative flow disables the device first and never hits
it.
Release the device driver before the queues are unregistered, so its
remove sees them while they exist. The conf_dev is unregistered where it
was, now without a driver bound, and the reference taken there only
keeps the memory alive for the remaining cleanup.
That remove also releases the driver of every bound queue, not only of
the queues that read enabled. A halt's clear_state leaves a bound queue
disabled, and skipping it would unregister the dma_device while that
queue's channel is still registered, so the queue's own unbind later
freed the channel and the device a second time.
The devices on the dsa bus stay open to a sysfs bind until they are
unregistered, and the bus does not take the parent's lock, so the PCI
device lock that the remove holds does not exclude a bind. The early
unbind leaves the device reading enabled while its queues' drivers are
released, and disabled before it is unregistered, which a queue probe
and the device probe accept respectively. The remove therefore marks
the device as going away before it unbinds the driver, and the device
probe and the queue enable, which every queue driver's probe calls,
refuse a device so marked, as the driver core did once the devices
were unregistered.
Fixes: 745e92a6d816 ("dmaengine: idxd: idxd: move remove() bits for idxd 'struct device' to device.c")
Assisted-by: Claude:claude-fable-5-1
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Daniel Byrne <daniel1.byrne@intel.com>
---
drivers/dma/idxd/device.c | 26 +++++++++++++++++++++++---
drivers/dma/idxd/idxd.h | 1 +
drivers/dma/idxd/init.c | 28 +++++++++++++++++++++++-----
3 files changed, 47 insertions(+), 8 deletions(-)
diff --git a/drivers/dma/idxd/device.c b/drivers/dma/idxd/device.c
index 131138483b87..a83b5da88f8c 100644
--- a/drivers/dma/idxd/device.c
+++ b/drivers/dma/idxd/device.c
@@ -1415,6 +1415,16 @@ int idxd_drv_enable_wq(struct idxd_wq *wq)
lockdep_assert_held(&wq->wq_lock);
+ /*
+ * The device remove releases the queues' drivers before it disables
+ * the device, so the device still reads enabled meanwhile.
+ */
+ if (test_bit(IDXD_FLAG_REMOVING, &idxd->flags)) {
+ idxd->cmd_status = IDXD_SCMD_DEV_NOT_ENABLED;
+ rc = -ENODEV;
+ goto err;
+ }
+
if (idxd->state != IDXD_DEV_ENABLED) {
idxd->cmd_status = IDXD_SCMD_DEV_NOT_ENABLED;
goto err;
@@ -1563,6 +1573,9 @@ int idxd_device_drv_probe(struct idxd_dev *idxd_dev)
struct idxd_device *idxd = idxd_dev_to_idxd(idxd_dev);
int rc = 0;
+ if (test_bit(IDXD_FLAG_REMOVING, &idxd->flags))
+ return -ENODEV;
+
/*
* Device should be in disabled state for the idxd_drv to load. If it's in
* enabled state, then the device was altered outside of driver's control.
@@ -1623,9 +1636,16 @@ void idxd_device_drv_remove(struct idxd_dev *idxd_dev)
struct idxd_wq *wq = idxd->wqs[i];
struct device *wq_dev = wq_confdev(wq);
- if (wq->state == IDXD_WQ_DISABLED)
- continue;
- dev_warn(dev, "Active wq %d on disable %s.\n", i, dev_name(wq_dev));
+ /*
+ * A halt's clear_state leaves a bound queue DISABLED while
+ * its driver still holds the queue's channel and resources.
+ * Release every bound driver, not only the enabled queues',
+ * before the dma_device below frees their channels. For a
+ * queue without a driver the release is a no-op.
+ */
+ if (wq->state != IDXD_WQ_DISABLED)
+ dev_warn(dev, "Active wq %d on disable %s.\n", i,
+ dev_name(wq_dev));
device_release_driver(wq_dev);
}
diff --git a/drivers/dma/idxd/idxd.h b/drivers/dma/idxd/idxd.h
index ce78b9a7c641..2343f0367d62 100644
--- a/drivers/dma/idxd/idxd.h
+++ b/drivers/dma/idxd/idxd.h
@@ -269,6 +269,7 @@ enum idxd_device_flag {
IDXD_FLAG_CMD_RUNNING,
IDXD_FLAG_PASID_ENABLED,
IDXD_FLAG_USER_PASID_ENABLED,
+ IDXD_FLAG_REMOVING, /* PCI remove under way, refuse binds */
};
struct idxd_dma_dev {
diff --git a/drivers/dma/idxd/init.c b/drivers/dma/idxd/init.c
index 4b827a329756..4443d23fde5a 100644
--- a/drivers/dma/idxd/init.c
+++ b/drivers/dma/idxd/init.c
@@ -1267,13 +1267,31 @@ static void idxd_remove(struct pci_dev *pdev)
{
struct idxd_device *idxd = pci_get_drvdata(pdev);
+ /*
+ * The devices on the dsa bus stay registered, and so open to a
+ * sysfs bind, until the unregisters below. A bind during the
+ * removal would re-probe the device or enable one of its queues
+ * while they are torn down, and the PCI device lock held here does
+ * not exclude it, so the probes test this first. The command path
+ * changes IDXD_FLAG_CMD_RUNNING in the same word with non-atomic
+ * bit operations under cmd_lock, which would lose a bit set
+ * concurrently, so the flag is set under that lock too.
+ */
+ spin_lock_irq(&idxd->cmd_lock);
+ set_bit(IDXD_FLAG_REMOVING, &idxd->flags);
+ spin_unlock_irq(&idxd->cmd_lock);
+ /*
+ * The idxd sub-driver's remove walks idxd->wqs to disable the
+ * active queues, and each wq is freed with its conf_dev. Unbind
+ * that driver while the queues still exist.
+ */
+ device_release_driver(idxd_confdev(idxd));
idxd_unregister_devices(idxd);
/*
- * When ->release() is called for the idxd->conf_dev, it frees all the memory related
- * to the idxd context. The driver still needs those bits in order to do the rest of
- * the cleanup. However, we do need to unbound the idxd sub-driver. So take a ref
- * on the device here to hold off the freeing while allowing the idxd sub-driver
- * to unbind.
+ * When ->release() is called for the idxd->conf_dev, it frees all
+ * the memory related to the idxd context. The driver still needs
+ * those bits in order to do the rest of the cleanup, so take a ref
+ * on the device here to hold off the freeing until that is done.
*/
get_device(idxd_confdev(idxd));
device_unregister(idxd_confdev(idxd));
--
2.43.0
next prev parent reply other threads:[~2026-10-19 15:14 UTC|newest]
Thread overview: 20+ 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 ` Daniel Byrne [this message]
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 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 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 15:06 ` [PATCH 11/17] dmaengine: idxd: disable the device before its work queues are unregistered Daniel Byrne
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 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 15:06 ` [PATCH 15/17] dmaengine: idxd: flush the work queues on every halt type Daniel Byrne
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 15:06 ` [PATCH 17/17] dmaengine: idxd: run the interrupt-handle revoke under the queue lock Daniel Byrne
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=20261019150630.4026341-6-daniel1.byrne@intel.com \
--to=daniel1.byrne@intel.com \
--cc=Frank.Li@kernel.org \
--cc=dave.jiang@intel.com \
--cc=djbw@kernel.org \
--cc=dmaengine@vger.kernel.org \
--cc=fenghua.yu@intel.com \
--cc=herbert@gondor.apana.org.au \
--cc=kristen.c.accardi@intel.com \
--cc=linux-crypto@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=stable@vger.kernel.org \
--cc=tom.zanussi@linux.intel.com \
--cc=vinicius.gomes@intel.com \
--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
all inboxes | Powered by JetHome®