All of lore.kernel.org
 help / color / mirror / Atom feed
* [BUG] KASAN: slab-use-after-free in pci_endpoint_test_ioctl
@ 2026-06-14 22:16 Shuangpeng Bai
  2026-06-15  2:39 ` Greg KH
                   ` (2 more replies)
  0 siblings, 3 replies; 9+ messages in thread
From: Shuangpeng Bai @ 2026-06-14 22:16 UTC (permalink / raw)
  To: mani, kwilczynski, kishon, arnd, gregkh, linux-pci, linux-kernel

Hi Kernel Maintainers,

I hit the following report while testing current upstream kernel:

KASAN: slab-use-after-free in pci_endpoint_test_ioctl

I reproduced this on commit: e8c2f9fdadee7cbc75134dc463c1e0d856d6e5c7 (May 25 2026)

The reproducer and .config files are here.
https://gist.github.com/shuangpengbai/1e03464adfefbb75fb5d8112691e38b3

I'm happy to test debug patches or provide additional information.

Reported-by: Shuangpeng Bai <shuangpeng.kernel@gmail.com>

[  222.367023][ T8376] BUG: KASAN: slab-use-after-free in pci_endpoint_test_ioctl (drivers/misc/pci_endpoint_test.c:1145)
[  222.369439][ T8376] Read of size 8 at addr ffff88811be68438 by task pci_endpoint_te/8376
[  222.371748][ T8376]
[  222.372441][ T8376] Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
[  222.372449][ T8376] Call Trace:
[  222.372455][ T8376]  <TASK>
[  222.372460][ T8376]  dump_stack_lvl (lib/dump_stack.c:94 lib/dump_stack.c:120)
[  222.372476][ T8376]  print_report (mm/kasan/report.c:378 mm/kasan/report.c:482)
[  222.372531][ T8376]  kasan_report (mm/kasan/report.c:595)
[  222.372558][ T8376]  pci_endpoint_test_ioctl (drivers/misc/pci_endpoint_test.c:1145)
[  222.372604][ T8376]  __se_sys_ioctl (fs/ioctl.c:51 fs/ioctl.c:597 fs/ioctl.c:583)
[  222.372615][ T8376]  do_syscall_64 (arch/x86/entry/syscall_64.c:63 arch/x86/entry/syscall_64.c:94)
[  222.372647][ T8376]  entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:121)
[  222.372658][ T8376] RIP: 0033:0x7fd68c893237
[  222.372668][ T8376] Code: 00 00 00 48 8b 05 59 cc 0d 00 64 c7 00 26 00 00 00 48 c7 c0 ff ff ff ff c3 66 2e 0f 1f 84 00 00 00 00 00 b8 10 00 00 00 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 8b 0d 29 cc 0d 00 f7 d8 64 89 01 48
[  222.372678][ T8376] RSP: 002b:00007ffc1aa73f18 EFLAGS: 00000206 ORIG_RAX: 0000000000000010
[  222.372693][ T8376] RAX: ffffffffffffffda RBX: 00007fd68c9774c0 RCX: 00007fd68c893237
[  222.372701][ T8376] RDX: 0000000000000000 RSI: 0000000000005009 RDI: 0000000000000003
[  222.372707][ T8376] RBP: 0000560e1af360c3 R08: 0000000000000000 R09: 00007ffc1aa73e27
[  222.372714][ T8376] R10: fffffffffffff64d R11: 0000000000000206 R12: 0000000000000003
[  222.372719][ T8376] R13: 0000000000005009 R14: 0000000000000000 R15: 00007ffc1aa73f80
[  222.372731][ T8376]  </TASK>
[  222.372735][ T8376]
[  222.392098][ T8376] Freed by task 8376 on cpu 0 at 222.263660s:
[  222.392729][ T8376]  kasan_save_track (mm/kasan/common.c:57 mm/kasan/common.c:78)
[  222.393215][ T8376]  kasan_save_free_info (mm/kasan/generic.c:584)
[  222.393743][ T8376]  __kasan_slab_free (mm/kasan/common.c:253 mm/kasan/common.c:285)
[  222.394242][ T8376]  kfree (include/linux/kasan.h:235 mm/slub.c:2689 mm/slub.c:6251 mm/slub.c:6566)
[  222.394657][ T8376]  devres_release_all (drivers/base/devres.c:50 drivers/base/devres.c:547 drivers/base/devres.c:576)
[  222.395176][ T8376]  device_release_driver_internal (drivers/base/dd.c:598 drivers/base/dd.c:1357 drivers/base/dd.c:1375)
[  222.395807][ T8376]  unbind_store (drivers/base/bus.c:244)
[  222.396280][ T8376]  kernfs_fop_write_iter (fs/kernfs/file.c:352)
[  222.396832][ T8376]  vfs_write (fs/read_write.c:595 fs/read_write.c:688)
[  222.397273][ T8376]  ksys_write (fs/read_write.c:740)
[  222.397725][ T8376]  do_syscall_64 (arch/x86/entry/syscall_64.c:63 arch/x86/entry/syscall_64.c:94)
[  222.398207][ T8376]  entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:121)
[  222.398823][ T8376]
[  222.399073][ T8376] The buggy address belongs to the object at ffff88811be68400
[  222.399073][ T8376]  which belongs to the cache kmalloc-512 of size 512
[  222.400509][ T8376] The buggy address is located 56 bytes inside of
[  222.400509][ T8376]  freed 512-byte region [ffff88811be68400, ffff88811be68600)
[  222.401905][ T8376]


