linux-input.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v4 0/5] HID: use spinlocks to safely schedule led workers
@ 2023-02-12 18:59 Pietro Borrello
  2023-02-12 18:59 ` [PATCH v4 1/5] HID: bigben: use spinlock to protect concurrent accesses Pietro Borrello
                   ` (6 more replies)
  0 siblings, 7 replies; 8+ messages in thread
From: Pietro Borrello @ 2023-02-12 18:59 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, Hanno Zulla, Hanno Zulla,
	Greg Kroah-Hartman
  Cc: Cristiano Giuffrida, Bos, H.J., Jakob Koschel, Jiri Kosina,
	Roderick Colenbrander, linux-input, linux-kernel, Pietro Borrello

I noticed a recurring pattern is present in multiple hid devices in the
Linux tree, where the LED controller of a device schedules a work_struct
to interact with the hardware.
The work_struct is embedded in the device structure and thus, is freed
at device removal.

The issue is that a LED worker may be scheduled by a timer concurrently
with device removal, causing the work_struct to be accessed after having
been freed.
I was able to trigger the issue in hid-bigbenff.c and hid-asus.c 
where the work_structs may be scheduled by the LED controller
while the device is disconnecting, triggering use-after-frees.
I can attach the reproducer, but it's very simple USB configuration, 
using the /dev/raw-gadget interface with some more USB interactions 
to manage LEDs configuration and pass checks in asus_kbd_init() 
and asus_kbd_get_functions() in case of hid-asus.c.
I triggered the issue by connecting a device and immediately 
disconnecting it, so that the remove function runs before the LED one
which remains pending.

I am attaching multiple patches for asus and bigben drivers. 
The proposed patches introduce safe wrappers to schedule the workers
safely with several spinlocks checks.

I attach the (partial for brevity) ODEBUG dumps:

```hid-bigbenff.c
[   37.803135][ T1170] usb 1-1: USB disconnect, device number 2
[   37.827979][ T1170] ODEBUG: free active (active state 0) object
type: work_struct hint: bigben_worker+0x0/0x860
[   37.829634][ T1170] WARNING: CPU: 0 PID: 1170 at
lib/debugobjects.c:505 debug_check_no_obj_freed+0x43a/0x630
[   37.830904][ T1170] Modules linked in:
[   37.831413][ T1170] CPU: 0 PID: 1170 Comm: kworker/0:3 Not tainted
6.1.0-rc4-dirty #43
[   37.832465][ T1170] Hardware name: QEMU Standard PC (i440FX + PIIX,
1996), BIOS 1.13.0-1ubuntu1.1 04/01/2014
[   37.833751][ T1170] Workqueue: usb_hub_wq hub_event
[   37.834409][ T1170] RIP: 0010:debug_check_no_obj_freed+0x43a/0x630
[   37.835218][ T1170] Code: 48 89 ef e8 28 82 58 ff 49 8b 14 24 4c 8b
45 00 48 c7 c7 40 5f 09 87 48 c7 c6 60 5b 09 87 89 d9 4d 89 f9 31 c0
e8 46 25 ef fe <0f> 0b 4c 8b 64 24 20 48 ba 00 00 00 00 00 fc ff df ff
05 4f 7c 17
[   37.837667][ T1170] RSP: 0018:ffffc900006fee60 EFLAGS: 00010246
[   37.838503][ T1170] RAX: 0d2d19ffcded3d00 RBX: 0000000000000000
RCX: ffff888117fc9b00
[   37.839519][ T1170] RDX: 0000000000000000 RSI: 0000000000000000
RDI: 0000000000000000
[   37.840570][ T1170] RBP: ffffffff86e88380 R08: ffffffff8130793b
R09: fffff520000dfd85
[   37.841618][ T1170] R10: fffff520000dfd85 R11: 0000000000000000
R12: ffffffff87095fb8
[   37.842649][ T1170] R13: ffff888117770ad8 R14: ffff888117770acc
R15: ffffffff852b7420
[   37.843728][ T1170] FS:  0000000000000000(0000)
GS:ffff8881f6600000(0000) knlGS:0000000000000000
[   37.844877][ T1170] CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[   37.845749][ T1170] CR2: 00007f992eaab380 CR3: 000000011834b000
CR4: 00000000001006f0
[   37.846794][ T1170] Call Trace:
[   37.847245][ T1170]  <TASK>
[   37.847643][ T1170]  slab_free_freelist_hook+0x89/0x160
[   37.848409][ T1170]  ? devres_release_all+0x262/0x350
[   37.849156][ T1170]  __kmem_cache_free+0x71/0x110
[   37.849829][ T1170]  devres_release_all+0x262/0x350
[   37.850478][ T1170]  ? devres_release+0x90/0x90
[   37.851118][ T1170]  device_release_driver_internal+0x5e5/0x8a0
[   37.851944][ T1170]  bus_remove_device+0x2ea/0x400
[   37.852611][ T1170]  device_del+0x64f/0xb40
[   37.853212][ T1170]  ? kill_device+0x150/0x150
[   37.853831][ T1170]  ? print_irqtrace_events+0x1f0/0x1f0
[   37.854564][ T1170]  hid_destroy_device+0x66/0x100
[   37.855226][ T1170]  usbhid_disconnect+0x9a/0xc0
[   37.855887][ T1170]  usb_unbind_interface+0x1e1/0x890
```

