[PATCH 1/1] ALSA: bcd2000: fix use-after-free of MIDI URBs on file close after disconnect
From: Yuanzhe Liu
Date: Wed Sep 23 2026 - 06:41:27 EST
Closing a rawmidi fd of the BCD2000 after its USB interface has been
unbound crashes with a slab-use-after-free in usb_kill_urb():
BUG: KASAN: slab-use-after-free in usb_kill_urb+0x74/0x80
Read of size 8 at addr ffff8880078e7d40 by task amidi/30287
usb_kill_urb+0x74/0x80 drivers/usb/core/urb.c:706
bcd2000_midi_output_close+0xd4/0x140 sound/usb/bcd2000/bcd2000.c:182
close_substream.part.0+0x15f/0x8d0 sound/core/rawmidi.c:560
rawmidi_release_priv+0x21d/0x290 sound/core/rawmidi.c:580
snd_rawmidi_release+0x4e/0xa0 sound/core/rawmidi.c:610
__fput+0x3a6/0xac0 fs/file_table.c:510
Allocated by: usb_alloc_urb <- bcd2000_init_midi <- bcd2000_probe
Freed by: usb_free_urb <- bcd2000_free_usb_related_resources
<- bcd2000_disconnect <- usb_driver_release_interface
<- usbdev_ioctl (USBDEVFS_DISCONNECT)
Root cause: bcd2000_disconnect() calls snd_card_disconnect(), which only
blocks *new* opens, and then immediately kills and frees midi_out_urb and
midi_in_urb. Already-open rawmidi fds, however, keep working: their
->close() callback bcd2000_midi_output_close() unconditionally calls
usb_kill_urb(bcd2k->midi_out_urb) when midi_out_active is set (which it
is, since the completion handler re-submits the out URB), and the URB
completion callbacks re-submit the URBs via bcd2k->midi_{in,out}_urb.
Both dereference pointers that were freed in disconnect -- a UAF. No
physical unplug is needed; USBDEVFS_DISCONNECT on the usbfs node triggers
the same path.
Fix the lifetime mismatch in two parts:
- Wait for the users that are already there: use
snd_card_disconnect_sync() instead of snd_card_disconnect() so that
disconnect blocks until all open card files are released. The rawmidi
close callbacks then run (and call usb_kill_urb()) while the URBs are
still valid, and no userspace-triggered callback can run afterwards.
devices_mutex held by bcd2000_disconnect() is not taken by the rawmidi
close path, so the wait cannot deadlock.
- Mark the device shut down before freeing: add an atomic shutdown flag,
set first in bcd2000_disconnect(), and check it in bcd2000_midi_send()
and in both URB completion handlers so no new URB submission can be
queued while disconnect frees the old ones. Also NULL the URB
pointers after usb_free_urb() so any stray access becomes an
immediate, diagnosable NULL dereference instead of a silent UAF.
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Yuanzhe Liu <25031212351@xxxxxxxxxxxxxxxxx>
---
sound/usb/bcd2000/bcd2000.c | 25 ++++++++++++++++++++-----
1 file changed, 20 insertions(+), 5 deletions(-)
diff --git a/sound/usb/bcd2000/bcd2000.c b/sound/usb/bcd2000/bcd2000.c
index bebb48c..cbf6d39 100644
--- a/sound/usb/bcd2000/bcd2000.c
+++ b/sound/usb/bcd2000/bcd2000.c
@@ -55,6 +55,7 @@ struct bcd2000 {
struct urb *midi_in_urb;
struct usb_anchor anchor;
+ atomic_t shutdown;
};
static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
@@ -131,7 +132,7 @@ static void bcd2000_midi_send(struct bcd2000 *bcd2k)
BUILD_BUG_ON(sizeof(device_cmd_prefix) >= BUFSIZE);
midi_out_substream = READ_ONCE(bcd2k->midi_out_substream);
- if (!midi_out_substream)
+ if (!midi_out_substream || atomic_read(&bcd2k->shutdown))
return;
/* copy command prefix bytes */
@@ -212,7 +213,7 @@ static void bcd2000_output_complete(struct urb *urb)
dev_warn(&urb->dev->dev,
PREFIX "output urb->status: %d\n", urb->status);
- if (urb->status == -ESHUTDOWN)
+ if (urb->status == -ESHUTDOWN || atomic_read(&bcd2k->shutdown))
return;
/* check if there is more data userspace wants to send */
@@ -228,7 +229,8 @@ static void bcd2000_input_complete(struct urb *urb)
dev_warn(&urb->dev->dev,
PREFIX "input urb->status: %i\n", urb->status);
- if (!bcd2k || urb->status == -ESHUTDOWN)
+ if (!bcd2k || urb->status == -ESHUTDOWN ||
+ atomic_read(&bcd2k->shutdown))
return;
if (urb->actual_length > 0)
@@ -353,6 +355,8 @@ static void bcd2000_free_usb_related_resources(struct bcd2000 *bcd2k,
usb_free_urb(bcd2k->midi_out_urb);
usb_free_urb(bcd2k->midi_in_urb);
+ bcd2k->midi_out_urb = NULL;
+ bcd2k->midi_in_urb = NULL;
if (bcd2k->intf) {
usb_set_intfdata(bcd2k->intf, NULL);
@@ -427,8 +431,19 @@ static void bcd2000_disconnect(struct usb_interface *interface)
guard(mutex)(&devices_mutex);
- /* make sure that userspace cannot create new requests */
- snd_card_disconnect(bcd2k->card);
+ /*
+ * Make sure that the URB completion handlers and the rawmidi ops
+ * don't touch the USB device or the URBs any longer.
+ */
+ atomic_set(&bcd2k->shutdown, 1);
+
+ /*
+ * Make sure that userspace cannot create new requests, and wait
+ * until all already-open files are closed so that the rawmidi
+ * close callbacks (which may kill the out URB) run while the
+ * URBs are still valid.
+ */
+ snd_card_disconnect_sync(bcd2k->card);
bcd2000_free_usb_related_resources(bcd2k, interface);
--
2.45.1.windows.1