Best,
Shuangpeng

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [BUG] KASAN: slab-use-after-free in pci_endpoint_test_ioctl
  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
  2 siblings, 1 reply; 9+ messages in thread
From: Greg KH @ 2026-06-15  2:39 UTC (permalink / raw)
  To: Shuangpeng Bai; +Cc: mani, kwilczynski, kishon, arnd, linux-pci, linux-kernel

On Sun, Jun 14, 2026 at 06:16:39PM -0400, Shuangpeng Bai wrote:
> Hi Kernel Maintainers,
> 
> I hit the following report while testing current upstream kernel:
> 
> KASAN: slab-use-after-free in pci_endpoint_test_ioctl
> 
> I reproduced this on commit: e8c2f9fdadee7cbc75134dc463c1e0d856d6e5c7 (May 25 2026)
> 
> The reproducer and .config files are here.
> https://gist.github.com/shuangpengbai/1e03464adfefbb75fb5d8112691e38b3
> 
> I'm happy to test debug patches or provide additional information.

As we are drowning in real/fake/unknown bug reports, why not take the
time to work on a patch to fix these issues as you have a reproducer for
them?  That way you get full credit for the fix.

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [BUG] KASAN: slab-use-after-free in pci_endpoint_test_ioctl
  2026-06-15  2:39 ` Greg KH
@ 2026-06-15 20:06   ` Shuangpeng
  0 siblings, 0 replies; 9+ messages in thread
From: Shuangpeng @ 2026-06-15 20:06 UTC (permalink / raw)
  To: Greg KH; +Cc: mani, kwilczynski, kishon, arnd, linux-pci, linux-kernel



> On Jun 14, 2026, at 22:39, Greg KH <gregkh@linuxfoundation.org> wrote:
> 
> On Sun, Jun 14, 2026 at 06:16:39PM -0400, Shuangpeng Bai wrote:
>> Hi Kernel Maintainers,
>> 
>> I hit the following report while testing current upstream kernel:
>> 
>> KASAN: slab-use-after-free in pci_endpoint_test_ioctl
>> 
>> I reproduced this on commit: e8c2f9fdadee7cbc75134dc463c1e0d856d6e5c7 (May 25 2026)
>> 
>> The reproducer and .config files are here.
>> https://gist.github.com/shuangpengbai/1e03464adfefbb75fb5d8112691e38b3
>> 
>> I'm happy to test debug patches or provide additional information.
> 
> As we are drowning in real/fake/unknown bug reports, why not take the
> time to work on a patch to fix these issues as you have a reproducer for
> them?  That way you get full credit for the fix.
> 

