Thread (7 messages) 7 messages, 2 authors, 15h ago

Re: [PATCH v14 1/4] HID: nintendo: Add preliminary Switch 2 controller driver

From: sashiko-bot@kernel.org
Date: 2026-09-29 05:11:11

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 [off-list ref]
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.
quoted hunk ↗ jump to hunk
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.
+}
[ ... ]
quoted hunk ↗ jump to hunk
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
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help