``` hid-asus.c
[   77.409878][ T1169] usb 1-1: USB disconnect, device number 2
[   77.423606][ T1169] ODEBUG: free active (active state 0) object
type: work_struct hint: asus_kbd_backlight_work+0x0/0x2c0
[   77.425222][ T1169] WARNING: CPU: 0 PID: 1169 at
lib/debugobjects.c:505 debug_check_no_obj_freed+0x43a/0x630
[   77.426599][ T1169] Modules linked in:
[   77.427322][ T1169] CPU: 0 PID: 1169 Comm: kworker/0:3 Not tainted
6.1.0-rc4-dirty #43
[   77.428404][ T1169] Hardware name: QEMU Standard PC (i440FX + PIIX,
1996), BIOS 1.13.0-1ubuntu1.1 04/01/2014
[   77.429644][ T1169] Workqueue: usb_hub_wq hub_event
[   77.430296][ T1169] RIP: 0010:debug_check_no_obj_freed+0x43a/0x630
[   77.431142][ T1169] Code: 48 89 ef e8 28 82 58 ff 49 8b 14 24 4c 8b
45 00 48 c7 c7 40 5f 09 87 48 c7 c6 60 5b 09 87 89 d9 4d 89 f9 31 c0
e8 46 25 ef fe <0f> 0b 4c 8b 64 24 20 48 ba 00 00 00 00 00 fc ff df ff
05 4f 7c 17
[   77.433691][ T1169] RSP: 0018:ffffc9000069ee60 EFLAGS: 00010246
[   77.434470][ T1169] RAX: b85d2b40c12d7600 RBX: 0000000000000000
RCX: ffff888117a78000
[   77.435507][ T1169] RDX: 0000000000000000 RSI: 0000000080000000
RDI: 0000000000000000
[   77.436521][ T1169] RBP: ffffffff86e88380 R08: ffffffff8130793b
R09: ffffed103ecc4ed6
[   77.437582][ T1169] R10: ffffed103ecc4ed6 R11: 0000000000000000
R12: ffffffff87095fb8
[   77.438593][ T1169] R13: ffff88810e348fe0 R14: ffff88810e348fd4
R15: ffffffff852b5780
[   77.439667][ T1169] FS:  0000000000000000(0000)
GS:ffff8881f6600000(0000) knlGS:0000000000000000
[   77.440842][ T1169] CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[   77.441688][ T1169] CR2: 00007ffc05495ff0 CR3: 000000010cdf0000
CR4: 00000000001006f0
[   77.442720][ T1169] Call Trace:
[   77.443167][ T1169]  <TASK>
[   77.443555][ T1169]  slab_free_freelist_hook+0x89/0x160
[   77.444302][ T1169]  ? devres_release_all+0x262/0x350
[   77.444990][ T1169]  __kmem_cache_free+0x71/0x110
[   77.445638][ T1169]  devres_release_all+0x262/0x350
[   77.446309][ T1169]  ? devres_release+0x90/0x90
[   77.446978][ T1169]  device_release_driver_internal+0x5e5/0x8a0
[   77.447748][ T1169]  bus_remove_device+0x2ea/0x400
[   77.448421][ T1169]  device_del+0x64f/0xb40
[   77.448976][ T1169]  ? kill_device+0x150/0x150
[   77.449577][ T1169]  ? print_irqtrace_events+0x1f0/0x1f0
[   77.450307][ T1169]  hid_destroy_device+0x66/0x100
[   77.450938][ T1169]  usbhid_disconnect+0x9a/0xc0
```

Signed-off-by: Pietro Borrello <borrello@diag.uniroma1.it>
---
Changes in v4:
- Split patches that add spinlock wrt the patches that check scheduling
- Smaller locked regions
- Link to v3: https://lore.kernel.org/r/20230125-hid-unregister-leds-v3-0-0a52ac225e00@diag.uniroma1.it