Thanks for your suggestion.

I will try to look into this and understand the root cause.
If I can identify a proper fix, I will send a patch for review.

Thanks,
Shuangpeng


> thanks,
> 
> greg k-h


^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [BUG] KASAN: slab-use-after-free in pci_endpoint_test_ioctl
  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-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
  2 siblings, 0 replies; 9+ messages in thread
From: Krzysztof Wilczyński @ 2026-06-18  3:01 UTC (permalink / raw)
  To: Shuangpeng Bai; +Cc: mani, kishon, arnd, gregkh, linux-pci, linux-kernel

Hello,

> I hit the following report while testing current upstream kernel:
> 
> KASAN: slab-use-after-free in pci_endpoint_test_ioctl
> 
> I reproduced this on commit: e8c2f9fdadee7cbc75134dc463c1e0d856d6e5c7 (May 25 2026)
> 
> The reproducer and .config files are here.
> https://gist.github.com/shuangpengbai/1e03464adfefbb75fb5d8112691e38b3

The proof-of-concept you have there can be distilled down to:

#include <fcntl.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>

#define BDF "0000:XX:YY.Z"

int main(void)
{
	int fd = open("/dev/pci-endpoint-test.0", O_RDWR);
	int u = open("/sys/bus/pci/drivers/pci-endpoint-test/unbind", O_WRONLY);

	if (write(u, BDF, strlen(BDF)) < 0)
		return 1;

	ioctl(fd, 0);
	return 0;
}

Only required steps would be to update the BDF definition with the
relevant device address, and then bind the right device prior to
running the proof-of-concept binary:

  $ echo "1234 0987" > /sys/bus/pci/drivers/pci-endpoint-test/new_id

