Re: [PATCH v13 1/3] HID: nintendo: Add preliminary Switch 2 controller driver

[email protected] Wed, 12 Aug 2026 01:34:48 +0000
Newsgroups org.kernel.vger.linux-input
Message-ID <[email protected]>
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [High] Missing `usb_unanchor_urb()` on `usb_submit_urb()` failure in `switch2_usb_probe()` causes an infinite loop during cleanup.
- [High] Player LED class devices are registered using devm helpers attached to an unmanaged `input_dev`, causing a Use-After-Free on device unplug.
- [High] Calling `ida_free()` with an unallocated ID (`U32_MAX`) on error and unbind paths triggers a `BUG_ON()` kernel panic.
- [High] The controller context (`ns2`) is freed before stopping incoming HID reports during device removal.
- [High] Submitting the bulk-in URB before successfully attaching the controller configuration causes a TOCTOU race and Use-After-Free if `switch2_controller_attach_cfg()` fails.
--

--- Patch [1]: [PATCH v13 1/3] HID: nintendo: Add preliminary Switch 2 controller driver ---
commit d121d655a283cdffdd144741982d049db4fd8c05
Author: Vicki Pfau <[email protected]>
Subject: HID: nintendo: Add preliminary Switch 2 controller driver

This adds a new driver for the Switch 2 controllers. The Switch 2 uses an
unusual split-interface design such that input and rumble occur on the main
HID interface, but all other communication occurs over a configuration
interface. This initial work supports general input for the Switch 2 Joy-Con,
Pro Controller, and GameCube NSO controllers.

> diff --git a/drivers/hid/hid-nintendo.c b/drivers/hid/hid-nintendo.c
> index e7302ec01ff1f..78c9ad3c16104 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)
> +{

[ ... ]

> +	ns2->leds = devm_kcalloc(&input->dev, JC_NUM_LEDS, sizeof(*ns2->leds), GFP_KERNEL);
> +	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;
> +		}
> +	}

[Severity: High]
Is it safe to tie the LED devm allocations to the unmanaged input device here?

If userspace holds an open file descriptor for the input device when it is
unregistered during unplug, the devres cleanup is deferred. Could a subsequent
sysfs write to the LED brightness trigger a use-after-free when
switch2_player_led_brightness_set() dereferences the already freed controller
context?

[ ... ]

> +static int switch2_probe(struct hid_device *hdev, const struct hid_device_id *id)
> +{

[ ... ]

> +	ns2->player_id = U32_MAX;
> +	ret = ida_alloc(&nintendo_player_id_allocator, GFP_KERNEL);
> +	if (ret < 0)
> +		hid_warn(hdev, "Failed to allocate player ID, skipping; ret=%d\n", ret);
> +	else
> +		ns2->player_id = ret;

[ ... ]

> +err_cleanup:
> +	ida_free(&nintendo_player_id_allocator, ns2->player_id);
> +	ns2->hdev = NULL;
> +	mutex_unlock(&ns2->lock);
> +	switch2_controller_put(ns2);
> +	kref_put(&ns2->refcount, switch2_kref_put);
> +
> +	return ret;
> +}
> +
> +static void switch2_remove(struct hid_device *hdev)
> +{
> +	struct switch2_controller *ns2 = hid_get_drvdata(hdev);
> +
> +	switch2_controller_put(ns2);
> +	mutex_lock(&ns2->lock);
> +	ns2->hdev = NULL;
> +	ida_free(&nintendo_player_id_allocator, ns2->player_id);

[Severity: High]
If ida_alloc() fails or is skipped, ns2->player_id remains U32_MAX. Since
ida_free() expects a valid ID and asserts on negative values, could
unconditionally freeing U32_MAX cause a kernel panic here?

> +	mutex_unlock(&ns2->lock);
> +	kref_put(&ns2->refcount, switch2_kref_put);
> +	hid_hw_close(hdev);
> +	hid_hw_stop(hdev);

[Severity: High]
If the hardware continues sending events during device removal, could a report
arrive after kref_put() frees the controller context but before hid_hw_stop()
completes?

This appears to leave a window for a use-after-free in switch2_event() when
dereferencing the controller context.

> +}

[ ... ]

> diff --git a/drivers/input/joystick/nintendo-switch2-usb.c b/drivers/input/joystick/nintendo-switch2-usb.c
> new file mode 100644
> index 0000000000000..36a5ffbd6a756
> --- /dev/null
> +++ b/drivers/input/joystick/nintendo-switch2-usb.c

[ ... ]

> +static void switch2_usb_message_in_work(struct work_struct *work)
> +{

[ ... ]

> +	spin_lock_irqsave(&ns2_usb->bulk_in_lock, flags);
> +	for (i = 0; i < NS2_IN_URBS; i++) {
> +		urb = &ns2_usb->bulk_in[i];
> +		if (urb->state != NS2_URB_IN)
> +			continue;
> +		spin_unlock_irqrestore(&ns2_usb->bulk_in_lock, flags);
> +
> +		if (ns2_usb->cfg.parent) {
> +			err = switch2_receive_command(ns2_usb->cfg.parent,
> +				urb->urb->transfer_buffer, urb->urb->actual_length);

[Severity: High]
Is it possible for the endpoint to respond immediately after the bulk-in URB
is submitted in switch2_usb_probe(), scheduling message_in_work?

If switch2_controller_attach_cfg() subsequently fails, it will clean up and
free the controller context. Could this cause a use-after-free if the worker
thread evaluates ns2_usb->cfg.parent as non-NULL just before the failure frees
it, passing a dangling pointer to switch2_receive_command()?

[ ... ]

> +static int switch2_usb_probe(struct usb_interface *intf, const struct usb_device_id *id)
> +{

[ ... ]

> +	spin_lock_irqsave(&ns2_usb->bulk_in_lock, flags);
> +	ns2_usb->bulk_in[0].state = NS2_URB_OUT;
> +	usb_anchor_urb(ns2_usb->bulk_in[0].urb, &ns2_usb->bulk_in_anchor);
> +	ret = usb_submit_urb(ns2_usb->bulk_in[0].urb, GFP_ATOMIC);
> +	spin_unlock_irqrestore(&ns2_usb->bulk_in_lock, flags);
> +
> +	if (ret < 0)
> +		goto err_free_out;

[Severity: High]
When usb_submit_urb() fails, does the URB stay on the anchor list?

Since usb_kill_anchored_urbs() does not manually unanchor unsubmitted URBs,
could this cause an infinite loop when the error path eventually calls
usb_kill_anchored_urbs(&ns2_usb->bulk_in_anchor)?

> +
> +	ret = switch2_controller_attach_cfg(phys, &ns2_usb->cfg);
> +	if (ret < 0)
> +		goto err_free_out;

-- 
Sashiko AI review ยท https://sashiko.dev/#/patchset/[email protected]?part=1