A new security vulnerability, CVE-2025-21859, was recently discovered and fixed in the Linux kernel’s USB MIDI Gadget implementation. This bug could affect devices or development boards using the f_midi function, which allows Linux-powered gadgets (like Raspberry Pi Zero boards) to present themselves as USB MIDI instruments to a host computer (like Windows or macOS).
In this long read, I’ll walk you through what the bug was, how it could hang your device, what’s changed in the kernel source code, and why this is important for you as a developer or tinkerer.
What is the USB MIDI Gadget?
The USB MIDI gadget feature in Linux lets a device act as a USB MIDI interface. This is used for DIY synths, controllers, and more, often on Raspberry Pi or other ARM devices.
The relevant kernel driver is drivers/usb/gadget/function/f_midi.c.
What Happened
The kernel code for the f_midi gadget tried to acquire the same lock twice (a double-lock) when sending MIDI messages. This happened in a scenario known as a *re-entrant call*: the transmit function (f_midi_transmit) ended up calling itself indirectly via a completion callback (f_midi_complete). When this happens, it tries to lock a mutex it already holds—causing a deadlock. The device will hang, freezing MIDI transfer, and may require a reboot to recover.
Plain English Example
Imagine you’re in a room with a door that auto-locks behind you. You go in (lock #1), then while checking the back closet, you find another locked door—to the same room! But you’re already inside, so you never get out. That’s a deadlock.
Vulnerable Code (Schematic Only)
static void f_midi_complete(struct usb_ep *ep, struct usb_request *req)
{
// ... other code ...
f_midi_transmit(midi); // <- Could cause re-entrant lock attempt!
}
The f_midi_transmit function tries to lock a mutex, but if it's already locked by the same code path, you get a kernel hang.
Fix: Instead, Schedule with queue_work()
The fix is to schedule a new task on a work queue, specifically the f_midi_transmit function, using queue_work() at high priority. This ensures that the callback doesn’t directly re-enter the locked function, breaking the deadlock cycle.
Patched Code (Schematic)
static void f_midi_complete(struct usb_ep *ep, struct usb_request *req)
{
// ... other code ...
queue_work(f_midi_workqueue, &midi->transmit_work);
}
Here, f_midi_workqueue is a high-priority workqueue, and midi->transmit_work is a struct work_struct that knows how to call f_midi_transmit.
How Could It Be Exploited?
- Denial of Service (DoS): An attacker (or even an accidental user action) could trigger the deadlock by sending specifically timed MIDI messages, causing your gadget to lock up.
Local Exploit: Only possible if you control the device or the host sends crafted USB data.
No privilege escalation or remote exploit reported yet, but a frozen MIDI controller is bad news for performers and developers.
Embedded boards, synth devices, or any gadget mode device exposing USB-MIDI class.
Mainstream off-the-shelf Linux desktop/server installations are unaffected unless you manually enable this gadget mode.
Original Patch:
Linux USB Gadget Subsystem Docs:
https://www.kernel.org/doc/html/latest/usb/gadget.html
MIDI Gadget Kernel Source:
https://elixir.bootlin.com/linux/latest/source/drivers/usb/gadget/function/f_midi.c
CVE Entry:
CVE-2025-21859 - NVD *(URL may not be live if just published)*
Conclusion
CVE-2025-21859 is a reminder that even well-tested kernel code can have subtle bugs, especially when dealing with concurrency and locks in critical I/O paths. If you build or maintain embedded Linux devices with USB MIDI gadget features, up-to-date kernels and careful review of callback logic are essential.
If you have further questions or need help patching your custom kernel, drop your questions below!
Timeline
Published on: 03/12/2025 10:15:18 UTC
Last modified on: 03/24/2025 15:41:30 UTC