All of lore.kernel.org
 help / color / mirror / Atom feed
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


  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.