Changes in v3:
- use spinlocks to prevent workers scheduling
- drop patches on sony & playstation hid drivers
- Link to v2: https://lore.kernel.org/r/20230125-hid-unregister-leds-v2-0-689cc62fc878@diag.uniroma1.it

Changes in v2:
- dualshock4: Clarify UAF
- dualsense:  Clarify UAF
- dualsense:  Unregister multicolor led controller
- Link to v1: https://lore.kernel.org/r/20230125-hid-unregister-leds-v1-0-9a5192dcef16@diag.uniroma1.it

---
Pietro Borrello (5):
      HID: bigben: use spinlock to protect concurrent accesses
      HID: bigben_worker() remove unneeded check on report_field
      HID: bigben: use spinlock to safely schedule workers
      HID: asus: use spinlock to protect concurrent accesses
      HID: asus: use spinlock to safely schedule workers

 drivers/hid/hid-asus.c     | 37 +++++++++++++++++++++----
 drivers/hid/hid-bigbenff.c | 68 +++++++++++++++++++++++++++++++++++++++++-----
 2 files changed, 93 insertions(+), 12 deletions(-)
---
base-commit: 2241ab53cbb5cdb08a6b2d4688feb13971058f65
change-id: 20230125-hid-unregister-leds-4cbf67099e1d

Best regards,
-- 
Pietro Borrello <borrello@diag.uniroma1.it>


^ permalink raw reply	[flat|nested] 8+ messages in thread

* [PATCH v4 1/5] HID: bigben: use spinlock to protect concurrent accesses
  2023-02-12 18:59 [PATCH v4 0/5] HID: use spinlocks to safely schedule led workers Pietro Borrello
@ 2023-02-12 18:59 ` Pietro Borrello
  2023-02-12 19:00 ` [PATCH v4 2/5] HID: bigben_worker() remove unneeded check on report_field Pietro Borrello
                   ` (5 subsequent siblings)
  6 siblings, 0 replies; 8+ messages in thread
From: Pietro Borrello @ 2023-02-12 18:59 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, Hanno Zulla, Hanno Zulla,
	Greg Kroah-Hartman
  Cc: Cristiano Giuffrida, Bos, H.J., Jakob Koschel, Jiri Kosina,
	Roderick Colenbrander, linux-input, linux-kernel, Pietro Borrello

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 <borrello@diag.uniroma1.it>
---
 drivers/hid/hid-bigbenff.c | 52 ++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 50 insertions(+), 2 deletions(-)

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);

-- 
2.25.1


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH v4 2/5] HID: bigben_worker() remove unneeded check on report_field
  2023-02-12 18:59 [PATCH v4 0/5] HID: use spinlocks to safely schedule led workers Pietro Borrello
  2023-02-12 18:59 ` [PATCH v4 1/5] HID: bigben: use spinlock to protect concurrent accesses Pietro Borrello
@ 2023-02-12 19:00 ` Pietro Borrello
  2023-02-12 19:00 ` [PATCH v4 3/5] HID: bigben: use spinlock to safely schedule workers Pietro Borrello
                   ` (4 subsequent siblings)
  6 siblings, 0 replies; 8+ messages in thread
From: Pietro Borrello @ 2023-02-12 19:00 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, Hanno Zulla, Hanno Zulla,
	Greg Kroah-Hartman
  Cc: Cristiano Giuffrida, Bos, H.J., Jakob Koschel, Jiri Kosina,
	Roderick Colenbrander, linux-input, linux-kernel, Pietro Borrello

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 <borrello@diag.uniroma1.it>
---
 drivers/hid/hid-bigbenff.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

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);

-- 
2.25.1


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH v4 3/5] HID: bigben: use spinlock to safely schedule workers
  2023-02-12 18:59 [PATCH v4 0/5] HID: use spinlocks to safely schedule led workers Pietro Borrello
  2023-02-12 18:59 ` [PATCH v4 1/5] HID: bigben: use spinlock to protect concurrent accesses Pietro Borrello
  2023-02-12 19:00 ` [PATCH v4 2/5] HID: bigben_worker() remove unneeded check on report_field Pietro Borrello
@ 2023-02-12 19:00 ` Pietro Borrello
  2023-02-12 19:00 ` [PATCH v4 4/5] HID: asus: use spinlock to protect concurrent accesses Pietro Borrello
                   ` (3 subsequent siblings)
  6 siblings, 0 replies; 8+ messages in thread
