From: sashiko-bot@kernel.org
To: "Vicki Pfau" <vi@endrift.com>
Cc: linux-input@vger.kernel.org, dmitry.torokhov@gmail.com
Subject: Re: [PATCH v14 1/4] HID: nintendo: Add preliminary Switch 2 controller driver
Date: Tue, 29 Sep 2026 05:11:10 +0000 [thread overview]
Message-ID: <20260929051111.681361F000FF@smtp.kernel.org> (raw)
In-Reply-To: <20260929045607.2106149-2-vi@endrift.com>
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] input: memory leak and UAF due to devm actions on unmanaged input_dev
- [High] usb: TOCTOU race condition causing UAF in message_in_work
- [High] usb: infinite interrupt storm on -EPIPE URB completion
--
commit 276f7b7da744b926faf29e3e4027c0cc47d9bc19
Author: Vicki Pfau <vi@endrift.com>
Subject: HID: nintendo: Add preliminary Switch 2 controller driver
This commit introduces preliminary support for the Nintendo Switch 2
controllers. It uses a split-driver design with a main HID interface driver
and a secondary configuration interface driver to handle input and non-standard
communications over USB and Bluetooth.
> diff --git a/drivers/hid/hid-nintendo-switch2-usb.c b/drivers/hid/hid-nintendo-switch2-usb.c
> new file mode 100644
> index 0000000000000..fabe6b3f4163c
> --- /dev/null
> +++ b/drivers/hid/hid-nintendo-switch2-usb.c
[ ... ]
> +static void switch2_bulk_in(struct urb *urb)
> +{
> + struct switch2_usb *ns2_usb = urb->context;
> + int i;
> + bool schedule = false;
> + unsigned long flags;
> +
> + switch (urb->status) {
> + case 0:
> + schedule = true;
> + break;
> + case -ECONNRESET:
> + case -ENOENT:
> + case -ESHUTDOWN:
> + dev_dbg(&ns2_usb->udev->dev, "shutting down input urb: %d\n", urb->status);
> + return;
> + case -EPIPE:
> + break;
[Severity: High]
Does this error path handle halted endpoints correctly? The EPIPE status
indicates a stalled endpoint. By breaking out here without setting the
schedule flag, the code avoids scheduling work, but it proceeds to the
loop below where it immediately re-anchors and resubmits the URB via
usb_submit_urb() without clearing the halt condition. This can cause the
hardware to instantly complete the new URB with EPIPE again, leading to an
infinite interrupt storm in softirq context and a CPU lockup.
> + default:
> + dev_dbg(&ns2_usb->udev->dev, "unknown input urb status: %d\n", urb->status);
> + break;
> + }
> +
> + spin_lock_irqsave(&ns2_usb->bulk_in_lock, flags);
> + if (ns2_usb->shutdown)
> + schedule = false;
> +
> + for (i = 0; i < NS2_IN_URBS; i++) {
> + int err;
> + struct switch2_urb *ns2_urb;
> +
> + if (ns2_usb->bulk_in[i].urb == urb) {
> + if (schedule) {
> + ns2_usb->bulk_in[i].state = NS2_URB_IN;
> + continue;
> + } else {
> + ns2_usb->bulk_in[i].state = NS2_URB_FREE;
> + }
> + }
> +
> + if (ns2_usb->bulk_in[i].state != NS2_URB_FREE)
> + continue;
> +
> + /*
> + * We want exactly one bulk in URB scheduled at a time, so only
> + * reschedule this immediately if nothing else is scheduled
> + * currently.
> + */
> + if (!usb_anchor_empty(&ns2_usb->bulk_in_anchor) || ns2_usb->shutdown)
> + continue;
> +
> + ns2_urb = &ns2_usb->bulk_in[i];
> + if (!ns2_urb)
> + continue;
> +
> + usb_anchor_urb(ns2_urb->urb, &ns2_usb->bulk_in_anchor);
> + err = usb_submit_urb(ns2_urb->urb, GFP_ATOMIC);
> + if (err) {
> + usb_unanchor_urb(ns2_urb->urb);
> + dev_dbg(&ns2_usb->udev->dev, "failed to queue input urb: %d\n", err);
> + } else {
> + ns2_urb->state = NS2_URB_OUT;
> + }
> + }
> + spin_unlock_irqrestore(&ns2_usb->bulk_in_lock, flags);
> +
> + if (schedule)
> + schedule_work(&ns2_usb->message_in_work);
[Severity: High]
Can calling schedule_work() outside the spinlock create a TOCTOU race
against switch2_usb_disconnect()?
If switch2_bulk_in() checks ns2_usb->shutdown, releases the lock, and is
then preempted before calling schedule_work(), switch2_usb_disconnect()
could run. It would acquire the lock, set shutdown to true, release the lock,
and call cancel_work_sync(&ns2_usb->message_in_work). Since the work is not
yet queued, cancel_work_sync() returns immediately, and devres frees
ns2_usb. When switch2_bulk_in() resumes, it will call schedule_work() on
freed memory, causing a use-after-free.
> +}
[ ... ]
> diff --git a/drivers/hid/hid-nintendo.c b/drivers/hid/hid-nintendo.c
> index b1fcf3e5767ba..3a7c35375be60 100644
> --- a/drivers/hid/hid-nintendo.c
> +++ b/drivers/hid/hid-nintendo.c
[ ... ]
> +static int switch2_player_led_brightness_set(struct led_classdev *led,
> + enum led_brightness brightness)
> +{
> + struct device *dev = led->dev->parent;
> + struct input_dev *input = to_input_dev(dev);
> + struct switch2_controller *ns2 = input_get_drvdata(input);
> +
> + if (!ns2)
> + return -ENODEV;
> +
> + guard(mutex)(&ns2->lock);
> + return switch2_set_leds(ns2);
> +}
[ ... ]
> +static int switch2_init_input(struct switch2_controller *ns2)
> +{
> + struct input_dev *input;
[ ... ]
> + input = input_allocate_device();
> + if (!input)
> + return -ENOMEM;
[ ... ]
> + ns2->leds = devm_kcalloc(&input->dev, JC_NUM_LEDS, sizeof(*ns2->leds), GFP_KERNEL);
[Severity: High]
Does allocating devm resources tied to an unmanaged input device create a
memory leak and use-after-free risk?
The input device here is allocated manually with input_allocate_device().
Using devm_kcalloc() and later devm_led_classdev_register() with
&input->dev as the parent means the LED devices take a reference to their
parent input device. When the device is removed, input_unregister_device()
is called, but the child LED devices hold the reference so the input device
is never freed.
This leaves the devres actions unexecuted, leaking the input device and
leaving the LED devices active in sysfs. Meanwhile, the driver frees the
ns2 structure. If userspace then writes to the LED brightness file,
switch2_player_led_brightness_set() will execute, fetch the freed ns2
pointer, and dereference it, leading to a deterministic use-after-free.
> + if (!ns2->leds) {
> + hid_err(ns2->hdev, "Failed to allocate LEDs\n");
> + input_unregister_device(input);
> + return -ENOMEM;
> + }
> +
> + for (i = 0; i < JC_NUM_LEDS; i++) {
> + struct led_classdev *led = &ns2->leds[i];
[ ... ]
> + ret = devm_led_classdev_register(&input->dev, led);
> + if (ret < 0) {
> + dev_err(&input->dev, "Failed to register player %d LED; ret=%d\n",
> + i + 1, ret);
> + break;
> + }
> + }
> +
> + rcu_assign_pointer(ns2->input, input);
> + synchronize_rcu();
> +
> + switch2_init_step_done(ns2, NS2_INIT_INPUT);
> + return switch2_init_controller(ns2);
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260929045607.2106149-1-vi@endrift.com?part=1
next prev parent reply other threads:[~2026-09-29 5:11 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-29 4:56 [PATCH v14 0/4] HID: nintendo: Add preliminary Switch 2 controller driver Vicki Pfau
2026-09-29 4:56 ` [PATCH v14 1/4] " Vicki Pfau
2026-09-29 5:11 ` sashiko-bot [this message]
2026-09-29 4:56 ` [PATCH v14 2/4] HID: nintendo: Add rumble support for Switch 2 controllers Vicki Pfau
2026-09-29 5:15 ` sashiko-bot
2026-09-29 4:56 ` [PATCH v14 3/4] HID: nintendo: Add unified report format support Vicki Pfau
2026-09-29 4:56 ` [PATCH v14 4/4] HID: nintendo: Patch NYXI Hyperion 3 descriptor Vicki Pfau
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=20260929051111.681361F000FF@smtp.kernel.org \
--to=sashiko-bot@kernel.org \
--cc=dmitry.torokhov@gmail.com \
--cc=linux-input@vger.kernel.org \
--cc=sashiko-reviews@lists.linux.dev \
--cc=vi@endrift.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