Re: [PATCH 4/5] HID: steam: Clean up locking

[email protected] Wed, 05 Aug 2026 02:16:01 +0000
Newsgroups org.kernel.vger.linux-input
Message-ID <[email protected]>
Thank you for your contribution! Sashiko AI review found 6 potential issue(=
s) to consider:

New issues:
- [High] Self-deadlock in `steam_unregister` due to `report_mutex` acquisit=
ion.
- [High] AB-BA lock inversion between `steam_devices_lock` and `steam->repo=
rt_mutex`.
- [Medium] Inverted lizard mode logic in `steam_param_set_lizard_mode` brea=
ks the parameter.
- [Medium] Missing `report_mutex` in `steam_get_conn_status` creates a race=
 condition with concurrent hardware access.

Pre-existing issues:
- [High] Use-after-free caused by cancelling workqueues before stopping the=
 hardware.
- [High] Resource leak and use-after-free of `power_supply` object on probe=
 failure.
--

--- Patch [4]: [PATCH 4/5] HID: steam: Clean up locking ---
commit 4a13ea9d8650f6b804ba3ebe1f98c65bb697f0c4
Author: Vicki Pfau <[email protected]>

HID: steam: Clean up locking

This cleans up several issues with locking behavior, including RCU accesses
not being guarded behind a lock.

> diff --git a/drivers/hid/hid-steam.c b/drivers/hid/hid-steam.c
> index 2d60b79441b69..87af4bee9f5ff 100644
> --- a/drivers/hid/hid-steam.c
> +++ b/drivers/hid/hid-steam.c

[ ... ]

> @@ -728,7 +727,6 @@ static int steam_get_conn_status(struct steam_device =
*steam)
>  	else
>  		report_id =3D 0;
> =20
> -	guard(mutex)(&steam->report_mutex);
>  	ret =3D steam_send_report_id(steam, cmd, sizeof(cmd), report_id);

[Severity: Medium]
Will this removal create a race condition with concurrent hardware access?
During steam_probe() in hid-steam.c, the hardware is opened and interrupts
are enabled before steam_get_conn_status() is called. If the device
immediately sends a connection event, the interrupt handler schedules
steam_work_connect_cb(), which acquires report_mutex and sends feature
reports. Without the mutex here, can the control transfers from
steam_get_conn_status() interleave with the worker's transfers and
confuse the controller firmware?

[ ... ]

> @@ -1728,7 +1728,9 @@ static int steam_probe(struct hid_device *hdev,
>  			steam->connected =3D true;
>  	}
>  	if (steam->connected) {
> +		mutex_lock(&steam->report_mutex);
>  		ret =3D steam_register(steam);
> +		mutex_unlock(&steam->report_mutex);
>  		if (ret) {
>  			hid_err(hdev,
>  				"%s:steam_register failed with error %d\n",
>  				__func__, ret);
>  			goto err_hw_close;

[Severity: High]
This isn't a bug introduced by this patch, but does steam_probe() leak
the power_supply object if steam_register() fails? If steam_input_register()
or steam_sensors_register() fails inside steam_register(), it jumps to an
error label and returns an error without unregistering the battery created
via steam_battery_register(). The error propagates here, skipping
steam_unregister() and jumping to err_hw_close. Doesn't this leave the
power_supply device registered while devm frees the steam_device, leading to
a use-after-free when userspace interacts with the stale power_supply?

[ ... ]

> @@ -1787,19 +1789,17 @@ static void steam_remove(struct hid_device *hdev)
>  	if (steam->quirks & STEAM_QUIRK_WIRELESS) {
>  		hid_info(hdev, "Steam wireless receiver disconnected");
>  	}
> +	guard(mutex)(&steam->report_mutex);
>  	steam_unregister(steam);
>  	hid_hw_stop(hdev);
>  }

[Severity: High]
Can holding report_mutex across steam_unregister() cause a self-deadlock?
When a device is removed, steam_remove() acquires report_mutex here and cal=
ls
steam_unregister(). steam_unregister() calls cancel_work_sync() on
rumble_work, and steam_input_unregister(). If the rumble work is running in
steam_haptic_rumble_cb(), it will attempt to acquire the same report_mutex
and block forever. Additionally, unregistering the input device triggers
the steam_input_close() callback, which also attempts to acquire this
non-recursive report_mutex.

[Severity: High]
This is a pre-existing issue, but is there a use-after-free window here
in steam_remove() due to the order of teardown? cancel_work_sync() for
work_connect is called just before the quoted code, but hid_hw_stop() is
called at the end. If an interrupt arrives between the cancellation and
hid_hw_stop(), steam_raw_event() can process it and re-queue the work.
When steam_remove() returns, devm frees the steam_device, but the queued
work could later execute steam_work_connect_cb() on the freed memory.

[ ... ]

> @@ -2631,6 +2624,8 @@ static int steam_param_set_lizard_mode(const char *=
val,
>  {
>  	struct steam_device *steam;
>  	int ret;
> +	bool client_opened;
> +	unsigned long flags;
> =20
>  	ret =3D param_set_bool(val, kp);
>  	if (ret)
> @@ -2638,8 +2633,13 @@ static int steam_param_set_lizard_mode(const char =
*val,
> =20
>  	mutex_lock(&steam_devices_lock);
>  	list_for_each_entry(steam, &steam_devices, list) {
> -		if (!steam->client_opened)
> +		spin_lock_irqsave(&steam->lock, flags);
> +		client_opened =3D steam->client_opened;
> +		spin_unlock_irqrestore(&steam->lock, flags);
> +		if (client_opened) {
> +			guard(mutex)(&steam->report_mutex);
>  			steam_set_lizard_mode(steam, lizard_mode);
> +		}
>  	}
>  	mutex_unlock(&steam_devices_lock);
>  	return 0;

[Severity: High]
Does acquiring report_mutex while holding steam_devices_lock introduce an
lock inversion? Here in steam_param_set_lizard_mode(),
steam_devices_lock is acquired first, followed by report_mutex. However,
concurrently in steam_probe() or steam_work_connect_cb(), report_mutex is
acquired first, and then steam_register() is called, which attempts to
acquire steam_devices_lock to add the device to the list. Could this lead
to an indefinite deadlock?

[Severity: Medium]
Was the condition intentionally changed from !steam->client_opened to
if (client_opened)? It seems like this applies the lizard mode settings
to devices actively controlled by the Steam client, and ignores devices that
actually need the fallback mappings.

--=20
Sashiko AI review =C2=B7 https://sashiko.dev/#/patchset/20260805015116.3884=
[email protected]?part=3D4