Linux Input/HID development
 help / color / mirror / Atom feed
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;

      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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox