HID: bigben: use spinlock to safely schedule workers
authorPietro Borrello <borrello@diag.uniroma1.it>
Sun, 12 Feb 2023 19:00:01 +0000 (19:00 +0000)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Fri, 10 Mar 2023 08:33:23 +0000 (09:33 +0100)
[ Upstream commit 76ca8da989c7d97a7f76c75d475fe95a584439d7 ]

Use spinlocks to deal with workers introducing a wrapper
bigben_schedule_work(), and several spinlock checks.
Otherwise, bigben_set_led() may schedule bigben->worker after the
structure has been freed, causing a use-after-free.

Fixes: 4eb1b01de5b9 ("HID: hid-bigbenff: fix race condition for scheduled work during removal")
Signed-off-by: Pietro Borrello <borrello@diag.uniroma1.it>
Link: https://lore.kernel.org/r/20230125-hid-unregister-leds-v4-3-7860c5763c38@diag.uniroma1.it
Signed-off-by: Benjamin Tissoires <benjamin.tissoires@redhat.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>
drivers/hid/hid-bigbenff.c

index b98c5f3..9d6560d 100644 (file)
@@ -185,6 +185,15 @@ struct bigben_device {
        struct work_struct worker;
 };
 
+static inline void bigben_schedule_work(struct bigben_device *bigben)
+{
+       unsigned long flags;
+
+       spin_lock_irqsave(&bigben->lock, flags);
+       if (!bigben->removed)
+               schedule_work(&bigben->worker);
+       spin_unlock_irqrestore(&bigben->lock, flags);
+}
 
 static void bigben_worker(struct work_struct *work)
 {
@@ -197,9 +206,6 @@ static void bigben_worker(struct work_struct *work)
        u32 len;
        unsigned long flags;
 
-       if (bigben->removed)
-               return;
-
        buf = hid_alloc_report_buf(bigben->report, GFP_KERNEL);
        if (!buf)
                return;
@@ -285,7 +291,7 @@ static int hid_bigben_play_effect(struct input_dev *dev, void *data,
                bigben->work_ff = true;
                spin_unlock_irqrestore(&bigben->lock, flags);
 
-               schedule_work(&bigben->worker);
+               bigben_schedule_work(bigben);
        }
 
        return 0;
@@ -320,7 +326,7 @@ static void bigben_set_led(struct led_classdev *led,
 
                        if (work) {
                                bigben->work_led = true;
-                               schedule_work(&bigben->worker);
+                               bigben_schedule_work(bigben);
                        }
                        return;
                }
@@ -450,7 +456,7 @@ static int bigben_probe(struct hid_device *hid,
        bigben->left_motor_force = 0;
        bigben->work_led = true;
        bigben->work_ff = true;
-       schedule_work(&bigben->worker);
+       bigben_schedule_work(bigben);
 
        hid_info(hid, "LED and force feedback support for BigBen gamepad\n");