[PATCH] media: em28xx: don't unlink a dual-TS dev_next that was never listed
From: Vincent CAZARD
Date: Wed Oct 07 2026 - 10:55:18 EST
Commit 826915b6b65e ("media: em28xx: fix use-after-free of
dev_next->devlist on disconnect") made em28xx_close_extension()
unconditionally unlink dev->dev_next from em28xx_devlist.
However, on regular dual-TS boards dev->dev_next is never added to that
list: em28xx_duplicate_dev() creates it with kmemdup() at probe time,
before the primary device is linked, and em28xx_init_extension() only
calls list_add_tail() for the primary device. dev_next->devlist is
therefore a copy of an unlinked, zeroed list_head, and the new
list_del() hits a NULL ->next.
With CONFIG_DEBUG_LIST this triggers a BUG on every USB disconnect of
such a board, e.g. a Hauppauge WinTV-dualHD (2040:8265), and with
panic_on_oops=1 the whole machine reboots:
list_del corruption, ffff88b642e141e8->next is NULL
kernel BUG at lib/list_debug.c:52!
Oops: invalid opcode: 0000 [#1] PREEMPT SMP PTI
Workqueue: usb_hub_wq hub_event [usbcore]
RIP: 0010:__list_del_entry_valid_or_report.cold+0xf/0x6f
Call Trace:
em28xx_close_extension+0x74/0x130 [em28xx]
em28xx_usb_disconnect.cold+0x71/0xbc [em28xx]
usb_unbind_interface+0x93/0x290 [usbcore]
device_release_driver_internal+0x19c/0x200
bus_remove_device+0xe9/0x1b0
device_del+0x161/0x3e0
usb_disable_device+0xd4/0x1f0 [usbcore]
usb_disconnect+0xe2/0x300 [usbcore]
hub_event+0xe30/0x1980 [usbcore]
Initialise dev_next->devlist in em28xx_duplicate_dev() so it is a valid
empty list head, and only unlink it in em28xx_close_extension() when it
was actually added to em28xx_devlist. This keeps the use-after-free fix
for configurations where dev_next does get listed.
Fixes: 826915b6b65e ("media: em28xx: fix use-after-free of dev_next->devlist on disconnect")
Cc: stable@xxxxxxxxxxxxxxx
Assisted-by: LLM
Signed-off-by: Vincent CAZARD <vincentcazard@xxxxxxxx>
---
The offending commit has been backported to stable; this was hit on
Debian's 6.12.111 (linux-image-6.12.111+deb13-amd64), which reboots on
every disconnect of the board above (here the board is attached over
USB/IP, so a disconnect happens whenever the remote side is re-bound).
Tested on 6.12.111 with an equivalent change built out of tree: two
forced disconnects/reconnects of the WinTV-dualHD, both tuners come back,
no list_del splat and no reboot. The tested variant additionally checked
dev_next->devlist.next for NULL before list_empty(); that check is
redundant once em28xx_duplicate_dev() initialises the list head, so it is
dropped here. Not yet tested on mainline.
AI assistance details (per Documentation/process/generated-content.rst):
the assistant was Claude (Anthropic) running in the Hermes agent with
shell access to the affected machine. It was asked to find why the node
rebooted when the tuner was used; it read the pstore oops, compared the
Debian 6.12.111 em28xx sources with mainline, identified 826915b6b65e,
wrote the patch and this changelog, built the out-of-tree module and ran
the disconnect test. All of the patch and changelog text is
AI-generated.
drivers/media/usb/em28xx/em28xx-cards.c | 1 +
drivers/media/usb/em28xx/em28xx-core.c | 4 ++--
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/drivers/media/usb/em28xx/em28xx-cards.c b/drivers/media/usb/em28xx/em28xx-cards.c
index b7c534f..5f0f862 100644
--- a/drivers/media/usb/em28xx/em28xx-cards.c
+++ b/drivers/media/usb/em28xx/em28xx-cards.c
@@ -3985,6 +3985,7 @@ static int em28xx_duplicate_dev(struct em28xx *dev)
} while (test_and_set_bit(nr, em28xx_devused));
sec_dev->devno = nr;
snprintf(sec_dev->name, 28, "em28xx #%d", nr);
+ INIT_LIST_HEAD(&sec_dev->devlist);
sec_dev->dev_next = NULL;
dev->dev_next = sec_dev;
return 0;
diff --git a/drivers/media/usb/em28xx/em28xx-core.c b/drivers/media/usb/em28xx/em28xx-core.c
index 0b998e7..5eb1705 100644
--- a/drivers/media/usb/em28xx/em28xx-core.c
+++ b/drivers/media/usb/em28xx/em28xx-core.c
@@ -1265,8 +1265,8 @@ void em28xx_close_extension(struct em28xx *dev)
ops->fini(dev);
}
}
- if (dev->dev_next)
- list_del(&dev->dev_next->devlist);
+ if (dev->dev_next && !list_empty(&dev->dev_next->devlist))
+ list_del_init(&dev->dev_next->devlist);
list_del(&dev->devlist);
mutex_unlock(&em28xx_devlist_mutex);
}
--
2.39.5