> [  222.372449][ T8376] Call Trace:
> [  222.372455][ T8376]  <TASK>
> [  222.372460][ T8376]  dump_stack_lvl (lib/dump_stack.c:94 lib/dump_stack.c:120)
> [  222.372476][ T8376]  print_report (mm/kasan/report.c:378 mm/kasan/report.c:482)
> [  222.372531][ T8376]  kasan_report (mm/kasan/report.c:595)
> [  222.372558][ T8376]  pci_endpoint_test_ioctl (drivers/misc/pci_endpoint_test.c:1145)
> [  222.372604][ T8376]  __se_sys_ioctl (fs/ioctl.c:51 fs/ioctl.c:597 fs/ioctl.c:583)
> [  222.372615][ T8376]  do_syscall_64 (arch/x86/entry/syscall_64.c:63 arch/x86/entry/syscall_64.c:94)
> [  222.372647][ T8376]  entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:121)
> [  222.372658][ T8376] RIP: 0033:0x7fd68c893237
> [  222.372668][ T8376] Code: 00 00 00 48 8b 05 59 cc 0d 00 64 c7 00 26 00 00 00 48 c7 c0 ff ff ff ff c3 66 2e 0f 1f 84 00 00 00 00 00 b8 10 00 00 00 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 8b 0d 29 cc 0d 00 f7 d8 64 89 01 48
> [  222.372678][ T8376] RSP: 002b:00007ffc1aa73f18 EFLAGS: 00000206 ORIG_RAX: 0000000000000010
> [  222.372693][ T8376] RAX: ffffffffffffffda RBX: 00007fd68c9774c0 RCX: 00007fd68c893237
> [  222.372701][ T8376] RDX: 0000000000000000 RSI: 0000000000005009 RDI: 0000000000000003
> [  222.372707][ T8376] RBP: 0000560e1af360c3 R08: 0000000000000000 R09: 00007ffc1aa73e27
> [  222.372714][ T8376] R10: fffffffffffff64d R11: 0000000000000206 R12: 0000000000000003
> [  222.372719][ T8376] R13: 0000000000005009 R14: 0000000000000000 R15: 00007ffc1aa73f80
> [  222.372731][ T8376]  </TASK>
> [  222.372735][ T8376]
> [  222.392098][ T8376] Freed by task 8376 on cpu 0 at 222.263660s:
> [  222.392729][ T8376]  kasan_save_track (mm/kasan/common.c:57 mm/kasan/common.c:78)
> [  222.393215][ T8376]  kasan_save_free_info (mm/kasan/generic.c:584)
> [  222.393743][ T8376]  __kasan_slab_free (mm/kasan/common.c:253 mm/kasan/common.c:285)
> [  222.394242][ T8376]  kfree (include/linux/kasan.h:235 mm/slub.c:2689 mm/slub.c:6251 mm/slub.c:6566)
> [  222.394657][ T8376]  devres_release_all (drivers/base/devres.c:50 drivers/base/devres.c:547 drivers/base/devres.c:576)
> [  222.395176][ T8376]  device_release_driver_internal (drivers/base/dd.c:598 drivers/base/dd.c:1357 drivers/base/dd.c:1375)
> [  222.395807][ T8376]  unbind_store (drivers/base/bus.c:244)
> [  222.396280][ T8376]  kernfs_fop_write_iter (fs/kernfs/file.c:352)
> [  222.396832][ T8376]  vfs_write (fs/read_write.c:595 fs/read_write.c:688)
> [  222.397273][ T8376]  ksys_write (fs/read_write.c:740)
> [  222.397725][ T8376]  do_syscall_64 (arch/x86/entry/syscall_64.c:63 arch/x86/entry/syscall_64.c:94)
> [  222.398207][ T8376]  entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:121)
> [  222.398823][ T8376]
> [  222.399073][ T8376] The buggy address belongs to the object at ffff88811be68400
> [  222.399073][ T8376]  which belongs to the cache kmalloc-512 of size 512
> [  222.400509][ T8376] The buggy address is located 56 bytes inside of
> [  222.400509][ T8376]  freed 512-byte region [ffff88811be68400, ffff88811be68600)

At first glance, it looks like the file descriptor outlives the driver
binding.  An open file descriptor doesn't block unbind via sysfs, and
misc_deregister() doesn't revoke currently open file descriptors either.

The struct pci_endpoint_test has to stay alive until both the driver is
unbound and the last open file descriptor is closed - whichever happens
last, whereas devres frees it as soon as the driver is unbound, regardless
of any file descriptor that is still open.  Hence the dangling reference
and the use-after-free.

Thank you!

	Krzysztof

^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH] misc: pci_endpoint_test: fix use-after-free after device unbind
  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-18  3:01 ` Krzysztof Wilczyński
@ 2026-06-21  5:46 ` Shuangpeng Bai
  2026-06-21  6:02   ` sashiko-bot
  2026-06-22 16:52   ` [PATCH v2] " Shuangpeng Bai
  2 siblings, 2 replies; 9+ messages in thread
From: Shuangpeng Bai @ 2026-06-21  5:46 UTC (permalink / raw)
  To: mani, kwilczynski
  Cc: kishon, Frank.Li, arnd, gregkh, bhelgaas, linux-pci, linux-kernel,
	Shuangpeng Bai

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 initial reference. 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>
---
 drivers/misc/pci_endpoint_test.c | 61 ++++++++++++++++++++++++++++----
 1 file changed, 55 insertions(+), 6 deletions(-)

diff --git a/drivers/misc/pci_endpoint_test.c b/drivers/misc/pci_endpoint_test.c
index dbd017cabbb9..981a834ce41b 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)
 {
@@ -1141,10 +1152,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 +1222,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 +1278,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 +1301,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 +1387,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 +1405,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 +1422,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


^ permalink raw reply related	[flat|nested] 9+ messages in thread

* Re: [PATCH] misc: pci_endpoint_test: fix use-after-free after device unbind
  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   ` [PATCH v2] " Shuangpeng Bai
  1 sibling, 0 replies; 9+ messages in thread
From: sashiko-bot @ 2026-06-21  6:02 UTC (permalink / raw)
  To: Shuangpeng Bai; +Cc: linux-pci

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Deadlock in pci_endpoint_test_remove() on device unbind due to serialization with ioctls that sleep indefinitely.
--

commit 8935a4226276767a2564aa469158f4ffb5341492
Author: Shuangpeng Bai <shuangpeng.kernel@gmail.com>

misc: pci_endpoint_test: fix use-after-free after device unbind

This commit explicitly manages the lifetime of the test object with a kref
to prevent a use-after-free when an open file descriptor outlives the PCI
device binding. The remove path now serializes with ioctl using the
existing mutex and clears device state before dropping the initial
reference.

Closes: https://lore.kernel.org/all/178144969601.60470.7358419009914000395@gmail.com/

> diff --git a/drivers/misc/pci_endpoint_test.c b/drivers/misc/pci_endpoint_test.c
> index dbd017cabbb92..981a834ce41bd 100644
> --- a/drivers/misc/pci_endpoint_test.c
> +++ b/drivers/misc/pci_endpoint_test.c
[ ... ]
> @@ -1364,10 +1405,13 @@ static void pci_endpoint_test_remove(struct pci_dev *pdev)
>  	if (id < 0)
>  		return;
>  
> +	misc_deregister(&test->miscdev);
> +
> +	mutex_lock(&test->mutex);
> +

[Severity: High]
Can this lock acquisition cause a deadlock during device unbind?

The ioctl handlers, such as pci_endpoint_test_copy(), hold this same mutex
while waiting indefinitely for hardware interrupts:

    mutex_lock(&test->mutex);
    ...
    wait_for_completion(&test->irq_raised);

If the hardware fails to raise the expected interrupt, perhaps due to a
hardware hang or abrupt hot-unplug, the ioctl thread will block forever
while holding the mutex.

Since pci_endpoint_test_remove() now waits on this mutex, wouldn't a hanging
ioctl permanently block the removal path and deadlock the PCI subsystem for
this device?

>  	pci_endpoint_test_release_irq(test);
>  	pci_endpoint_test_free_irq_vectors(test);
>  
> -	misc_deregister(&test->miscdev);
>  	kfree(misc_device->name);
>  	kfree(test->name);

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260621054609.186877-1-shuangpeng.kernel@gmail.com?part=1

^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH v2] misc: pci_endpoint_test: fix use-after-free after device unbind
  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
  2026-06-22 17:15     ` sashiko-bot
  2026-06-22 20:40     ` Frank Li
  1 sibling, 2 replies; 9+ messages in thread
From: Shuangpeng Bai @ 2026-06-22 16:52 UTC (permalink / raw)
  To: mani, kwilczynski
  Cc: kishon, Frank.Li, arnd, gregkh, bhelgaas, linux-pci, linux-kernel,
	Shuangpeng Bai

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


^ permalink raw reply related	[flat|nested] 9+ messages in thread

* Re: [PATCH v2] misc: pci_endpoint_test: fix use-after-free after device unbind
  2026-06-22 16:52   ` [PATCH v2] " Shuangpeng Bai
@ 2026-06-22 17:15     ` sashiko-bot
  2026-06-22 20:40     ` Frank Li
  1 sibling, 0 replies; 9+ messages in thread
From: sashiko-bot @ 2026-06-22 17:15 UTC (permalink / raw)
  To: Shuangpeng Bai; +Cc: linux-pci

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:

New issues:
- [Critical] Adding a timeout to DMA transfer ioctls without aborting the ongoing hardware operation allows the PCI endpoint to perform DMA into freed physical memory, causing a use-after-free and memory corruption.

Pre-existing issues:
- [Critical] Spurious delayed completions from timed-out commands desynchronize the ioctl state machine, causing subsequent commands to prematurely return and free in-use DMA buffers.
--

commit 55971777f70d30f6a31f34520e097bb21bd66951
Author: Shuangpeng Bai <shuangpeng.kernel@gmail.com>

misc: pci_endpoint_test: fix use-after-free after device unbind

This commit changes the lifetime management of the pci_endpoint_test object
by using a kref and taking a reference during .open(). It also replaces
wait_for_completion() with wait_for_completion_timeout() to prevent
unbounded waits on missing endpoint interrupts.

Link: https://lore.kernel.org/all/178144969601.60470.7358419009914000395@gmail.com/

> diff --git a/drivers/misc/pci_endpoint_test.c b/drivers/misc/pci_endpoint_test.c
> index dbd017cabbb92..65d768dab4504 100644
> --- a/drivers/misc/pci_endpoint_test.c
> +++ b/drivers/misc/pci_endpoint_test.c
[ ... ]
> @@ -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;

[Severity: Critical]
If this times out, does the endpoint hardware continue its DMA transfer?

On timeout, this jumps to err_dst_unmap, where dma_unmap_single() and
subsequently kfree() will be called to free the memory. If the PCI endpoint
hardware is still actively processing the transfer and wasn't aborted, won't
it perform DMA into freed physical memory, causing memory corruption?

[ ... ]
> @@ -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);

