tascam_stop_pcm_work_handler() is queued by feedback_urb_complete() after a feedback sync loss, then reads tascam->playback_substream and tascam->capture_substream without any lock and calls snd_pcm_stop() on them. PCM close does not cancel this work. If close completes first, snd_pcm_detach_substream() sets substream->runtime to NULL and frees the snd_pcm_runtime; when the handler reaches snd_pcm_stop(), snd_pcm_pre_stop() re-reads substream->runtime and dereferences the NULL pointer. This is a conditional NULL-pointer dereference / oops on an ordinary PCM close; a narrower runtime-UAF window is theoretically possible but was not reproduced, so this report does not claim UAF. The handler also violates the ALSA convention that snd_pcm_action() must be called with the PCM stream lock held. The detailed description: * Test environment / affected version Linux 7.3.0 source tree (VERSION=7, PATCHLEVEL=3, SUBLEVEL=0), sound/usb/usx2y/us144mkii_playback.c, us144mkii_pcm.c and us144mkii.h. The driver binds the TASCAM US-144MKII (0644:8020) and US-144 (0644:800f). The relevant configuration is CONFIG_SND_USB_US144MKII=m. This report is based on source inspection, plus a synthetic ALSA PCM model used to validate the close/detach ordering. I have not run the real driver on TASCAM hardware; no real-hardware result is claimed. The attached POC is that synthetic-ordering model, not a TASCAM hardware reproducer. * Mechanism 1. The work is queued on feedback sync loss: if (tascam->feedback_synced) { tascam->feedback_consecutive_errors++; if (tascam->feedback_consecutive_errors > FEEDBACK_SYNC_LOSS_THRESHOLD) { dev_err(tascam->card->dev, "Fatal: Feedback sync lost. Stopping stream.\n"); schedule_work(&tascam->stop_pcm_work); tascam->feedback_synced = false; break; } } (us144mkii_playback.c:347-356). FEEDBACK_SYNC_LOSS_THRESHOLD is 41 (us144mkii.h:88), so the condition is reached after 42 consecutive feedback errors. 2. The handler reads the pointers without a lock: void tascam_stop_pcm_work_handler(struct work_struct *work) { struct tascam_card *tascam = container_of(work, struct tascam_card, stop_pcm_work); if (tascam->playback_substream) snd_pcm_stop(tascam->playback_substream, SNDRV_PCM_STATE_XRUN); if (tascam->capture_substream) snd_pcm_stop(tascam->capture_substream, SNDRV_PCM_STATE_XRUN); } (us144mkii_playback.c:447-456). 3. PCM close only NULLs the pointers and does not cancel the work: tascam->playback_substream = NULL; /* or capture */ return 0; (us144mkii_playback.c:35-42, us144mkii_capture.c:35-42). There is no cancel_work_sync(&tascam->stop_pcm_work) in either close callback. 4. ALSA then detaches and frees the runtime: snd_pcm_drop(substream); ... substream->ops->close(substream); ... snd_pcm_detach_substream(substream); (sound/core/pcm_native.c:2785-2805; sound/core/pcm.c:978-1005: substream->runtime = NULL at 997, kfree(runtime) at 1001). 5. snd_pcm_stop() is not safe without the stream lock. snd_pcm_action() is documented "Note: call with stream lock" (sound/core/pcm_native.c:1411-1412); snd_pcm_pre_stop() does: struct snd_pcm_runtime *runtime = substream->runtime; if (runtime->state == SNDRV_PCM_STATE_OPEN) return -EBADFD; runtime->trigger_master = substream; (sound/core/pcm_native.c:1549-1558). After detach runtime is NULL, so the first dereference is a NULL-pointer oops. snd_pcm_do_stop()/snd_pcm_post_stop() would also touch runtime->trigger_master and runtime->sleep/tsleep (1570-1577). 6. Interleaving: the handler loads tascam->playback_substream (or capture_substream) before close clears it, is preempted, close then detaches and frees the runtime, and the handler proceeds into snd_pcm_stop() and touches the NULL/freed runtime. The workqueue item does not need to overlap with an URB completion at close time; it only needs to be queued before close and executed after detach. If the handler already passed a runtime non-NULL check before detach, a narrower UAF is possible, but that form was not reproduced and is not the primary claim. * Trigger model Feedback sync loss is needed: 42 consecutive feedback errors while playback or capture is running. This can be produced with a USB traffic fault injector, a short-packet/loss injection on the feedback endpoint, or a programmable USB gadget emulating the device. After the driver logs the feedback sync loss and queues the work, close the PCM stream before the work runs. No special privilege beyond access to the PCM device is required; TASCAM US-144MKII/US-144 hardware (or an emulated equivalent) is required. Consequence: NULL-pointer dereference / oops in tascam_stop_pcm_work_handler() -> snd_pcm_stop() -> snd_pcm_pre_stop(); because the work is already queued, this is independent of the URB completion timing in Case 1. * Reproducer (attached POC) Attached files: tascam_poc.c - synthetic ALSA PCM kernel module (C2 mode pcm2) pcmtest.c - userland exerciser Makefile - builds tascam_poc.ko against the running kernel This POC does not drive the TASCAM device or inject feedback errors. It creates a synthetic ALSA PCM card whose .close() only drops the substream pointer, queues work that caches the substream, starts the stream, and lets ALSA detach the runtime before the work calls snd_pcm_stop(). It therefore reproduces the close/detach ordering in point 4 above, not the real feedback-error path. Build and run: make gcc -static -O2 -o pcmtest pcmtest.c sudo modprobe snd-pcm sudo insmod ./tascam_poc.ko mode=pcm2 sudo ./pcmtest 1 dmesg | tail -50 sudo rmmod tascam_poc Expected result: sndusb_race: c2 call snd_pcm_stop ss=... runtime=0000000000000000 BUG: KASAN: null-ptr-deref in snd_pcm_pre_stop+0x24/0x50 BUG: kernel NULL pointer dereference, address: 0000000000000000 Oops: 0000 [#1] SMP KASAN NOPTI RIP: snd_pcm_pre_stop+0x24/0x50 Negative control (close cancels the work): sudo insmod ./tascam_poc.ko mode=pcm2 safe=1 sudo ./pcmtest 1 Expected: no oops/KASAN report. The module creates a capture PCM at card index 0; if that index is occupied, check /proc/asound/cards and adjust DEV in pcmtest.c. A minimal real-hardware KASAN test would: - start playback and/or capture; - inject 42 consecutive feedback errors (or use a gadget that returns short/error packets on the feedback endpoint) until the driver logs "Fatal: Feedback sync lost. Stopping stream."; - immediately close the PCM stream in a loop; - watch dmesg for a KASAN report with tascam_stop_pcm_work_handler in the stack. * Suggested direction Make .close() cancel the work before returning to ALSA: clear the active flag and pointer under tascam->lock, kill the relevant URBs per-URB, then cancel_work_sync(&tascam->stop_pcm_work) (and stop_work/capture_work as appropriate). cancel_work_sync() is the lifecycle guarantee; it waits for a running handler. In the handler, use snd_pcm_stop_xrun() (sound/core/pcm_native.c:1628-1635), which takes the PCM stream lock and rechecks substream->runtime. That fixes the API-convention violation, but it is not a substitute for cancelling the work in close: snd_pcm_detach_substream() does not take the stream lock, so a handler that already passed the runtime check can still race with detach. I am happy to test a proposed fix. Please keep me in Cc; I am not subscribed to the lists. Regards, Hanxiang Xu xuhx@hust.edu.cn