* Re: [BUG] ALSA: usb-audio: use-after-free in snd_dualsense_ih_match during rapid USB reconnect
2026-05-11 21:58 [BUG] ALSA: usb-audio: use-after-free in snd_dualsense_ih_match during rapid USB reconnect Sean Brar
@ 2026-05-19 13:44 ` Takashi Iwai
0 siblings, 0 replies; 2+ messages in thread
From: Takashi Iwai @ 2026-05-19 13:44 UTC (permalink / raw)
To: Sean Brar
Cc: linux-input, linux-sound, roderick.colenbrander, jikos, bentiss,
perex, tiwai
On Mon, 11 May 2026 23:58:30 +0200,
Sean Brar wrote:
>
> A rapid USB disconnect/reconnect cycle on a DualSense controller
> (054c:0ce6) triggers a use-after-free in snd_dualsense_ih_match()
> (sound/usb/mixer_quirks.c), resulting in a general protection fault
> and leaving the USB subsystem in an unrecoverable state requiring a
> hard reboot.
>
> Kernel version: 7.0.5-arch1-1 (also reproduced the trigger on
> 7.0.3-arch1-2; affected code in sound/usb/mixer_quirks.c is
> unchanged on current mainline)
>
> Hardware:
> - Sony DualSense Wireless Controller (054c:0ce6, bcdDevice=1.00)
> - hw_version=0x00000711, fw_version=0x0110002a
> - Host: Gigabyte B550 AORUS PRO AC, BIOS F17 03/22/2024
>
> Trigger condition:
>
> When connected to a degraded USB port (intermittent electrical
> contact), the DualSense enters a rapid disconnect/reconnect cycle.
> snd_usb_audio times out (ETIMEDOUT, -110) querying mixer controls
> during probe, each timeout triggers a USB reset, and the device
> reconnects every 1–2 seconds. This is not a driver bug, it's the
> expected kernel behavior when USB communication is unreliable, but
> it creates a race window for the use-after-free described below.
>
> The bug:
>
> In sound/usb/mixer_quirks.c, snd_dualsense_ih_match() is invoked as
> an input handler match callback from input_register_device() during
> ps_probe() (hid_playstation). At line 575, it obtains a pointer to
> the USB device struct:
>
> snd_dev = mei->info.head.mixer->chip->dev;
>
> It then uses this pointer in dev_warn() calls at lines 579 and 585:
>
> dev_warn(&snd_dev->dev, "Failed to get input dev path\n");
> dev_warn(&snd_dev->dev, "Failed to get USB dev path\n");
>
> No reference is taken on snd_dev. If the USB device is concurrently
> disconnected and freed while snd_dualsense_ih_match() is executing,
> snd_dev becomes a dangling pointer. The dev_warn() call dereferences
> snd_dev->dev.kobj.name via dev_vprintk_emit() → strnlen(), faulting
> on the freed memory.
>
> The OOPS offset (snd_dualsense_ih_match.cold+0xf/0x2b) corresponds
> to the first dev_warn() at line 579. The compiler placed both error
> branches in a cold section, and the offset is consistent with the
> earlier branch.
>
> OOPS:
>
> Oops: general protection fault, probably for non-canonical address
> 0x441f0ffa1e0ff3: 0000 [#1] SMP NOPTI
> CPU: 14 UID: 0 PID: 108 Comm: kworker/14:0 Not tainted 7.0.5-arch1-1
> #1 PREEMPT(full)
> Hardware name: Gigabyte Technology Co., Ltd. B550 AORUS PRO AC/B550
> AORUS PRO AC, BIOS F17 03/22/2024
> Workqueue: usb_hub_wq hub_event
> RIP: 0010:strnlen+0x29/0x40
> RSP: 0018:ffffce3f40537378 EFLAGS: 00010202
> RAX: 00441f0ffa1e0ff3 RBX: 00441f0ffa1e0ff3 RCX: 0000000000000000
> RDX: 00441f0ffa1e1003 RSI: 0000000000000010 RDI: 00441f0ffa1e0ff3
> RBP: ffffce3f40537408 R08: 0000000000000000 R09: ffffce3f40537478
> R10: 0000000000000004 R11: ffffffffc20ebe12 R12: ffff8943511c80b0
> R13: ffff8943511c80b0 R14: ffff894345daf028 R15: ffffce3f40537418
> FS: 0000000000000000(0000) GS:ffff8962144e7000(0000)
> knlGS:0000000000000000
> CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
> CR2: 000055e7f9dc3c18 CR3: 0000000135ffe000 CR4: 0000000000f50ef0
>
> Stack trace:
>
> Call Trace:
> <TASK>
> dev_vprintk_emit+0x70/0x1b0
> dev_printk_emit+0x61/0x7b
> __dev_printk+0x2d/0x70
> _dev_warn+0x7f/0x99
> snd_dualsense_ih_match.cold+0xf/0x2b [snd_usb_audio
> a3e71fdbdb8c7ccfdfdb57dadc854b1d1e18445c]
> input_attach_handler.isra.0+0x4b/0xa0
> input_register_device.cold+0xf8/0x1f0
> ps_probe+0xf79/0x10dc [hid_playstation
> 0cc5feea5231aa914fe418c8a06b14588cd5f064]
> hid_device_probe+0x1b8/0x270
> hid_add_device+0xcd/0x130
> usbhid_probe+0x49b/0x6c0
> usb_probe_interface+0xf8/0x2f0
> usb_set_configuration+0x738/0x920
> usb_generic_driver_probe+0x4a/0x70
> usb_probe_device+0x44/0x170
> usb_new_device.cold+0x154/0x3fd
> hub_event+0x129d/0x1ad0
> process_one_work+0x19c/0x3a0
> worker_thread+0x1b1/0x310
> kthread+0xe1/0x120
> ret_from_fork+0x2bc/0x350
> ret_from_fork_asm+0x1a/0x30
> </TASK>
> ---[ end trace 0000000000000000 ]---
>
> Analysis:
>
> The race is between two concurrent paths:
>
> 1. Probe: hub_event → usb_new_device → ... → ps_probe →
> input_register_device → input_attach_handler →
> snd_dualsense_ih_match (accesses USB device struct at line 575)
>
> 2. Disconnect: hub_event → usb_disconnect → ... → USB device
> struct freed
>
> snd_dualsense_ih_match() retrieves snd_dev via the mixer→chip→dev
> chain at line 575 without taking a reference. A concurrent disconnect
> can free the USB device struct before the subsequent dev_warn() call
> dereferences it. The faulting RDI value (0x441f0ffa1e0ff3) is
> non-canonical and consistent with SLUB freed-object poisoning,
> confirming a use-after-free.
>
> Impact:
>
> After the OOPS, the USB subsystem is left in an unrecoverable state:
> all USB ports stop enumerating devices (including ports not involved
> in the reconnect cycle), and a clean shutdown hangs in USB driver
> teardown. A hard reset is required. The trigger condition (rapid USB
> reconnection due to a degraded port) is a normal hardware failure
> mode.
>
> Workaround:
>
> # /etc/modprobe.d/dualsense.conf
> options snd_usb_audio ignore_ctl_error=1
>
> This suppresses the mixer control timeouts that drive the reconnect
> cycle, preventing the race window from opening. DualSense headphone
> jack audio controls may not function correctly with this option.
>
> Steps to reproduce:
>
> Disconnect/reconnect loop (the trigger condition):
> 1. Load hid_playstation and snd_usb_audio (default on standard
> desktop kernels)
> 2. Connect a DualSense (054c:0ce6) via USB to a port with
> unreliable electrical contact
> 3. Observe rapid disconnect/reconnect in dmesg -w, with device
> number incrementing each cycle
>
> The OOPS:
> The OOPS was observed after the loop ran for several minutes. Exact
> timing-dependent reproduction beyond triggering the loop is not
> confirmed; this was observed once and not retested to avoid the
> unrecoverable USB hang.
>
> Kernel .config (7.0.5-arch1-1):
> https://gist.github.com/seanbrar/d97a577efc3f48098d300105c4de398b
> dmesg with OOPS (7.0.5-arch1-1):
> https://gist.github.com/seanbrar/994ed882ca6f25b93e4763a8906d0fd5
> dmesg with reconnect loop (7.0.3-arch1-2):
> https://gist.github.com/seanbrar/188a75bf69fffe0d089f7aa82c6aebb6
Thanks for the report.
As this is involved with two individual devices, I wonder the bug is
triggered at which timing -- has the sound interface been already
disconnected (or being disconnected) while the input is being probed?
In such a case, does the change below help?
Takashi
-- 8< --
--- a/sound/usb/mixer_quirks.c
+++ b/sound/usb/mixer_quirks.c
@@ -705,14 +705,15 @@ static int snd_dualsense_resume_jack(struct usb_mixer_elem_list *list)
return 0;
}
-static void snd_dualsense_mixer_elem_free(struct snd_kcontrol *kctl)
+static void snd_dualsense_mixer_free(struct usb_mixer_interface *mixer)
{
- struct dualsense_mixer_elem_info *mei = snd_kcontrol_chip(kctl);
+ struct dualsense_mixer_elem_info *mei = mixer->private_data;
- if (mei->ih.event)
+ if (mei && mei->ih.event) {
input_unregister_handler(&mei->ih);
-
- snd_usb_mixer_elem_free(kctl);
+ mei->ih.event = NULL;
+ }
+ mixer->private_data = NULL;
}
static int snd_dualsense_jack_create(struct usb_mixer_interface *mixer,
@@ -744,7 +745,7 @@ static int snd_dualsense_jack_create(struct usb_mixer_interface *mixer,
}
strscpy(kctl->id.name, name, sizeof(kctl->id.name));
- kctl->private_free = snd_dualsense_mixer_elem_free;
+ kctl->private_free = snd_usb_mixer_elem_free;
err = snd_usb_mixer_add_control(&mei->info.head, kctl);
if (err)
@@ -774,6 +775,9 @@ static int snd_dualsense_jack_create(struct usb_mixer_interface *mixer,
dev_warn(&mixer->chip->dev->dev,
"Could not register input handler: %d\n", err);
mei->ih.event = NULL;
+ } else {
+ mixer->private_free = snd_dualsense_mixer_free;
+ mixer->private_data = mei;
}
return 0;
^ permalink raw reply [flat|nested] 2+ messages in thread