From: Pietro Borrello @ 2023-02-12 19:00 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, Hanno Zulla, Hanno Zulla,
	Greg Kroah-Hartman
  Cc: Cristiano Giuffrida, Bos, H.J., Jakob Koschel, Jiri Kosina,
	Roderick Colenbrander, linux-input, linux-kernel, Pietro Borrello

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>
---
 drivers/hid/hid-bigbenff.c | 18 ++++++++++++------
 1 file changed, 12 insertions(+), 6 deletions(-)

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");
 

-- 
2.25.1


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH v4 4/5] HID: asus: use spinlock to protect concurrent accesses
  2023-02-12 18:59 [PATCH v4 0/5] HID: use spinlocks to safely schedule led workers Pietro Borrello
                   ` (2 preceding siblings ...)
  2023-02-12 19:00 ` [PATCH v4 3/5] HID: bigben: use spinlock to safely schedule workers Pietro Borrello
@ 2023-02-12 19:00 ` Pietro Borrello
  2023-02-12 19:00 ` [PATCH v4 5/5] HID: asus: use spinlock to safely schedule workers Pietro Borrello
                   ` (2 subsequent siblings)
  6 siblings, 0 replies; 8+ messages in thread
From: Pietro Borrello @ 2023-02-12 19:00 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, Hanno Zulla, Hanno Zulla,
	Greg Kroah-Hartman
  Cc: Cristiano Giuffrida, Bos, H.J., Jakob Koschel, Jiri Kosina,
	Roderick Colenbrander, linux-input, linux-kernel, Pietro Borrello

asus driver has a worker that may access data concurrently.
Proct the accesses using a spinlock.

Fixes: af22a610bc38 ("HID: asus: support backlight on USB keyboards")
Signed-off-by: Pietro Borrello <borrello@diag.uniroma1.it>
---
 drivers/hid/hid-asus.c | 22 +++++++++++++++++++++-
 1 file changed, 21 insertions(+), 1 deletion(-)

diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
index f99752b998f3..9f767baf39fb 100644
--- a/drivers/hid/hid-asus.c
+++ b/drivers/hid/hid-asus.c
@@ -98,6 +98,7 @@ struct asus_kbd_leds {
 	struct hid_device *hdev;
 	struct work_struct work;
 	unsigned int brightness;
+	spinlock_t lock;
 	bool removed;
 };
 
@@ -495,7 +496,12 @@ static void asus_kbd_backlight_set(struct led_classdev *led_cdev,
 {
 	struct asus_kbd_leds *led = container_of(led_cdev, struct asus_kbd_leds,
 						 cdev);
+	unsigned long flags;
+
+	spin_lock_irqsave(&led->lock, flags);
 	led->brightness = brightness;
+	spin_unlock_irqrestore(&led->lock, flags);
+
 	schedule_work(&led->work);
 }
 
@@ -503,8 +509,14 @@ static enum led_brightness asus_kbd_backlight_get(struct led_classdev *led_cdev)
 {
 	struct asus_kbd_leds *led = container_of(led_cdev, struct asus_kbd_leds,
 						 cdev);
+	enum led_brightness brightness;
+	unsigned long flags;
 
-	return led->brightness;
+	spin_lock_irqsave(&led->lock, flags);
+	brightness = led->brightness;
+	spin_unlock_irqrestore(&led->lock, flags);
+
+	return brightness;
 }
 
 static void asus_kbd_backlight_work(struct work_struct *work)
@@ -512,11 +524,14 @@ static void asus_kbd_backlight_work(struct work_struct *work)
 	struct asus_kbd_leds *led = container_of(work, struct asus_kbd_leds, work);
 	u8 buf[] = { FEATURE_KBD_REPORT_ID, 0xba, 0xc5, 0xc4, 0x00 };
 	int ret;
+	unsigned long flags;
 
 	if (led->removed)
 		return;
 
+	spin_lock_irqsave(&led->lock, flags);
 	buf[4] = led->brightness;
+	spin_unlock_irqrestore(&led->lock, flags);
 
 	ret = asus_kbd_set_report(led->hdev, buf, sizeof(buf));
 	if (ret < 0)
@@ -584,6 +599,7 @@ static int asus_kbd_register_leds(struct hid_device *hdev)
 	drvdata->kbd_backlight->cdev.brightness_set = asus_kbd_backlight_set;
 	drvdata->kbd_backlight->cdev.brightness_get = asus_kbd_backlight_get;
 	INIT_WORK(&drvdata->kbd_backlight->work, asus_kbd_backlight_work);
+	spin_lock_init(&drvdata->kbd_backlight->lock);
 
 	ret = devm_led_classdev_register(&hdev->dev, &drvdata->kbd_backlight->cdev);
 	if (ret < 0) {
@@ -1119,9 +1135,13 @@ static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id)
 static void asus_remove(struct hid_device *hdev)
 {
 	struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
+	unsigned long flags;
 
 	if (drvdata->kbd_backlight) {
+		spin_lock_irqsave(&drvdata->kbd_backlight->lock, flags);
 		drvdata->kbd_backlight->removed = true;
+		spin_unlock_irqrestore(&drvdata->kbd_backlight->lock, flags);
+
 		cancel_work_sync(&drvdata->kbd_backlight->work);
 	}
 

-- 
2.25.1


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH v4 5/5] HID: asus: use spinlock to safely schedule workers
  2023-02-12 18:59 [PATCH v4 0/5] HID: use spinlocks to safely schedule led workers Pietro Borrello
                   ` (3 preceding siblings ...)
  2023-02-12 19:00 ` [PATCH v4 4/5] HID: asus: use spinlock to protect concurrent accesses Pietro Borrello
@ 2023-02-12 19:00 ` Pietro Borrello
  2023-02-15 18:18 ` (subset) [PATCH v4 0/5] HID: use spinlocks to safely schedule led workers Benjamin Tissoires
  2023-02-15 18:20 ` Benjamin Tissoires
  6 siblings, 0 replies; 8+ messages in thread
