* [PATCH] PCI: fix use-after-free in pci_pme_list_scan()
@ 2026-09-03 6:07 Torsten Hilbrich
2026-09-03 11:20 ` Ilpo Järvinen
2026-10-01 7:25 ` Christian Hedin
0 siblings, 2 replies; 3+ messages in thread
From: Torsten Hilbrich @ 2026-09-03 6:07 UTC (permalink / raw)
To: Bjorn Helgaas; +Cc: linux-pci, linux-kernel
struct pci_pme_device holds a raw, non-refcounted pointer to the
pci_dev it tracks. A device is added to pci_pme_list by pci_pme_active()
only while its pme_poll flag is set, but that flag can be cleared by the
various PME wakeup paths (pci_acpi_wake_dev(), pcie_pme_handle_request(),
pcie_pme_walk_bus(), pci_pme_wakeup()) without removing the entry from the
list.
On device teardown, pci_stop_dev() -> pci_pme_active(dev, false) skips
the list removal whenever pme_poll has already been cleared, so the entry
is left behind. pci_destroy_dev() then frees the pci_dev while its entry
is still on the list, and the periodic pci_pme_list_scan() work
dereferences the freed structure, causing a use-after-free (observed as a
general protection fault on a PaX 0xfe-poisoned pointer).
Take a reference to the pci_dev for each list entry (pci_dev_get() when
adding) and drop it when the entry is removed, both in pci_pme_list_scan()
and in pci_pme_active(). This keeps the device alive as long as its entry
is on the list, regardless of the pme_poll state or teardown ordering.
Patch is based on v7.3-rc1.
The problem was found when using PaX memory free poisoning (0xfe). Here
is the panic from a v6.18.45 test run. It happened after disconnecting
a TB4 dock with 2 displays connected.
<6>[66245.416348] usb 5-1.4.4.4: USB disconnect, device number 19
<6>[66245.448107] [drm] drm_dp_dpcd_access card0-DP-4 via AMDGPU DM aux hw bus 5: Too many retries, giving up. First error: -5, First reply: 0x0, Last reply: 0x0, Request: 0x9, Address: 0x111, Size: 0x1
<6>[66245.485882] [drm] drm_dp_dpcd_access card0-DP-4 via AMDGPU DM aux hw bus 5: Too many retries, giving up. First error: -5, First reply: 0x0, Last reply: 0x0, Request: 0x8, Address: 0x111, Size: 0x1
<4>[66245.930686] Oops: general protection fault, probably for non-canonical address 0xfefefefefefeff36: 0000 [#1] SMP NOPTI
<4>[66245.931209] CPU: 0 UID: 0 PID: 21940 Comm: kworker/0:4 Tainted: P O 6.18.45-grsec+ #1 PREEMPT(voluntary)
<4>[66245.931623] Tainted: [P]=PROPRIETARY_MODULE, [O]=OOT_MODULE
<4>[66245.931890] Hardware name: LENOVO 21QKS01V00/21QKS01V00, BIOS R2XET42T (1.21 ) 08/20/2026
<4>[66245.932213] Workqueue: events_freezable pci_pme_list_scan
<4>[66245.932475] RIP: 0010:[<ffffffff818f09ec>] pci_pme_list_scan+0x4c/0x1f0
<4>[66245.932761] Code: 1f 03 4c 8b 23 48 89 df 48 81 fb c0 64 ae 84 0f 84 36 01 00 00 48 8b 6b 10 f6 85 9d 00 00 00 20 0f 84 88 00 00 00 48 8b 45 10 <48> 8b 58 38 48 85 db 0f 84 b2 00 00 00 49 89 de 49 81 c6 c0 00 00
<4>[66245.933346] RSP: 0018:ffffc9002109bee8 EFLAGS: 00010202
<4>[66245.933607] RAX: fefefefefefefefe RBX: ffff8901040c10e0 RCX: ffff8907dca25828
<4>[66245.933916] RDX: ffff890181762940 RSI: 0000000000000000 RDI: ffff8901040c10e0
<4>[66245.934227] RBP: ffff8905fe998000 R08: ffffffffffffffff R09: ffff89010000d4c0
<4>[66245.934538] R10: ffff89046b63ff40 R11: fefefefefefefeff R12: ffff8901040c1a60
<4>[66245.934848] R13: ffff8907dca25800 R14: ffff8901000a3205 R15: 0000000000000000
<4>[66245.935172] RBX: kmalloc-32+0x0/0x20 [slab object]
<4>[66245.935433] RCX: cpu_worker_pools+0x28/0x640 [percpu0 25828]
<4>[66245.935718] RDX: task_struct[kworker/0:4+events_freezable 21940 21940]+0x0/0xdc0 [slab object]
<4>[66245.936076] RDI: kmalloc-32+0x0/0x20 [slab object]
<4>[66245.936339] RBP: kmalloc-4k+0x0/0x1000 [slab object]
<4>[66245.936609] RSP: vm[ffffc90021098000 4002 kernel_clone+0xcc/0x450]+0x3ee8/0x4000 [vmalloc]
<4>[66245.936958] R08: -EPERM
<4>[66245.937166] R09: kmalloc-512+0xc0/0x200 [slab object]
<4>[66245.937437] R10: kmalloc-192+0x80/0xc0 [slab object]
<4>[66245.937706] R12: kmalloc-32+0x0/0x20 [slab object]
<4>[66245.937972] R13: cpu_worker_pools+0x0/0x640 [percpu0 25800]
<4>[66245.938261] R14: pool_workqueue+0x5/0x200 [slab object]
<4>[66245.938542] FS: 0000000000000000(0000) GS:ffff890856bdd000(0000) knlGS:0000000000000000
<4>[66245.938893] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
<4>[66245.939187] CR2: ffff9a8a2c97d000 CR3: 0000000182794000 CR4: 0000000000740ef0 shadow CR4: 0000000000740ef0
<4>[66245.939581] PKRU: 55555554
<4>[66245.939807] Stack:
<4>[66245.940015] ffff89046b63fec0 ffffffff84ae6440 ffff8901000a3200 ffff8907dca25800
<4>[66245.940355] ffff8901000a3205 ffffffff812f497a ffff8907dca25800 ffff89046b63ff00
<4>[66245.940697] ffff8907dca25828 ffff89046b63fec0 ffff8907dca25800 0000000000000000
<4>[66245.941045] Call Trace:
<4>[66245.941269] <TASK>
<4>[66245.941484] [<ffffffff812f497a>] process_one_work+0x1ca/0x4b0 ffffc9002109bf10
<4>[66245.941829] [<ffffffff812f569c>] worker_thread+0x16c/0x2f0 ffffc9002109bf48
<4>[66245.942165] [<ffffffff812f5530>] ? __pfx_worker_thread+0x10/0x10 ffffc9002109bf68
<4>[66245.942513] [<ffffffff812fe89f>] kthread+0x12f/0x270 ffffc9002109bf80
<4>[66245.942837] [<ffffffff812fe770>] ? __pfx_kthread+0x10/0x10 ffffc9002109bf88
<4>[66245.943174] [<ffffffff812fe770>] ? __pfx_kthread+0x10/0x10 ffffc9002109bf98
<4>[66245.943508] [<ffffffff81265203>] ret_from_fork+0x173/0x190 ffffc9002109bfc8
<4>[66245.943842] [<ffffffff812fe770>] ? __pfx_kthread+0x10/0x10 ffffc9002109bfd0
<4>[66245.944175] [<ffffffff81221639>] ret_from_fork_asm+0x29/0x50 ffffc9002109bfe8
<4>[66245.944512] </TASK>
<4>[66245.944727] Modules linked in: vtx(O) vboxdrv(O) pl2303 ftdi_sio usbserial modstop_test_unload(O) mcd_drv(O) wacom mac_passthrough(O) dm_crypt_sina(O) chiasmus(PO) cryptoapi_plugger(O) dm_mod
<4>[66245.945340] ---[ end trace 0000000000000000 ]---
Assisted-by: LLM opencode
Signed-off-by: Torsten Hilbrich <torsten.hilbrich@secunet.com>
---
drivers/pci/pci.c | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
index b2879a6be5f80..b3a4b710d7c8d 100644
--- a/drivers/pci/pci.c
+++ b/drivers/pci/pci.c
@@ -2410,6 +2410,7 @@ static void pci_pme_list_scan(struct work_struct *work)
pm_runtime_put(bdev);
} else {
list_del(&pme_dev->list);
+ pci_dev_put(pdev);
kfree(pme_dev);
}
}
@@ -2497,7 +2498,7 @@ void pci_pme_active(struct pci_dev *dev, bool enable)
pci_warn(dev, "can't enable PME#\n");
return;
}
- pme_dev->dev = dev;
+ pme_dev->dev = pci_dev_get(dev);
mutex_lock(&pci_pme_list_mutex);
list_add(&pme_dev->list, &pci_pme_list);
if (list_is_singular(&pci_pme_list))
@@ -2510,6 +2511,7 @@ void pci_pme_active(struct pci_dev *dev, bool enable)
list_for_each_entry(pme_dev, &pci_pme_list, list) {
if (pme_dev->dev == dev) {
list_del(&pme_dev->list);
+ pci_dev_put(pme_dev->dev);
kfree(pme_dev);
break;
}
--
2.47.3
^ permalink raw reply [flat|nested] 3+ messages in thread
* Re: [PATCH] PCI: fix use-after-free in pci_pme_list_scan()
2026-09-03 6:07 [PATCH] PCI: fix use-after-free in pci_pme_list_scan() Torsten Hilbrich
@ 2026-09-03 11:20 ` Ilpo Järvinen
2026-10-01 7:25 ` Christian Hedin
1 sibling, 0 replies; 3+ messages in thread
From: Ilpo Järvinen @ 2026-09-03 11:20 UTC (permalink / raw)
To: Torsten Hilbrich; +Cc: Bjorn Helgaas, linux-pci, linux-kernel
On Thu, 3 Sep 2026, Torsten Hilbrich wrote:
> struct pci_pme_device holds a raw, non-refcounted pointer to the
> pci_dev it tracks. A device is added to pci_pme_list by pci_pme_active()
> only while its pme_poll flag is set, but that flag can be cleared by the
> various PME wakeup paths (pci_acpi_wake_dev(), pcie_pme_handle_request(),
> pcie_pme_walk_bus(), pci_pme_wakeup()) without removing the entry from the
> list.
>
> On device teardown, pci_stop_dev() -> pci_pme_active(dev, false) skips
> the list removal whenever pme_poll has already been cleared, so the entry
> is left behind. pci_destroy_dev() then frees the pci_dev while its entry
> is still on the list, and the periodic pci_pme_list_scan() work
> dereferences the freed structure, causing a use-after-free (observed as a
> general protection fault on a PaX 0xfe-poisoned pointer).
>
> Take a reference to the pci_dev for each list entry (pci_dev_get() when
> adding) and drop it when the entry is removed, both in pci_pme_list_scan()
> and in pci_pme_active(). This keeps the device alive as long as its entry
> is on the list, regardless of the pme_poll state or teardown ordering.
>
> Patch is based on v7.3-rc1.
>
> The problem was found when using PaX memory free poisoning (0xfe). Here
> is the panic from a v6.18.45 test run. It happened after disconnecting
> a TB4 dock with 2 displays connected.
>
> <6>[66245.416348] usb 5-1.4.4.4: USB disconnect, device number 19
> <6>[66245.448107] [drm] drm_dp_dpcd_access card0-DP-4 via AMDGPU DM aux hw bus 5: Too many retries, giving up. First error: -5, First reply: 0x0, Last reply: 0x0, Request: 0x9, Address: 0x111, Size: 0x1
> <6>[66245.485882] [drm] drm_dp_dpcd_access card0-DP-4 via AMDGPU DM aux hw bus 5: Too many retries, giving up. First error: -5, First reply: 0x0, Last reply: 0x0, Request: 0x8, Address: 0x111, Size: 0x1
> <4>[66245.930686] Oops: general protection fault, probably for non-canonical address 0xfefefefefefeff36: 0000 [#1] SMP NOPTI
> <4>[66245.931209] CPU: 0 UID: 0 PID: 21940 Comm: kworker/0:4 Tainted: P O 6.18.45-grsec+ #1 PREEMPT(voluntary)
> <4>[66245.931623] Tainted: [P]=PROPRIETARY_MODULE, [O]=OOT_MODULE
> <4>[66245.931890] Hardware name: LENOVO 21QKS01V00/21QKS01V00, BIOS R2XET42T (1.21 ) 08/20/2026
> <4>[66245.932213] Workqueue: events_freezable pci_pme_list_scan
> <4>[66245.932475] RIP: 0010:[<ffffffff818f09ec>] pci_pme_list_scan+0x4c/0x1f0
> <4>[66245.932761] Code: 1f 03 4c 8b 23 48 89 df 48 81 fb c0 64 ae 84 0f 84 36 01 00 00 48 8b 6b 10 f6 85 9d 00 00 00 20 0f 84 88 00 00 00 48 8b 45 10 <48> 8b 58 38 48 85 db 0f 84 b2 00 00 00 49 89 de 49 81 c6 c0 00 00
> <4>[66245.933346] RSP: 0018:ffffc9002109bee8 EFLAGS: 00010202
> <4>[66245.933607] RAX: fefefefefefefefe RBX: ffff8901040c10e0 RCX: ffff8907dca25828
> <4>[66245.933916] RDX: ffff890181762940 RSI: 0000000000000000 RDI: ffff8901040c10e0
> <4>[66245.934227] RBP: ffff8905fe998000 R08: ffffffffffffffff R09: ffff89010000d4c0
> <4>[66245.934538] R10: ffff89046b63ff40 R11: fefefefefefefeff R12: ffff8901040c1a60
> <4>[66245.934848] R13: ffff8907dca25800 R14: ffff8901000a3205 R15: 0000000000000000
> <4>[66245.935172] RBX: kmalloc-32+0x0/0x20 [slab object]
> <4>[66245.935433] RCX: cpu_worker_pools+0x28/0x640 [percpu0 25828]
> <4>[66245.935718] RDX: task_struct[kworker/0:4+events_freezable 21940 21940]+0x0/0xdc0 [slab object]
> <4>[66245.936076] RDI: kmalloc-32+0x0/0x20 [slab object]
> <4>[66245.936339] RBP: kmalloc-4k+0x0/0x1000 [slab object]
> <4>[66245.936609] RSP: vm[ffffc90021098000 4002 kernel_clone+0xcc/0x450]+0x3ee8/0x4000 [vmalloc]
> <4>[66245.936958] R08: -EPERM
> <4>[66245.937166] R09: kmalloc-512+0xc0/0x200 [slab object]
> <4>[66245.937437] R10: kmalloc-192+0x80/0xc0 [slab object]
> <4>[66245.937706] R12: kmalloc-32+0x0/0x20 [slab object]
> <4>[66245.937972] R13: cpu_worker_pools+0x0/0x640 [percpu0 25800]
> <4>[66245.938261] R14: pool_workqueue+0x5/0x200 [slab object]
> <4>[66245.938542] FS: 0000000000000000(0000) GS:ffff890856bdd000(0000) knlGS:0000000000000000
> <4>[66245.938893] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
> <4>[66245.939187] CR2: ffff9a8a2c97d000 CR3: 0000000182794000 CR4: 0000000000740ef0 shadow CR4: 0000000000740ef0
> <4>[66245.939581] PKRU: 55555554
> <4>[66245.939807] Stack:
> <4>[66245.940015] ffff89046b63fec0 ffffffff84ae6440 ffff8901000a3200 ffff8907dca25800
> <4>[66245.940355] ffff8901000a3205 ffffffff812f497a ffff8907dca25800 ffff89046b63ff00
> <4>[66245.940697] ffff8907dca25828 ffff89046b63fec0 ffff8907dca25800 0000000000000000
> <4>[66245.941045] Call Trace:
> <4>[66245.941269] <TASK>
> <4>[66245.941484] [<ffffffff812f497a>] process_one_work+0x1ca/0x4b0 ffffc9002109bf10
> <4>[66245.941829] [<ffffffff812f569c>] worker_thread+0x16c/0x2f0 ffffc9002109bf48
> <4>[66245.942165] [<ffffffff812f5530>] ? __pfx_worker_thread+0x10/0x10 ffffc9002109bf68
> <4>[66245.942513] [<ffffffff812fe89f>] kthread+0x12f/0x270 ffffc9002109bf80
> <4>[66245.942837] [<ffffffff812fe770>] ? __pfx_kthread+0x10/0x10 ffffc9002109bf88
> <4>[66245.943174] [<ffffffff812fe770>] ? __pfx_kthread+0x10/0x10 ffffc9002109bf98
> <4>[66245.943508] [<ffffffff81265203>] ret_from_fork+0x173/0x190 ffffc9002109bfc8
> <4>[66245.943842] [<ffffffff812fe770>] ? __pfx_kthread+0x10/0x10 ffffc9002109bfd0
> <4>[66245.944175] [<ffffffff81221639>] ret_from_fork_asm+0x29/0x50 ffffc9002109bfe8
> <4>[66245.944512] </TASK>
> <4>[66245.944727] Modules linked in: vtx(O) vboxdrv(O) pl2303 ftdi_sio usbserial modstop_test_unload(O) mcd_drv(O) wacom mac_passthrough(O) dm_crypt_sina(O) chiasmus(PO) cryptoapi_plugger(O) dm_mod
> <4>[66245.945340] ---[ end trace 0000000000000000 ]---
Can you trim this dump a bit, at least the level and timestamps are not
useful here and no need for the end trace line either.
> Assisted-by: LLM opencode
> Signed-off-by: Torsten Hilbrich <torsten.hilbrich@secunet.com>
> ---
> drivers/pci/pci.c | 4 +++-
> 1 file changed, 3 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
> index b2879a6be5f80..b3a4b710d7c8d 100644
> --- a/drivers/pci/pci.c
> +++ b/drivers/pci/pci.c
> @@ -2410,6 +2410,7 @@ static void pci_pme_list_scan(struct work_struct *work)
> pm_runtime_put(bdev);
> } else {
> list_del(&pme_dev->list);
> + pci_dev_put(pdev);
> kfree(pme_dev);
> }
> }
> @@ -2497,7 +2498,7 @@ void pci_pme_active(struct pci_dev *dev, bool enable)
> pci_warn(dev, "can't enable PME#\n");
> return;
> }
> - pme_dev->dev = dev;
> + pme_dev->dev = pci_dev_get(dev);
> mutex_lock(&pci_pme_list_mutex);
> list_add(&pme_dev->list, &pci_pme_list);
> if (list_is_singular(&pci_pme_list))
> @@ -2510,6 +2511,7 @@ void pci_pme_active(struct pci_dev *dev, bool enable)
> list_for_each_entry(pme_dev, &pci_pme_list, list) {
> if (pme_dev->dev == dev) {
> list_del(&pme_dev->list);
> + pci_dev_put(pme_dev->dev);
> kfree(pme_dev);
How about adding a helper for these three calls as they seem common in
those two contexts.
> break;
> }
>
^ permalink raw reply [flat|nested] 3+ messages in thread
* Re: [PATCH] PCI: fix use-after-free in pci_pme_list_scan()
2026-09-03 6:07 [PATCH] PCI: fix use-after-free in pci_pme_list_scan() Torsten Hilbrich
2026-09-03 11:20 ` Ilpo Järvinen
@ 2026-10-01 7:25 ` Christian Hedin
1 sibling, 0 replies; 3+ messages in thread
From: Christian Hedin @ 2026-10-01 7:25 UTC (permalink / raw)
To: torsten.hilbrich; +Cc: bhelgaas, linux-kernel, linux-pci
Hi Torsten,
I hit what looks like the same use-after-free on an unpatched, non-PaX
kernel, so here is a second data point in case it helps the patch along.
Hardware: Framework Laptop 13 (AMD Ryzen AI 300 Series), BIOS 03.05
Kernel: 7.2.5 (distro build, 7.2.5-3-omarchy), not tainted
Device: LG 40WT95UF monitor on USB4, with PCIe and USB tunneled
The monitor went into its automatic standby overnight (laptop awake, lid
closed), which drops the USB4 link and hot-removes the tunneled PCIe
bridges. In the same second as the removal:
thunderbolt 0-2: device disconnected
pcieport 0000:00:01.1: pciehp: Slot(0): Card not present
pci_bus 0000:03: busn_res: [bus 03-21] is released
pci_bus 0000:22: busn_res: [bus 22-40] is released
pci_bus 0000:41: busn_res: [bus 41-5f] is released
pci_bus 0000:02: busn_res: [bus 02-5f] is released
BUG: unable to handle page fault for address: 0000075700000060
#PF: supervisor read access in kernel mode
#PF: error_code(0x0000) - not-present page
Oops: Oops: 0000 [#1] SMP NOPTI
CPU: 10 UID: 0 PID: 4136500 Comm: kworker/10:1 Not tainted 7.2.5-3-omarchy #1 PREEMPT(full)
Workqueue: events_freezable pci_pme_list_scan
RIP: 0010:pci_pme_list_scan+0x4e/0x220
Code: ... 48 8b 43 10 <4c> 8b 60 38 4d 85 e4 0f 84 b8 00 00 00 ...
RAX: 0000075700000028 RBX: ffff8b26f0cca000
Call Trace:
process_one_work+0x19f/0x370
worker_thread+0x1b1/0x330
kthread+0xe4/0x120
ret_from_fork+0x2bd/0x350
ret_from_fork_asm+0x1a/0x30
note: kworker/10:1[4136500] exited with irqs disabled
It faults on the same load as your trace (bus->self, 0x38 off RAX), with a
garbage pdev->bus in RAX instead of the 0xfe poison, i.e. the pci_dev at RBX had already been freed and reused.
Without free poisoning the stale pointer is just whatever landed there,
which is probably why this is rarely seen.
One thing worth adding to the commit message: without panic_on_oops the
damage is not limited to the one worker. It dies while holding
pci_pme_list_mutex, so every later pci_pme_active() blocks forever. Here
that meant:
- irq/34-pciehp and several pm workqueue workers stuck in D state, so
the monitor's USB and PCIe functions never came back on replug (DP
tunnelling still worked).
- Any config space read that needs a runtime resume hangs unkillably:
task:lspci state:D
__mutex_lock.constprop.0+0x3e6/0x930
pci_pme_active+0x158/0x1f0
__pci_enable_wake+0x90/0xc0
pci_pm_runtime_resume+0x9a/0x130
...
pci_read_config+0x98/0x310
- System suspend failed in a loop for four hours ("Freezing user space
processes failed ... 3 tasks refusing to freeze"), and a clean reboot
hung as well.
So on a stock kernel a single hot-unplug can leave the machine needing a
hard power-off, which may be an argument for Cc: stable.
It is a race here: the same boot had two earlier disconnects of the same
device without an oops.
I can test a v2 on this machine if that is useful, though reproducing may
take a while given how rarely it fires. Full kernel log available on
request.
Thanks,
Christian
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-10-01 7:26 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-03 6:07 [PATCH] PCI: fix use-after-free in pci_pme_list_scan() Torsten Hilbrich
2026-09-03 11:20 ` Ilpo Järvinen
2026-10-01 7:25 ` Christian Hedin
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®