[Severity: Critical]
Similar to pci_endpoint_test_copy() above, if this times out, won't the
source buffer be unmapped and freed while the endpoint hardware might still be
reading from it?

[ ... ]
> @@ -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;

[Severity: Critical]
As with the other ioctls, if this times out, won't the destination buffer
be unmapped and freed while the endpoint hardware might still be writing to it?

[ ... ]
> @@ -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;

[Severity: Critical]
This is a pre-existing issue, but won't reinit_completion() here be racy
after a previous command timed out?

Since a timed-out command leaves the endpoint hardware running, a delayed
interrupt from that prior command could arrive later. Because
reinit_completion() is called without first ensuring the hardware is idle or
discarding pending interrupts, could a spurious completion from a timed-out
command falsely wake up a subsequent ioctl's wait_for_completion_timeout()?

If this happens for the DMA-based READ, WRITE, or COPY ioctls, it would cause
them to prematurely wake up and free their in-use DMA buffers before the
hardware actually finishes the new transfer, leading to a use-after-free
and memory corruption.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260622165249.374155-1-shuangpeng.kernel@gmail.com?part=1

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH v2] misc: pci_endpoint_test: fix use-after-free after device unbind
  2026-06-22 16:52   ` [PATCH v2] " Shuangpeng Bai
  2026-06-22 17:15     ` sashiko-bot
@ 2026-06-22 20:40     ` Frank Li
  1 sibling, 0 replies; 9+ messages in thread
From: Frank Li @ 2026-06-22 20:40 UTC (permalink / raw)
  To: Shuangpeng Bai
  Cc: mani, kwilczynski, kishon, Frank.Li, arnd, gregkh, bhelgaas,
	linux-pci, linux-kernel

On Mon, Jun 22, 2026 at 12:52:49PM -0400, Shuangpeng Bai wrote:
> [You don't often get email from shuangpeng.kernel@gmail.com. Learn why this is important at https://aka.ms/LearnAboutSenderIdentification ]
>
> 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.

Anyways to provent unbound when still open? This driver is only for test
ednpoint funciton, it is not worth to make complex for this unusual case.

Frank

>
> 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
>

^ permalink raw reply	[flat|nested] 9+ messages in thread

end of thread, other threads:[~2026-06-22 20:40 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
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   ` [PATCH v2] " Shuangpeng Bai
2026-06-22 17:15     ` sashiko-bot
2026-06-22 20:40     ` Frank Li

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.