From: Pietro Borrello @ 2023-02-12 19:00 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, Hanno Zulla, Hanno Zulla,
	Greg Kroah-Hartman
  Cc: Cristiano Giuffrida, Bos, H.J., Jakob Koschel, Jiri Kosina,
	Roderick Colenbrander, linux-input, linux-kernel, Pietro Borrello

Use spinlocks to deal with workers introducing a wrapper
asus_schedule_work(), and several spinlock checks.
Otherwise, asus_kbd_backlight_set() may schedule led->work after the
structure has been freed, causing a use-after-free.

Fixes: af22a610bc38 ("HID: asus: support backlight on USB keyboards")
Signed-off-by: Pietro Borrello <borrello@diag.uniroma1.it>
---
 drivers/hid/hid-asus.c | 15 +++++++++++----
 1 file changed, 11 insertions(+), 4 deletions(-)

diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
index 9f767baf39fb..d1094bb1aa42 100644
--- a/drivers/hid/hid-asus.c
+++ b/drivers/hid/hid-asus.c
@@ -491,6 +491,16 @@ static int rog_nkey_led_init(struct hid_device *hdev)
 	return ret;
 }
 
+static void asus_schedule_work(struct asus_kbd_leds *led)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&led->lock, flags);
+	if (!led->removed)
+		schedule_work(&led->work);
+	spin_unlock_irqrestore(&led->lock, flags);
+}
+
 static void asus_kbd_backlight_set(struct led_classdev *led_cdev,
 				   enum led_brightness brightness)
 {
@@ -502,7 +512,7 @@ static void asus_kbd_backlight_set(struct led_classdev *led_cdev,
 	led->brightness = brightness;
 	spin_unlock_irqrestore(&led->lock, flags);
 
-	schedule_work(&led->work);
+	asus_schedule_work(led);
 }
 
 static enum led_brightness asus_kbd_backlight_get(struct led_classdev *led_cdev)
@@ -526,9 +536,6 @@ static void asus_kbd_backlight_work(struct work_struct *work)
 	int ret;
 	unsigned long flags;
 
-	if (led->removed)
-		return;
-
 	spin_lock_irqsave(&led->lock, flags);
 	buf[4] = led->brightness;
 	spin_unlock_irqrestore(&led->lock, flags);

-- 
2.25.1


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* Re: (subset) [PATCH v4 0/5] HID: use spinlocks to safely schedule led workers
  2023-02-12 18:59 [PATCH v4 0/5] HID: use spinlocks to safely schedule led workers Pietro Borrello
                   ` (4 preceding siblings ...)
  2023-02-12 19:00 ` [PATCH v4 5/5] HID: asus: use spinlock to safely schedule workers Pietro Borrello
@ 2023-02-15 18:18 ` Benjamin Tissoires
  2023-02-15 18:20 ` Benjamin Tissoires
  6 siblings, 0 replies; 8+ messages in thread
From: Benjamin Tissoires @ 2023-02-15 18:18 UTC (permalink / raw)
  To: Jiri Kosina, Hanno Zulla, Hanno Zulla, Greg Kroah-Hartman,
	Pietro Borrello
  Cc: Cristiano Giuffrida, Bos, H.J., Jakob Koschel, Jiri Kosina,
	Roderick Colenbrander, linux-input, linux-kernel

On Sun, 12 Feb 2023 18:59:58 +0000, Pietro Borrello wrote:
> I noticed a recurring pattern is present in multiple hid devices in the
> Linux tree, where the LED controller of a device schedules a work_struct
> to interact with the hardware.
> The work_struct is embedded in the device structure and thus, is freed
> at device removal.
> 
> The issue is that a LED worker may be scheduled by a timer concurrently
> with device removal, causing the work_struct to be accessed after having
> been freed.
> I was able to trigger the issue in hid-bigbenff.c and hid-asus.c
> where the work_structs may be scheduled by the LED controller
> while the device is disconnecting, triggering use-after-frees.
> I can attach the reproducer, but it's very simple USB configuration,
> using the /dev/raw-gadget interface with some more USB interactions
> to manage LEDs configuration and pass checks in asus_kbd_init()
> and asus_kbd_get_functions() in case of hid-asus.c.
> I triggered the issue by connecting a device and immediately
> disconnecting it, so that the remove function runs before the LED one
> which remains pending.
> 
> [...]

Applied to https://git.kernel.org/pub/scm/linux/kernel/git/hid/hid.git (for-6.3/asus), thanks!

[4/5] HID: asus: use spinlock to protect concurrent accesses
      https://git.kernel.org/hid/hid/c/315c537068a1
[5/5] HID: asus: use spinlock to safely schedule workers
      https://git.kernel.org/hid/hid/c/4ab3a086d10e

Cheers,
-- 
Benjamin Tissoires <benjamin.tissoires@redhat.com>


^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH v4 0/5] HID: use spinlocks to safely schedule led workers
  2023-02-12 18:59 [PATCH v4 0/5] HID: use spinlocks to safely schedule led workers Pietro Borrello
                   ` (5 preceding siblings ...)
  2023-02-15 18:18 ` (subset) [PATCH v4 0/5] HID: use spinlocks to safely schedule led workers Benjamin Tissoires
