* [BUG] PCI/VGA: KASAN slab-use-after-free in dev_driver_string from vga_arb_release
@ 2026-06-22 4:01 Shuangpeng
2026-06-30 16:30 ` [PATCH] PCI/VGA: vgaarb: Hold pci_dev references for per-file cards Shuangpeng Bai
0 siblings, 1 reply; 3+ messages in thread
From: Shuangpeng @ 2026-06-22 4:01 UTC (permalink / raw)
To: bhelgaas; +Cc: linux-pci, linux-kernel
Hi Kernel Maintainers,
I hit the following report while testing current upstream kernel:
KASAN: slab-use-after-free in dev_driver_string from vga_arb_release
on commit: e8c2f9fdadee7cbc75134dc463c1e0d856d6e5c7 (May 25 2026)
The reproducer and .config files are here.
https://gist.github.com/shuangpengbai/47ec0715a0120beaacf5009460b90687
I'm happy to test debug patches or provide additional information.
Reported-by: Shuangpeng Bai <shuangpeng.kernel@gmail.com>
[ 250.268327][ T8342] ==================================================================
[ 250.271034][ T8342] BUG: KASAN: slab-use-after-free in dev_driver_string (drivers/base/core.c:2406)
[ 250.273371][ T8342] Read of size 8 at addr ffff88810aebc138 by task bash/8342
[ 250.274604][ T8342]
[ 250.275019][ T8342] CPU: 0 UID: 0 PID: 8342 Comm: bash Not tainted 7.1.0-rc5+ #106 PREEMPT(full)
[ 250.275027][ T8342] 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
[ 250.275031][ T8342] Call Trace:
[ 250.275044][ T8342] <TASK>
[ 250.275049][ T8342] dump_stack_lvl (lib/dump_stack.c:94 lib/dump_stack.c:120)
[ 250.275108][ T8342] print_report (mm/kasan/report.c:378 mm/kasan/report.c:482)
[ 250.275177][ T8342] kasan_report (mm/kasan/report.c:595)
[ 250.275190][ T8342] dev_driver_string (drivers/base/core.c:2406)
[ 250.275196][ T8342] __dynamic_dev_dbg (lib/dynamic_debug.c:906)
[ 250.275228][ T8342] vga_arb_release (drivers/pci/vgaarb.c:1436)
[ 250.275245][ T8342] __fput (fs/file_table.c:510)
[ 250.275260][ T8342] fput_close_sync (fs/file_table.c:615)
[ 250.275265][ T8342] __x64_sys_close (fs/open.c:1507 fs/open.c:1492 fs/open.c:1492)
[ 250.275271][ T8342] do_syscall_64 (arch/x86/entry/syscall_64.c:63 arch/x86/entry/syscall_64.c:94)
[ 250.275327][ T8342] entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:121)
[ 250.275333][ T8342] RIP: 0033:0x7f65140dbc03
[ 250.275341][ T8342] Code: e9 37 ff ff ff e8 2d f9 01 00 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 00 64 8b 04 25 18 00 00 00 85 c0 75 14 b8 03 00 00 00 0f 05 <48> 3d 00 f0 ff ff 77 45 c3 0f 1f 40 00 48 83 ec 18 89 7c 24 0c e8
[ 250.275346][ T8342] RSP: 002b:00007ffce08e3808 EFLAGS: 00000246 ORIG_RAX: 0000000000000003
[ 250.275363][ T8342] RAX: ffffffffffffffda RBX: 0000564bd1808ba0 RCX: 00007f65140dbc03
[ 250.275367][ T8342] RDX: 00000000ffffffff RSI: 0000000000000001 RDI: 000000000000000a
[ 250.275371][ T8342] RBP: 00007ffce08e3890 R08: 0000000000000000 R09: 0000000000000000
[ 250.275374][ T8342] R10: 00007f651417efc0 R11: 0000000000000246 R12: 0000000000000009
[ 250.275377][ T8342] R13: 0000000000000009 R14: 000000000000000a R15: 0000000000000000
[ 250.275393][ T8342] </TASK>
[ 250.275395][ T8342]
[ 250.295059][ T8342] Freed by task 8342 on cpu 1 at 249.233016s:
[ 250.295689][ T8342] kasan_save_track (mm/kasan/common.c:57 mm/kasan/common.c:78)
[ 250.296168][ T8342] kasan_save_free_info (mm/kasan/generic.c:584)
[ 250.296682][ T8342] __kasan_slab_free (mm/kasan/common.c:253 mm/kasan/common.c:285)
[ 250.297176][ T8342] kfree (include/linux/kasan.h:235 mm/slub.c:2689 mm/slub.c:6251 mm/slub.c:6566)
[ 250.297593][ T8342] device_release (drivers/base/core.c:2570)
[ 250.298062][ T8342] kobject_put (lib/kobject.c:689 lib/kobject.c:720 include/linux/kref.h:65 lib/kobject.c:737)
[ 250.298528][ T8342] pci_stop_and_remove_bus_device_locked (drivers/pci/remove.c:132 drivers/pci/remove.c:139)
[ 250.299187][ T8342] remove_store (drivers/pci/pci-sysfs.c:525)
[ 250.299631][ T8342] kernfs_fop_write_iter (fs/kernfs/file.c:352)
[ 250.300179][ T8342] vfs_write (fs/read_write.c:595 fs/read_write.c:688)
[ 250.300622][ T8342] ksys_write (fs/read_write.c:740)
[ 250.301054][ T8342] do_syscall_64 (arch/x86/entry/syscall_64.c:63 arch/x86/entry/syscall_64.c:94)
[ 250.301525][ T8342] entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:121)
[ 250.302121][ T8342]
[ 250.302372][ T8342] The buggy address belongs to the object at ffff88810aebc000
[ 250.302372][ T8342] which belongs to the cache kmalloc-4k of size 4096
[ 250.303772][ T8342] The buggy address is located 312 bytes inside of
[ 250.303772][ T8342] freed 4096-byte region [ffff88810aebc000, ffff88810aebd000)
[ 250.305161][ T8342]
Best,
Shuangpeng
^ permalink raw reply [flat|nested] 3+ messages in thread
* [PATCH] PCI/VGA: vgaarb: Hold pci_dev references for per-file cards
2026-06-22 4:01 [BUG] PCI/VGA: KASAN slab-use-after-free in dev_driver_string from vga_arb_release Shuangpeng
@ 2026-06-30 16:30 ` Shuangpeng Bai
2026-06-30 16:46 ` sashiko-bot
0 siblings, 1 reply; 3+ messages in thread
From: Shuangpeng Bai @ 2026-06-30 16:30 UTC (permalink / raw)
To: Bjorn Helgaas; +Cc: linux-pci, linux-kernel, Shuangpeng Bai
The VGA arbiter stores pci_dev pointers in each /dev/vga_arbiter file's
private state to track the target device and per-card lock counts. These
pointers may outlive PCI hot-remove, but the private state did not hold
pci_dev references for them.
After a userspace client opens /dev/vga_arbiter, selects and locks a PCI
VGA device, removes that device via sysfs, and closes the old fd,
vga_arb_release() can still dereference uc->pdev for dev_dbg() and
vga_put(). If the PCI device has already been freed, dynamic debug may
read the freed struct device in dev_driver_string(), triggering a KASAN
use-after-free.
Make each non-NULL priv->cards[] entry own one pci_dev reference.
priv->target is kept only as an alias of a tracked cards[] entry and does
not own an extra reference. When selecting a target, consume the lookup
reference by either storing it in a new cards[] entry or dropping it if
the device is already tracked. On release, use the still-referenced
pdevs to clean up lock accounting, then drop the pci_dev references after
leaving the spinlock.
Take vga_lock while converting vga_default_device() into a referenced
pci_dev and while looking up vgadev entries, so the reference acquisition
and vga_list traversal are serialized with VGA device removal.
Fixes: deb2d2ecd43d ("PCI/GPU: implement VGA arbitration on Linux")
Closes: https://lore.kernel.org/r/330FD8DD-ECBF-4531-900E-5B976FA9DF90@gmail.com/
Signed-off-by: Shuangpeng Bai <shuangpeng.kernel@gmail.com>
---
drivers/pci/vgaarb.c | 87 +++++++++++++++++++++++++++++++++++---------
1 file changed, 70 insertions(+), 17 deletions(-)
diff --git a/drivers/pci/vgaarb.c b/drivers/pci/vgaarb.c
index c360eee11dd9..244671d14e08 100644
--- a/drivers/pci/vgaarb.c
+++ b/drivers/pci/vgaarb.c
@@ -1044,6 +1044,58 @@ struct vga_arb_private {
spinlock_t lock;
};
+/*
+ * Each non-NULL priv->cards[i].pdev owns one pci_dev reference.
+ * priv->target is only an alias of one of priv->cards[] and does not
+ * own an extra reference.
+ *
+ * On success, this consumes the caller's @pdev reference. If the device
+ * is already tracked, the temporary reference is dropped; otherwise it is
+ * stored in a new cards[] entry. On failure, the caller still owns @pdev.
+ */
+static int vga_arb_set_target(struct vga_arb_private *priv,
+ struct pci_dev *pdev)
+{
+ unsigned long flags;
+ int i, ret = -ENOMEM;
+
+ spin_lock_irqsave(&priv->lock, flags);
+ for (i = 0; i < MAX_USER_CARDS; i++) {
+ if (priv->cards[i].pdev == pdev) {
+ priv->target = priv->cards[i].pdev;
+ ret = 0;
+ break;
+ }
+ if (!priv->cards[i].pdev) {
+ priv->cards[i].pdev = pdev;
+ priv->cards[i].io_cnt = 0;
+ priv->cards[i].mem_cnt = 0;
+ priv->target = priv->cards[i].pdev;
+ pdev = NULL;
+ ret = 0;
+ break;
+ }
+ }
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ if (!ret)
+ pci_dev_put(pdev);
+
+ return ret;
+}
+
+static struct pci_dev *vga_arb_get_default_pdev(void)
+{
+ struct pci_dev *pdev;
+ unsigned long flags;
+
+ spin_lock_irqsave(&vga_lock, flags);
+ pdev = pci_dev_get(vga_default_device());
+ spin_unlock_irqrestore(&vga_lock, flags);
+
+ return pdev;
+}
+
static LIST_HEAD(vga_user_list);
static DEFINE_SPINLOCK(vga_user_lock);
@@ -1294,13 +1346,14 @@ static ssize_t vga_arb_write(struct file *file, const char __user *buf,
} else if (strncmp(curr_pos, "target ", 7) == 0) {
unsigned int domain, bus, devfn;
struct vga_device *vgadev;
+ unsigned long flags;
curr_pos += 7;
remaining -= 7;
pr_debug("client 0x%p called 'target'\n", priv);
/* If target is default */
if (!strncmp(curr_pos, "default", 7))
- pdev = pci_dev_get(vga_default_device());
+ pdev = vga_arb_get_default_pdev();
else {
if (!vga_pci_str_to_vars(curr_pos, remaining,
&domain, &bus, &devfn)) {
@@ -1321,7 +1374,9 @@ static ssize_t vga_arb_write(struct file *file, const char __user *buf,
pdev);
}
+ spin_lock_irqsave(&vga_lock, flags);
vgadev = vgadev_find(pdev);
+ spin_unlock_irqrestore(&vga_lock, flags);
pr_debug("vgadev %p\n", vgadev);
if (vgadev == NULL) {
if (pdev) {
@@ -1333,18 +1388,8 @@ static ssize_t vga_arb_write(struct file *file, const char __user *buf,
goto done;
}
- priv->target = pdev;
- for (i = 0; i < MAX_USER_CARDS; i++) {
- if (priv->cards[i].pdev == pdev)
- break;
- if (priv->cards[i].pdev == NULL) {
- priv->cards[i].pdev = pdev;
- priv->cards[i].io_cnt = 0;
- priv->cards[i].mem_cnt = 0;
- break;
- }
- }
- if (i == MAX_USER_CARDS) {
+ ret_val = vga_arb_set_target(priv, pdev);
+ if (ret_val) {
vgaarb_dbg(&pdev->dev, "maximum user cards (%d) number reached, ignoring this one!\n",
MAX_USER_CARDS);
pci_dev_put(pdev);
@@ -1354,7 +1399,6 @@ static ssize_t vga_arb_write(struct file *file, const char __user *buf,
}
ret_val = count;
- pci_dev_put(pdev);
goto done;
@@ -1394,6 +1438,7 @@ static __poll_t vga_arb_fpoll(struct file *file, poll_table *wait)
static int vga_arb_open(struct inode *inode, struct file *file)
{
+ struct pci_dev *pdev;
struct vga_arb_private *priv;
unsigned long flags;
@@ -1410,8 +1455,9 @@ static int vga_arb_open(struct inode *inode, struct file *file)
spin_unlock_irqrestore(&vga_user_lock, flags);
/* Set the client's lists of locks */
- priv->target = vga_default_device(); /* Maybe this is still null! */
- priv->cards[0].pdev = priv->target;
+ pdev = vga_arb_get_default_pdev(); /* Maybe this is still null! */
+ priv->cards[0].pdev = pdev;
+ priv->target = pdev;
priv->cards[0].io_cnt = 0;
priv->cards[0].mem_cnt = 0;
@@ -1422,8 +1468,9 @@ static int vga_arb_release(struct inode *inode, struct file *file)
{
struct vga_arb_private *priv = file->private_data;
struct vga_arb_user_card *uc;
+ struct pci_dev *pdevs[MAX_USER_CARDS];
unsigned long flags;
- int i;
+ int i, nr_pdevs = 0;
pr_debug("%s\n", __func__);
@@ -1439,9 +1486,15 @@ static int vga_arb_release(struct inode *inode, struct file *file)
vga_put(uc->pdev, VGA_RSRC_LEGACY_IO);
while (uc->mem_cnt--)
vga_put(uc->pdev, VGA_RSRC_LEGACY_MEM);
+ pdevs[nr_pdevs++] = uc->pdev;
+ uc->pdev = NULL;
}
+ priv->target = NULL;
spin_unlock_irqrestore(&vga_user_lock, flags);
+ for (i = 0; i < nr_pdevs; i++)
+ pci_dev_put(pdevs[i]);
+
kfree(priv);
return 0;
--
2.43.0
^ permalink raw reply related [flat|nested] 3+ messages in thread
* Re: [PATCH] PCI/VGA: vgaarb: Hold pci_dev references for per-file cards
2026-06-30 16:30 ` [PATCH] PCI/VGA: vgaarb: Hold pci_dev references for per-file cards Shuangpeng Bai
@ 2026-06-30 16:46 ` sashiko-bot
0 siblings, 0 replies; 3+ messages in thread
From: sashiko-bot @ 2026-06-30 16:46 UTC (permalink / raw)
To: Shuangpeng Bai; +Cc: linux-pci
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] Concurrent `lock`, `unlock`, and `trylock` commands in `vga_arb_write` cause data races on `uc->io_cnt` and `uc->mem_cnt`, leading to global resource leaks or severe CPU stalls (Denial of Service).
--
commit 17b88a219897b83c9eda1acce8ef44e9f9f96033
Author: Shuangpeng Bai <shuangpeng.kernel@gmail.com>
PCI/VGA: vgaarb: Hold pci_dev references for per-file cards
The VGA arbiter stores pci_dev pointers in each /dev/vga_arbiter file's
private state to track the target device and per-card lock counts. This
patch ensures each tracked non-NULL cards[] entry owns a pci_dev reference
to prevent KASAN use-after-free bugs if the device is removed via sysfs.
Closes: https://lore.kernel.org/r/330FD8DD-ECBF-4531-900E-5B976FA9DF90@gmail.com/
> diff --git a/drivers/pci/vgaarb.c b/drivers/pci/vgaarb.c
> --- a/drivers/pci/vgaarb.c
> +++ b/drivers/pci/vgaarb.c
> @@ -1422,8 +1468,9 @@ static int vga_arb_release(struct inode *inode, struct file *file)
[ ... ]
> @@ -1439,9 +1486,15 @@ static int vga_arb_release(struct inode *inode, struct file *file)
> if (uc->pdev == NULL)
> continue;
> vgaarb_dbg(&uc->pdev->dev, "uc->io_cnt == %d, uc->mem_cnt == %d\n",
> uc->io_cnt, uc->mem_cnt);
> while (uc->io_cnt--)
> vga_put(uc->pdev, VGA_RSRC_LEGACY_IO);
[Severity: High]
This is a pre-existing issue, but can concurrent calls to vga_arb_write()
with unlock commands cause a denial of service here?
If userspace uses pwrite64 or io_uring to issue concurrent unlock commands
on the same file descriptor, two threads can simultaneously pass the
uc->io_cnt != 0 validation check in vga_arb_write().
Because the decrement happens locklessly after a blocking call to vga_put():
vga_arb_write() {
if (io_state & VGA_RSRC_LEGACY_IO && uc->io_cnt == 0) {
ret_val = -EINVAL;
goto done;
}
...
vga_put(pdev, io_state);
if (io_state & VGA_RSRC_LEGACY_IO)
uc->io_cnt--;
}
Both threads can execute uc->io_cnt--, which underflows the counter to
UINT_MAX.
When the file is closed, vga_arb_release() executes this loop:
while (uc->io_cnt--)
vga_put(uc->pdev, VGA_RSRC_LEGACY_IO);
This loop executes ~4.29 billion times while holding the vga_user_lock
spinlock with interrupts disabled, completely stalling the CPU.
Does this resource counting need to be properly serialized or converted to
atomic types to prevent a hard lockup?
> while (uc->mem_cnt--)
> vga_put(uc->pdev, VGA_RSRC_LEGACY_MEM);
> + pdevs[nr_pdevs++] = uc->pdev;
> + uc->pdev = NULL;
> }
--
Sashiko AI review · https://sashiko.dev/#/patchset/86061f17ea5f1fb76791ca5cc787ce84b6752f46.1782788237.git.shuangpeng.kernel@gmail.com?part=1
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-06-30 16:46 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-22 4:01 [BUG] PCI/VGA: KASAN slab-use-after-free in dev_driver_string from vga_arb_release Shuangpeng
2026-06-30 16:30 ` [PATCH] PCI/VGA: vgaarb: Hold pci_dev references for per-file cards Shuangpeng Bai
2026-06-30 16:46 ` sashiko-bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox