From: Takashi Iwai <tiwai@suse.de>
To: Sean Brar <hello@seanbrar.com>
Cc: linux-input@vger.kernel.org, linux-sound@vger.kernel.org,
roderick.colenbrander@sony.com, jikos@kernel.org,
bentiss@kernel.org, perex@perex.cz, tiwai@suse.com
Subject: Re: [BUG] ALSA: usb-audio: use-after-free in snd_dualsense_ih_match during rapid USB reconnect
Date: Tue, 19 May 2026 15:44:26 +0200 [thread overview]
Message-ID: <871pf7ms3p.wl-tiwai@suse.de> (raw)
In-Reply-To: <909876aa-7498-4ca4-b3a6-6618fbc6209d@seanbrar.com>
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;
prev parent reply other threads:[~2026-05-19 13:44 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
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 [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=871pf7ms3p.wl-tiwai@suse.de \
--to=tiwai@suse.de \
--cc=bentiss@kernel.org \
--cc=hello@seanbrar.com \
--cc=jikos@kernel.org \
--cc=linux-input@vger.kernel.org \
--cc=linux-sound@vger.kernel.org \
--cc=perex@perex.cz \
--cc=roderick.colenbrander@sony.com \
--cc=tiwai@suse.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.