From: Shuangpeng Bai <shuangpeng.kernel@gmail.com>
To: mani@kernel.org, kwilczynski@kernel.org
Cc: kishon@kernel.org, Frank.Li@kernel.org, arnd@arndb.de,
gregkh@linuxfoundation.org, bhelgaas@google.com,
linux-pci@vger.kernel.org, linux-kernel@vger.kernel.org,
Shuangpeng Bai <shuangpeng.kernel@gmail.com>
Subject: [PATCH v2] misc: pci_endpoint_test: fix use-after-free after device unbind
Date: Mon, 22 Jun 2026 12:52:49 -0400 [thread overview]
Message-ID: <20260622165249.374155-1-shuangpeng.kernel@gmail.com> (raw)
In-Reply-To: <20260621054609.186877-1-shuangpeng.kernel@gmail.com>
An open miscdevice file descriptor can outlive the PCI driver binding.
misc_deregister() removes the device node and prevents new opens, but it
does not revoke file descriptors that are already open.
Before this change, pci_endpoint_test stored the miscdevice inside struct
pci_endpoint_test, and ioctl() recovered the test object from
file->private_data with container_of(). Since the test object was allocated
with devm_kzalloc(), it was freed when the PCI device was unbound. A
process could therefore open /dev/pci-endpoint-test.N, unbind the PCI
device through sysfs, and then issue an ioctl on the stale file descriptor,
causing a use-after-free of struct pci_endpoint_test.
Manage the test object lifetime explicitly. Allocate it with kzalloc_obj(),
add a kref, take a reference from .open(), and drop it from .release().
The remove path deregisters the miscdevice to stop new opens, serializes
with ioctl using the existing mutex, releases the PCI resources, clears
test->pdev, and drops the probe reference.
Use the same 1s timeout already used by the other endpoint interrupt tests
for the READ, WRITE and COPY completion waits. This prevents device unbind
from blocking indefinitely behind an ioctl waiting for a missing endpoint
interrupt. Already-open file descriptors then keep the test object alive;
subsequent ioctls fail with -ENODEV instead of touching released device
resources.
Fixes: 2c156ac71c6b ("misc: Add host side PCI driver for PCI test function device")
Reported-by: Shuangpeng Bai <shuangpeng.kernel@gmail.com>
Closes: https://lore.kernel.org/all/178144969601.60470.7358419009914000395@gmail.com/
Suggested-by: Krzysztof Wilczyński <kwilczynski@kernel.org>
Signed-off-by: Shuangpeng Bai <shuangpeng.kernel@gmail.com>
---
Changes in v2:
- Bound READ, WRITE and COPY completion waits with the same 1s timeout used
by the other endpoint interrupt tests so device unbind cannot block
indefinitely behind an ioctl waiting for a missing endpoint interrupt.
Please let me know if there are any concerns with this approach or if there
is a simpler preferred way to handle this lifetime.
---
drivers/misc/pci_endpoint_test.c | 86 ++++++++++++++++++++++++++++----
1 file changed, 77 insertions(+), 9 deletions(-)
diff --git a/drivers/misc/pci_endpoint_test.c b/drivers/misc/pci_endpoint_test.c
index dbd017cabbb9..65d768dab450 100644
--- a/drivers/misc/pci_endpoint_test.c
+++ b/drivers/misc/pci_endpoint_test.c
@@ -13,6 +13,7 @@
#include <linux/io.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
+#include <linux/kref.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/mutex.h>
@@ -136,6 +137,7 @@ enum pci_barno {
};
struct pci_endpoint_test {
+ struct kref kref;
struct pci_dev *pdev;
void __iomem *base;
void __iomem *bar[PCI_STD_NUM_BARS];
@@ -143,7 +145,7 @@ struct pci_endpoint_test {
int last_irq;
int num_irqs;
int irq_type;
- /* mutex to protect the ioctls */
+ /* mutex to serialize ioctls and removal */
struct mutex mutex;
struct miscdevice miscdev;
enum pci_barno test_reg_bar;
@@ -157,6 +159,15 @@ struct pci_endpoint_test_data {
size_t alignment;
};
+static void pci_endpoint_test_free(struct kref *kref)
+{
+ struct pci_endpoint_test *test = container_of(kref,
+ struct pci_endpoint_test,
+ kref);
+
+ kfree(test);
+}
+
static inline u32 pci_endpoint_test_readl(struct pci_endpoint_test *test,
u32 offset)
{
@@ -804,10 +815,17 @@ static int pci_endpoint_test_copy(struct pci_endpoint_test *test,
pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_COMMAND,
COMMAND_COPY);
- wait_for_completion(&test->irq_raised);
+ if (!wait_for_completion_timeout(&test->irq_raised,
+ msecs_to_jiffies(1000))) {
+ ret = -ETIMEDOUT;
+ goto err_dst_unmap;
+ }
+err_dst_unmap:
dma_unmap_single(dev, orig_dst_phys_addr, size + alignment,
DMA_FROM_DEVICE);
+ if (ret)
+ goto err_dst_phys_addr;
dst_crc32 = crc32_le(~0, dst_addr, size);
if (dst_crc32 != src_crc32)
@@ -909,12 +927,17 @@ static int pci_endpoint_test_write(struct pci_endpoint_test *test,
pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_COMMAND,
COMMAND_READ);
- wait_for_completion(&test->irq_raised);
+ if (!wait_for_completion_timeout(&test->irq_raised,
+ msecs_to_jiffies(1000))) {
+ ret = -ETIMEDOUT;
+ goto err_unmap_addr;
+ }
reg = pci_endpoint_test_readl(test, PCI_ENDPOINT_TEST_STATUS);
if (!(reg & STATUS_READ_SUCCESS))
ret = -EIO;
+err_unmap_addr:
dma_unmap_single(dev, orig_phys_addr, size + alignment,
DMA_TO_DEVICE);
@@ -1000,10 +1023,17 @@ static int pci_endpoint_test_read(struct pci_endpoint_test *test,
pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_COMMAND,
COMMAND_WRITE);
- wait_for_completion(&test->irq_raised);
+ if (!wait_for_completion_timeout(&test->irq_raised,
+ msecs_to_jiffies(1000))) {
+ ret = -ETIMEDOUT;
+ goto err_unmap_addr;
+ }
+err_unmap_addr:
dma_unmap_single(dev, orig_phys_addr, size + alignment,
DMA_FROM_DEVICE);
+ if (ret)
+ goto err_phys_addr;
crc32 = crc32_le(~0, addr, size);
if (crc32 != pci_endpoint_test_readl(test, PCI_ENDPOINT_TEST_CHECKSUM))
@@ -1141,10 +1171,15 @@ static long pci_endpoint_test_ioctl(struct file *file, unsigned int cmd,
{
int ret = -EINVAL;
enum pci_barno bar;
- struct pci_endpoint_test *test = to_endpoint_test(file->private_data);
- struct pci_dev *pdev = test->pdev;
+ struct pci_endpoint_test *test = file->private_data;
+ struct pci_dev *pdev;
mutex_lock(&test->mutex);
+ pdev = test->pdev;
+ if (!pdev) {
+ ret = -ENODEV;
+ goto ret;
+ }
reinit_completion(&test->irq_raised);
test->last_irq = -ENODATA;
@@ -1206,9 +1241,30 @@ static long pci_endpoint_test_ioctl(struct file *file, unsigned int cmd,
return ret;
}
+static int pci_endpoint_test_open(struct inode *inode, struct file *file)
+{
+ struct miscdevice *miscdev = file->private_data;
+ struct pci_endpoint_test *test = to_endpoint_test(miscdev);
+
+ kref_get(&test->kref);
+ file->private_data = test;
+
+ return 0;
+}
+
+static int pci_endpoint_test_release(struct inode *inode, struct file *file)
+{
+ struct pci_endpoint_test *test = file->private_data;
+
+ kref_put(&test->kref, pci_endpoint_test_free);
+ return 0;
+}
+
static const struct file_operations pci_endpoint_test_fops = {
.owner = THIS_MODULE,
+ .open = pci_endpoint_test_open,
.unlocked_ioctl = pci_endpoint_test_ioctl,
+ .release = pci_endpoint_test_release,
};
static void pci_endpoint_test_get_capabilities(struct pci_endpoint_test *test)
@@ -1241,10 +1297,11 @@ static int pci_endpoint_test_probe(struct pci_dev *pdev,
if (pci_is_bridge(pdev))
return -ENODEV;
- test = devm_kzalloc(dev, sizeof(*test), GFP_KERNEL);
+ test = kzalloc_obj(*test);
if (!test)
return -ENOMEM;
+ kref_init(&test->kref);
test->pdev = pdev;
test->irq_type = PCITEST_IRQ_TYPE_UNDEFINED;
@@ -1263,7 +1320,7 @@ static int pci_endpoint_test_probe(struct pci_dev *pdev,
ret = pci_enable_device(pdev);
if (ret) {
dev_err(dev, "Cannot enable PCI device\n");
- return ret;
+ goto err_kfree_test;
}
ret = pci_request_regions(pdev, DRV_MODULE_NAME);
@@ -1349,6 +1406,9 @@ static int pci_endpoint_test_probe(struct pci_dev *pdev,
err_disable_pdev:
pci_disable_device(pdev);
+err_kfree_test:
+ kfree(test);
+
return ret;
}
@@ -1364,10 +1424,13 @@ static void pci_endpoint_test_remove(struct pci_dev *pdev)
if (id < 0)
return;
+ misc_deregister(&test->miscdev);
+
+ mutex_lock(&test->mutex);
+
pci_endpoint_test_release_irq(test);
pci_endpoint_test_free_irq_vectors(test);
- misc_deregister(&test->miscdev);
kfree(misc_device->name);
kfree(test->name);
ida_free(&pci_endpoint_test_ida, id);
@@ -1378,6 +1441,11 @@ static void pci_endpoint_test_remove(struct pci_dev *pdev)
pci_release_regions(pdev);
pci_disable_device(pdev);
+ pci_set_drvdata(pdev, NULL);
+ test->pdev = NULL;
+ mutex_unlock(&test->mutex);
+
+ kref_put(&test->kref, pci_endpoint_test_free);
}
static const struct pci_endpoint_test_data default_data = {
--
2.34.1
next prev parent reply other threads:[~2026-06-22 16:53 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-06-14 22:16 [BUG] KASAN: slab-use-after-free in pci_endpoint_test_ioctl Shuangpeng Bai
2026-06-15 2:39 ` Greg KH
2026-06-15 20:06 ` Shuangpeng
2026-06-18 3:01 ` Krzysztof Wilczyński
2026-06-21 5:46 ` [PATCH] misc: pci_endpoint_test: fix use-after-free after device unbind Shuangpeng Bai
2026-06-21 6:02 ` sashiko-bot
2026-06-22 16:52 ` Shuangpeng Bai [this message]
2026-06-22 17:15 ` [PATCH v2] " sashiko-bot
2026-06-22 20:40 ` 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=20260622165249.374155-1-shuangpeng.kernel@gmail.com \
--to=shuangpeng.kernel@gmail.com \
--cc=Frank.Li@kernel.org \
--cc=arnd@arndb.de \
--cc=bhelgaas@google.com \
--cc=gregkh@linuxfoundation.org \
--cc=kishon@kernel.org \
--cc=kwilczynski@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-pci@vger.kernel.org \
--cc=mani@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.