Re: [PATCH v5 07/11] HID: steam: Rearrange teardown sequence
[email protected] Thu, 30 Jul 2026 04:39:32 +0000
| Newsgroups | org.kernel.vger.linux-input |
|---|---|
| Message-ID | <[email protected]> |
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider: Pre-existing issues: - [High] Use-after-free due to `work_connect` rescheduling after cancellation in `steam_remove`. - [High] Race condition leading to use-after-free in `steam_probe` error path. - [High] Lockless TOCTOU race in `steam_raw_event` leads to Use-After-Free of `steam->client_hdev`. -- --- Patch [7]: [PATCH v5 07/11] HID: steam: Rearrange teardown sequence --- commit 0f9077495b7412b91d8e827f54f32dc26780b709 Author: Vicki Pfau <[email protected]> HID: steam: Rearrange teardown sequence This patch reorders the teardown sequence for the Steam controller driver to address a narrow window where callbacks could be scheduled with a dangling pointer. It also moves the cancellation of rumble and mode switch work to steam_unregister(). > diff --git a/drivers/hid/hid-steam.c b/drivers/hid/hid-steam.c > index 12203d61922f7..663fda8a86fdb 100644 > --- a/drivers/hid/hid-steam.c > +++ b/drivers/hid/hid-steam.c [ ... ] > @@ -1441,25 +1444,24 @@ static int steam_probe(struct hid_device *hdev, > static void steam_remove(struct hid_device *hdev) > { > struct steam_device *steam = hid_get_drvdata(hdev); > + unsigned long flags; > > if (!steam || hdev->group == HID_GROUP_STEAM) { > hid_hw_stop(hdev); > return; > } > > + hid_hw_close(hdev); > hid_destroy_device(steam->client_hdev); > - cancel_delayed_work_sync(&steam->mode_switch); > - cancel_work_sync(&steam->work_connect); > - cancel_work_sync(&steam->rumble_work); > - cancel_delayed_work_sync(&steam->coalesce_rumble_work); > - steam->client_hdev = NULL; > + spin_lock_irqsave(&steam->lock, flags); > steam->client_opened = 0; [Severity: High] This is a pre-existing issue, but does this sequence leave a window for a use-after-free on steam->client_hdev? In steam_remove(), hid_destroy_device(steam->client_hdev) is called to free the virtual client device while the hardware is still active, since hid_hw_stop() has not been called yet. Meanwhile, steam_raw_event() locklessly checks if the client is opened before dereferencing steam->client_hdev: drivers/hid/hid-steam.c:steam_raw_event() { ... if (steam->client_opened) hid_input_report(steam->client_hdev, HID_FEATURE_REPORT, data, size, 0); ... } If an interrupt fires and passes the client_opened check, could it get preempted, and then resume to call hid_input_report() on the freed client_hdev pointer after steam_remove() completes the destruction? > + spin_unlock_irqrestore(&steam->lock, flags); > + cancel_work_sync(&steam->work_connect); > if (steam->quirks & STEAM_QUIRK_WIRELESS) { > hid_info(hdev, "Steam wireless receiver disconnected"); > } > - hid_hw_close(hdev); > - hid_hw_stop(hdev); > steam_unregister(steam); > + hid_hw_stop(hdev); > } [Severity: High] This is a pre-existing issue, but can steam->work_connect still be rescheduled after it is cancelled here? In steam_remove(), cancel_work_sync(&steam->work_connect) is called, but hid_hw_stop(hdev) isn't called until the very end. If a wireless connect or disconnect event arrives in between, steam_do_connect_event() unconditionally reschedules the work: drivers/hid/hid-steam.c:steam_do_connect_event() { ... if (changed && schedule_work(&steam->work_connect) == 0) ... } When steam_remove() finishes, the steam object is freed by devres, leaving a dangling work item in the system workqueue. Will this lead to a use-after-free when the scheduled work executes? [Severity: High] This is another pre-existing issue, but does the error path in steam_probe() have a similar race condition with work_connect? In the steam_probe() error path, steam_unregister() is called before cancel_work_sync(&steam->work_connect): drivers/hid/hid-steam.c:steam_probe() { ... err_steam_unregister: if (steam->connected) steam_unregister(steam); err_hw_close: hid_hw_close(hdev); err_hw_stop: hid_hw_stop(hdev); err_cancel_work: cancel_work_sync(&steam->work_connect); ... } steam_unregister() clears the device state by setting steam->serial_no[0] = 0. If steam_work_connect_cb() is currently queued and runs concurrently, it will see connected == true and call steam_register(). Because serial_no was cleared, steam_register() re-registers all input devices and adds the steam object back to the global steam_devices list. After the work finishes, cancel_work_sync() silently returns, steam_probe() returns an error, and the steam object is freed by devres. Does this leave a freed object in the global list and registered to input subsystems, leading to a use-after-free? -- Sashiko AI review ยท https://sashiko.dev/#/patchset/[email protected]?part=7