@ 2023-02-15 18:20 ` Benjamin Tissoires
  6 siblings, 0 replies; 8+ messages in thread
From: Benjamin Tissoires @ 2023-02-15 18:20 UTC (permalink / raw)
  To: Pietro Borrello
  Cc: Jiri Kosina, Hanno Zulla, Hanno Zulla, Greg Kroah-Hartman,
	Cristiano Giuffrida, Bos, H.J., Jakob Koschel, Jiri Kosina,
	Roderick Colenbrander, linux-input, linux-kernel

On Feb 12 2023, Pietro Borrello wrote:
> I noticed a recurring pattern is present in multiple hid devices in the
> Linux tree, where the LED controller of a device schedules a work_struct
> to interact with the hardware.
> The work_struct is embedded in the device structure and thus, is freed
> at device removal.
> 
> The issue is that a LED worker may be scheduled by a timer concurrently
> with device removal, causing the work_struct to be accessed after having
> been freed.
> I was able to trigger the issue in hid-bigbenff.c and hid-asus.c 
> where the work_structs may be scheduled by the LED controller
> while the device is disconnecting, triggering use-after-frees.
> I can attach the reproducer, but it's very simple USB configuration, 
> using the /dev/raw-gadget interface with some more USB interactions 
> to manage LEDs configuration and pass checks in asus_kbd_init() 
> and asus_kbd_get_functions() in case of hid-asus.c.
> I triggered the issue by connecting a device and immediately 
> disconnecting it, so that the remove function runs before the LED one
> which remains pending.
> 
> I am attaching multiple patches for asus and bigben drivers. 
> The proposed patches introduce safe wrappers to schedule the workers
> safely with several spinlocks checks.
> 
> I attach the (partial for brevity) ODEBUG dumps:
> 
> ```hid-bigbenff.c
> [   37.803135][ T1170] usb 1-1: USB disconnect, device number 2
> [   37.827979][ T1170] ODEBUG: free active (active state 0) object
> type: work_struct hint: bigben_worker+0x0/0x860
> [   37.829634][ T1170] WARNING: CPU: 0 PID: 1170 at
> lib/debugobjects.c:505 debug_check_no_obj_freed+0x43a/0x630
> [   37.830904][ T1170] Modules linked in:
> [   37.831413][ T1170] CPU: 0 PID: 1170 Comm: kworker/0:3 Not tainted
> 6.1.0-rc4-dirty #43
> [   37.832465][ T1170] Hardware name: QEMU Standard PC (i440FX + PIIX,
> 1996), BIOS 1.13.0-1ubuntu1.1 04/01/2014
> [   37.833751][ T1170] Workqueue: usb_hub_wq hub_event
> [   37.834409][ T1170] RIP: 0010:debug_check_no_obj_freed+0x43a/0x630
> [   37.835218][ T1170] Code: 48 89 ef e8 28 82 58 ff 49 8b 14 24 4c 8b
> 45 00 48 c7 c7 40 5f 09 87 48 c7 c6 60 5b 09 87 89 d9 4d 89 f9 31 c0
> e8 46 25 ef fe <0f> 0b 4c 8b 64 24 20 48 ba 00 00 00 00 00 fc ff df ff
> 05 4f 7c 17
> [   37.837667][ T1170] RSP: 0018:ffffc900006fee60 EFLAGS: 00010246
> [   37.838503][ T1170] RAX: 0d2d19ffcded3d00 RBX: 0000000000000000
> RCX: ffff888117fc9b00
> [   37.839519][ T1170] RDX: 0000000000000000 RSI: 0000000000000000
> RDI: 0000000000000000
> [   37.840570][ T1170] RBP: ffffffff86e88380 R08: ffffffff8130793b
> R09: fffff520000dfd85
> [   37.841618][ T1170] R10: fffff520000dfd85 R11: 0000000000000000
> R12: ffffffff87095fb8
> [   37.842649][ T1170] R13: ffff888117770ad8 R14: ffff888117770acc
> R15: ffffffff852b7420
> [   37.843728][ T1170] FS:  0000000000000000(0000)
> GS:ffff8881f6600000(0000) knlGS:0000000000000000
> [   37.844877][ T1170] CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
> [   37.845749][ T1170] CR2: 00007f992eaab380 CR3: 000000011834b000
> CR4: 00000000001006f0
> [   37.846794][ T1170] Call Trace:
> [   37.847245][ T1170]  <TASK>
> [   37.847643][ T1170]  slab_free_freelist_hook+0x89/0x160
> [   37.848409][ T1170]  ? devres_release_all+0x262/0x350
> [   37.849156][ T1170]  __kmem_cache_free+0x71/0x110
> [   37.849829][ T1170]  devres_release_all+0x262/0x350
> [   37.850478][ T1170]  ? devres_release+0x90/0x90
> [   37.851118][ T1170]  device_release_driver_internal+0x5e5/0x8a0
> [   37.851944][ T1170]  bus_remove_device+0x2ea/0x400
> [   37.852611][ T1170]  device_del+0x64f/0xb40
> [   37.853212][ T1170]  ? kill_device+0x150/0x150
> [   37.853831][ T1170]  ? print_irqtrace_events+0x1f0/0x1f0
> [   37.854564][ T1170]  hid_destroy_device+0x66/0x100
> [   37.855226][ T1170]  usbhid_disconnect+0x9a/0xc0
> [   37.855887][ T1170]  usb_unbind_interface+0x1e1/0x890
> ```
> 
> ``` hid-asus.c
> [   77.409878][ T1169] usb 1-1: USB disconnect, device number 2
> [   77.423606][ T1169] ODEBUG: free active (active state 0) object
> type: work_struct hint: asus_kbd_backlight_work+0x0/0x2c0
> [   77.425222][ T1169] WARNING: CPU: 0 PID: 1169 at
> lib/debugobjects.c:505 debug_check_no_obj_freed+0x43a/0x630
> [   77.426599][ T1169] Modules linked in:
> [   77.427322][ T1169] CPU: 0 PID: 1169 Comm: kworker/0:3 Not tainted
> 6.1.0-rc4-dirty #43
> [   77.428404][ T1169] Hardware name: QEMU Standard PC (i440FX + PIIX,
> 1996), BIOS 1.13.0-1ubuntu1.1 04/01/2014
> [   77.429644][ T1169] Workqueue: usb_hub_wq hub_event
> [   77.430296][ T1169] RIP: 0010:debug_check_no_obj_freed+0x43a/0x630
> [   77.431142][ T1169] Code: 48 89 ef e8 28 82 58 ff 49 8b 14 24 4c 8b
> 45 00 48 c7 c7 40 5f 09 87 48 c7 c6 60 5b 09 87 89 d9 4d 89 f9 31 c0
> e8 46 25 ef fe <0f> 0b 4c 8b 64 24 20 48 ba 00 00 00 00 00 fc ff df ff
> 05 4f 7c 17
> [   77.433691][ T1169] RSP: 0018:ffffc9000069ee60 EFLAGS: 00010246
> [   77.434470][ T1169] RAX: b85d2b40c12d7600 RBX: 0000000000000000
> RCX: ffff888117a78000
> [   77.435507][ T1169] RDX: 0000000000000000 RSI: 0000000080000000
> RDI: 0000000000000000
> [   77.436521][ T1169] RBP: ffffffff86e88380 R08: ffffffff8130793b
> R09: ffffed103ecc4ed6
> [   77.437582][ T1169] R10: ffffed103ecc4ed6 R11: 0000000000000000
> R12: ffffffff87095fb8
> [   77.438593][ T1169] R13: ffff88810e348fe0 R14: ffff88810e348fd4
> R15: ffffffff852b5780
> [   77.439667][ T1169] FS:  0000000000000000(0000)
> GS:ffff8881f6600000(0000) knlGS:0000000000000000
> [   77.440842][ T1169] CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
> [   77.441688][ T1169] CR2: 00007ffc05495ff0 CR3: 000000010cdf0000
> CR4: 00000000001006f0
> [   77.442720][ T1169] Call Trace:
> [   77.443167][ T1169]  <TASK>
> [   77.443555][ T1169]  slab_free_freelist_hook+0x89/0x160
> [   77.444302][ T1169]  ? devres_release_all+0x262/0x350
> [   77.444990][ T1169]  __kmem_cache_free+0x71/0x110
> [   77.445638][ T1169]  devres_release_all+0x262/0x350
> [   77.446309][ T1169]  ? devres_release+0x90/0x90
> [   77.446978][ T1169]  device_release_driver_internal+0x5e5/0x8a0
> [   77.447748][ T1169]  bus_remove_device+0x2ea/0x400
> [   77.448421][ T1169]  device_del+0x64f/0xb40
> [   77.448976][ T1169]  ? kill_device+0x150/0x150
> [   77.449577][ T1169]  ? print_irqtrace_events+0x1f0/0x1f0
> [   77.450307][ T1169]  hid_destroy_device+0x66/0x100
> [   77.450938][ T1169]  usbhid_disconnect+0x9a/0xc0
> ```
> 
> Signed-off-by: Pietro Borrello <borrello@diag.uniroma1.it>
> ---
> Changes in v4:
> - Split patches that add spinlock wrt the patches that check scheduling
> - Smaller locked regions
> - Link to v3: https://lore.kernel.org/r/20230125-hid-unregister-leds-v3-0-0a52ac225e00@diag.uniroma1.it
> 
> Changes in v3:
> - use spinlocks to prevent workers scheduling
> - drop patches on sony & playstation hid drivers
> - Link to v2: https://lore.kernel.org/r/20230125-hid-unregister-leds-v2-0-689cc62fc878@diag.uniroma1.it
> 
> Changes in v2:
> - dualshock4: Clarify UAF
> - dualsense:  Clarify UAF
> - dualsense:  Unregister multicolor led controller
> - Link to v1: https://lore.kernel.org/r/20230125-hid-unregister-leds-v1-0-9a5192dcef16@diag.uniroma1.it
> 
> ---
> Pietro Borrello (5):
>       HID: bigben: use spinlock to protect concurrent accesses
>       HID: bigben_worker() remove unneeded check on report_field
>       HID: bigben: use spinlock to safely schedule workers
>       HID: asus: use spinlock to protect concurrent accesses
>       HID: asus: use spinlock to safely schedule workers
> 
>  drivers/hid/hid-asus.c     | 37 +++++++++++++++++++++----
>  drivers/hid/hid-bigbenff.c | 68 +++++++++++++++++++++++++++++++++++++++++-----
>  2 files changed, 93 insertions(+), 12 deletions(-)

And patches 1-3 applied to hid/hid.git branch for-6.3/bigben

Cheers,
Benjamin

> ---
> base-commit: 2241ab53cbb5cdb08a6b2d4688feb13971058f65
> change-id: 20230125-hid-unregister-leds-4cbf67099e1d
> 
> Best regards,
> -- 
> Pietro Borrello <borrello@diag.uniroma1.it>
> 


^ permalink raw reply	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2023-02-15 18:21 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2023-02-12 18:59 [PATCH v4 0/5] HID: use spinlocks to safely schedule led workers Pietro Borrello
2023-02-12 18:59 ` [PATCH v4 1/5] HID: bigben: use spinlock to protect concurrent accesses Pietro Borrello
2023-02-12 19:00 ` [PATCH v4 2/5] HID: bigben_worker() remove unneeded check on report_field Pietro Borrello
2023-02-12 19:00 ` [PATCH v4 3/5] HID: bigben: use spinlock to safely schedule workers Pietro Borrello
2023-02-12 19:00 ` [PATCH v4 4/5] HID: asus: use spinlock to protect concurrent accesses Pietro Borrello
2023-02-12 19:00 ` [PATCH v4 5/5] HID: asus: use spinlock to safely schedule workers Pietro Borrello
2023-02-15 18:18 ` (subset) [PATCH v4 0/5] HID: use spinlocks to safely schedule led workers Benjamin Tissoires
2023-02-15 18:20 ` Benjamin Tissoires

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).