[PATCH 1/2] HID: winwing: fix use-after-free of the rumble work
From: René Onier
Date: Wed Sep 30 2026 - 18:54:35 EST
winwing_remove() cancels the rumble work before it tears the device
down:
if (data)
cancel_work_sync(&data->rumble_work);
hid_hw_close(hdev);
hid_hw_stop(hdev);
Nothing keeps the work from being queued again after that cancel.
hid_hw_stop() unregisters the input device, and evdev_cleanup() flushes
it while it is still open: input_flush_device() -> input_ff_flush() ->
erase_effect() -> ff->playback(dev, id, 0). On a memoryless device that
is ml_ff_playback(), which marks a playing effect as aborting and calls
ml_play_effects(), handing the driver the combined - now zeroed -
effect. That is winwing_play_effect(), and it ends with
return schedule_work(&data->rumble_work);
Unbinding, rmmod'ing or unplugging the device while an effect is playing
therefore queues the work again, after cancel_work_sync() has run.
The driver data is devm-allocated, and hid_device_remove() releases the
driver's devres group as soon as winwing_remove() returns. The pending
work then runs on freed memory: both the work_struct itself and the
report buffer winwing_haptic_rumble() writes into live in that
allocation.
Cancel the work once the device has been stopped instead. After
hid_hw_stop() has returned the input device is gone and no further
effect can be played, so a single cancel_work_sync() is enough. The
driver data is still valid at that point, since devres only runs after
winwing_remove() has returned, so a work item that is still running
while the transport goes down touches valid memory only; its
hid_hw_output_report() call simply fails once the low-level driver has
torn its endpoints down.
Fixes: 42d020b54edc ("HID: winwing: Enable rumble effects")
Signed-off-by: René Onier <f3nr1l@xxxxxx>
---
drivers/hid/hid-winwing.c | 11 ++++++++---
1 file changed, 8 insertions(+), 3 deletions(-)
diff --git a/drivers/hid/hid-winwing.c b/drivers/hid/hid-winwing.c
index 19b92c2c65..e8030fdc14 100644
--- a/drivers/hid/hid-winwing.c
+++ b/drivers/hid/hid-winwing.c
@@ -391,11 +391,16 @@ static void winwing_remove(struct hid_device *hdev)
data = (struct winwing_drv_data *) hid_get_drvdata(hdev);
- if (data)
- cancel_work_sync(&data->rumble_work);
-
hid_hw_close(hdev);
hid_hw_stop(hdev);
+
+ /*
+ * Only cancel the work once the input device is gone: stopping the
+ * device flushes the force feedback effects, which plays them one
+ * last time and queues the work again.
+ */
+ if (data)
+ cancel_work_sync(&data->rumble_work);
}
static int winwing_input_configured(struct hid_device *hdev,
--
2.55.0