From 9fefb6201c4f8dd9f58c581b2a66e5cde2895ea2 Mon Sep 17 00:00:00 2001 From: Pietro Borrello Date: Sun, 12 Feb 2023 18:59:59 +0000 Subject: HID: bigben: use spinlock to protect concurrent accesses bigben driver has a worker that may access data concurrently. Proct the accesses using a spinlock. Fixes: 256a90ed9e46 ("HID: hid-bigbenff: driver for BigBen Interactive PS3OFMINIPAD gamepad") Signed-off-by: Pietro Borrello Link: https://lore.kernel.org/r/20230125-hid-unregister-leds-v4-1-7860c5763c38@diag.uniroma1.it Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-bigbenff.c | 52 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 2 deletions(-) (limited to 'drivers/hid') diff --git a/drivers/hid/hid-bigbenff.c b/drivers/hid/hid-bigbenff.c index e8b16665860d..ed3d2d7bc1dd 100644 --- a/drivers/hid/hid-bigbenff.c +++ b/drivers/hid/hid-bigbenff.c @@ -174,6 +174,7 @@ static __u8 pid0902_rdesc_fixed[] = { struct bigben_device { struct hid_device *hid; struct hid_report *report; + spinlock_t lock; bool removed; u8 led_state; /* LED1 = 1 .. LED4 = 8 */ u8 right_motor_on; /* right motor off/on 0/1 */ @@ -190,12 +191,27 @@ static void bigben_worker(struct work_struct *work) struct bigben_device *bigben = container_of(work, struct bigben_device, worker); struct hid_field *report_field = bigben->report->field[0]; + bool do_work_led = false; + bool do_work_ff = false; + u8 *buf; + u32 len; + unsigned long flags; if (bigben->removed || !report_field) return; + buf = hid_alloc_report_buf(bigben->report, GFP_KERNEL); + if (!buf) + return; + + len = hid_report_len(bigben->report); + + /* LED work */ + spin_lock_irqsave(&bigben->lock, flags); + if (bigben->work_led) { bigben->work_led = false; + do_work_led = true; report_field->value[0] = 0x01; /* 1 = led message */ report_field->value[1] = 0x08; /* reserved value, always 8 */ report_field->value[2] = bigben->led_state; @@ -204,11 +220,22 @@ static void bigben_worker(struct work_struct *work) report_field->value[5] = 0x00; /* padding */ report_field->value[6] = 0x00; /* padding */ report_field->value[7] = 0x00; /* padding */ - hid_hw_request(bigben->hid, bigben->report, HID_REQ_SET_REPORT); + hid_output_report(bigben->report, buf); + } + + spin_unlock_irqrestore(&bigben->lock, flags); + + if (do_work_led) { + hid_hw_raw_request(bigben->hid, bigben->report->id, buf, len, + bigben->report->type, HID_REQ_SET_REPORT); } + /* FF work */ + spin_lock_irqsave(&bigben->lock, flags); + if (bigben->work_ff) { bigben->work_ff = false; + do_work_ff = true; report_field->value[0] = 0x02; /* 2 = rumble effect message */ report_field->value[1] = 0x08; /* reserved value, always 8 */ report_field->value[2] = bigben->right_motor_on; @@ -217,8 +244,17 @@ static void bigben_worker(struct work_struct *work) report_field->value[5] = 0x00; /* padding */ report_field->value[6] = 0x00; /* padding */ report_field->value[7] = 0x00; /* padding */ - hid_hw_request(bigben->hid, bigben->report, HID_REQ_SET_REPORT); + hid_output_report(bigben->report, buf); + } + + spin_unlock_irqrestore(&bigben->lock, flags); + + if (do_work_ff) { + hid_hw_raw_request(bigben->hid, bigben->report->id, buf, len, + bigben->report->type, HID_REQ_SET_REPORT); } + + kfree(buf); } static int hid_bigben_play_effect(struct input_dev *dev, void *data, @@ -228,6 +264,7 @@ static int hid_bigben_play_effect(struct input_dev *dev, void *data, struct bigben_device *bigben = hid_get_drvdata(hid); u8 right_motor_on; u8 left_motor_force; + unsigned long flags; if (!bigben) { hid_err(hid, "no device data\n"); @@ -242,9 +279,12 @@ static int hid_bigben_play_effect(struct input_dev *dev, void *data, if (right_motor_on != bigben->right_motor_on || left_motor_force != bigben->left_motor_force) { + spin_lock_irqsave(&bigben->lock, flags); bigben->right_motor_on = right_motor_on; bigben->left_motor_force = left_motor_force; bigben->work_ff = true; + spin_unlock_irqrestore(&bigben->lock, flags); + schedule_work(&bigben->worker); } @@ -259,6 +299,7 @@ static void bigben_set_led(struct led_classdev *led, struct bigben_device *bigben = hid_get_drvdata(hid); int n; bool work; + unsigned long flags; if (!bigben) { hid_err(hid, "no device data\n"); @@ -267,6 +308,7 @@ static void bigben_set_led(struct led_classdev *led, for (n = 0; n < NUM_LEDS; n++) { if (led == bigben->leds[n]) { + spin_lock_irqsave(&bigben->lock, flags); if (value == LED_OFF) { work = (bigben->led_state & BIT(n)); bigben->led_state &= ~BIT(n); @@ -274,6 +316,7 @@ static void bigben_set_led(struct led_classdev *led, work = !(bigben->led_state & BIT(n)); bigben->led_state |= BIT(n); } + spin_unlock_irqrestore(&bigben->lock, flags); if (work) { bigben->work_led = true; @@ -307,8 +350,12 @@ static enum led_brightness bigben_get_led(struct led_classdev *led) static void bigben_remove(struct hid_device *hid) { struct bigben_device *bigben = hid_get_drvdata(hid); + unsigned long flags; + spin_lock_irqsave(&bigben->lock, flags); bigben->removed = true; + spin_unlock_irqrestore(&bigben->lock, flags); + cancel_work_sync(&bigben->worker); hid_hw_stop(hid); } @@ -362,6 +409,7 @@ static int bigben_probe(struct hid_device *hid, set_bit(FF_RUMBLE, hidinput->input->ffbit); INIT_WORK(&bigben->worker, bigben_worker); + spin_lock_init(&bigben->lock); error = input_ff_create_memless(hidinput->input, NULL, hid_bigben_play_effect); -- cgit v1.2.3-58-ga151 From 27d2a2fd844ec7da70d19fabb482304fd1e0595b Mon Sep 17 00:00:00 2001 From: Pietro Borrello Date: Sun, 12 Feb 2023 19:00:00 +0000 Subject: HID: bigben_worker() remove unneeded check on report_field bigben_worker() checks report_field to be non-NULL. The check has been added in commit 918aa1ef104d ("HID: bigbenff: prevent null pointer dereference") to prevent a NULL pointer crash. However, the true root cause was a missing check for output reports, patched in commit c7bf714f8755 ("HID: check empty report_list in bigben_probe()"), where the type-confused report list_entry was overlapping with a NULL pointer, which was then causing the crash. Fixes: 918aa1ef104d ("HID: bigbenff: prevent null pointer dereference") Signed-off-by: Pietro Borrello Link: https://lore.kernel.org/r/20230125-hid-unregister-leds-v4-2-7860c5763c38@diag.uniroma1.it Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-bigbenff.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/hid') diff --git a/drivers/hid/hid-bigbenff.c b/drivers/hid/hid-bigbenff.c index ed3d2d7bc1dd..b98c5f31c184 100644 --- a/drivers/hid/hid-bigbenff.c +++ b/drivers/hid/hid-bigbenff.c @@ -197,7 +197,7 @@ static void bigben_worker(struct work_struct *work) u32 len; unsigned long flags; - if (bigben->removed || !report_field) + if (bigben->removed) return; buf = hid_alloc_report_buf(bigben->report, GFP_KERNEL); -- cgit v1.2.3-58-ga151 From 76ca8da989c7d97a7f76c75d475fe95a584439d7 Mon Sep 17 00:00:00 2001 From: Pietro Borrello Date: Sun, 12 Feb 2023 19:00:01 +0000 Subject: HID: bigben: use spinlock to safely schedule workers 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 Link: https://lore.kernel.org/r/20230125-hid-unregister-leds-v4-3-7860c5763c38@diag.uniroma1.it Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-bigbenff.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) (limited to 'drivers/hid') diff --git a/drivers/hid/hid-bigbenff.c b/drivers/hid/hid-bigbenff.c index b98c5f31c184..9d6560db762b 100644 --- a/drivers/hid/hid-bigbenff.c +++ b/drivers/hid/hid-bigbenff.c @@ -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"); -- cgit v1.2.3-58-ga151 From b94335f899542a0da5fafc38af8edcaf90195843 Mon Sep 17 00:00:00 2001 From: Pietro Borrello Date: Sun, 12 Feb 2023 00:01:44 +0000 Subject: hid: bigben_probe(): validate report count bigben_probe() does not validate that the output report has the needed report values in the first field. A malicious device registering a report with one field and a single value causes an head OOB write in bigben_worker() when accessing report_field->value[1] to report_field->value[7]. Use hid_validate_values() which takes care of all the needed checks. Fixes: 256a90ed9e46 ("HID: hid-bigbenff: driver for BigBen Interactive PS3OFMINIPAD gamepad") Signed-off-by: Pietro Borrello Link: https://lore.kernel.org/r/20230211-bigben-oob-v1-1-d2849688594c@diag.uniroma1.it Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-bigbenff.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) (limited to 'drivers/hid') diff --git a/drivers/hid/hid-bigbenff.c b/drivers/hid/hid-bigbenff.c index 9d6560db762b..a02cb517b4c4 100644 --- a/drivers/hid/hid-bigbenff.c +++ b/drivers/hid/hid-bigbenff.c @@ -371,7 +371,6 @@ static int bigben_probe(struct hid_device *hid, { struct bigben_device *bigben; struct hid_input *hidinput; - struct list_head *report_list; struct led_classdev *led; char *name; size_t name_sz; @@ -396,14 +395,12 @@ static int bigben_probe(struct hid_device *hid, return error; } - report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; - if (list_empty(report_list)) { + bigben->report = hid_validate_values(hid, HID_OUTPUT_REPORT, 0, 0, 8); + if (!bigben->report) { hid_err(hid, "no output report found\n"); error = -ENODEV; goto error_hw_stop; } - bigben->report = list_entry(report_list->next, - struct hid_report, list); if (list_empty(&hid->inputs)) { hid_err(hid, "no inputs found\n"); -- cgit v1.2.3-58-ga151