From 03f449b5de36a25fc5eba7dabf2b4494389bdb91 Mon Sep 17 00:00:00 2001 From: Yousef Alhouseen Date: Mon, 29 Jun 2026 17:29:47 +0200 Subject: [PATCH 1/6] HID: hiddev: keep state alive through disconnect unlock mutex_unlock() clears the mutex owner before taking its wait lock. A thread spinning in the final hiddev file release can acquire existancelock after hiddev_disconnect() clears the owner, then free hiddev before the disconnecting thread reaches the mutex wait lock. This causes a use-after-free in the mutex slow unlock path. Give the connection and each open file an explicit reference. Drop each reference only after its existancelock critical section has completed, so neither unlock path can free the mutex while the other is still using it. Fixes: 079034073faf ("HID: hiddev cleanup -- handle all error conditions properly") Reported-by: syzbot+563191a4939ddbfe73d4@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=563191a4939ddbfe73d4 Cc: stable@vger.kernel.org Signed-off-by: Yousef Alhouseen Link: https://syzkaller.appspot.com/bug?extid=563191a4939ddbfe73d4 Signed-off-by: Jiri Kosina --- drivers/hid/usbhid/hiddev.c | 37 +++++++++++++++++++------------------ include/linux/hiddev.h | 2 ++ 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/drivers/hid/usbhid/hiddev.c b/drivers/hid/usbhid/hiddev.c index 6378801b22c6..21396481995b 100644 --- a/drivers/hid/usbhid/hiddev.c +++ b/drivers/hid/usbhid/hiddev.c @@ -46,6 +46,12 @@ struct hiddev_list { struct mutex thread_lock; }; +static void hiddev_put(struct hiddev *hiddev) +{ + if (refcount_dec_and_test(&hiddev->refcount)) + kfree(hiddev); +} + /* * Find a report, given the report's type and ID. The ID can be specified * indirectly by REPORT_ID_FIRST (which returns the first report of the given @@ -216,26 +222,21 @@ static int hiddev_fasync(int fd, struct file *file, int on) static int hiddev_release(struct inode * inode, struct file * file) { struct hiddev_list *list = file->private_data; + struct hiddev *hiddev = list->hiddev; unsigned long flags; - spin_lock_irqsave(&list->hiddev->list_lock, flags); + spin_lock_irqsave(&hiddev->list_lock, flags); list_del(&list->node); - spin_unlock_irqrestore(&list->hiddev->list_lock, flags); + spin_unlock_irqrestore(&hiddev->list_lock, flags); - mutex_lock(&list->hiddev->existancelock); - if (!--list->hiddev->open) { - if (list->hiddev->exist) { - hid_hw_close(list->hiddev->hid); - hid_hw_power(list->hiddev->hid, PM_HINT_NORMAL); - } else { - mutex_unlock(&list->hiddev->existancelock); - kfree(list->hiddev); - vfree(list); - return 0; - } + mutex_lock(&hiddev->existancelock); + if (!--hiddev->open && hiddev->exist) { + hid_hw_close(hiddev->hid); + hid_hw_power(hiddev->hid, PM_HINT_NORMAL); } - mutex_unlock(&list->hiddev->existancelock); + mutex_unlock(&hiddev->existancelock); + hiddev_put(hiddev); vfree(list); return 0; @@ -270,6 +271,7 @@ static int __hiddev_open(struct hiddev *hiddev, struct file *file) spin_unlock_irq(&hiddev->list_lock); file->private_data = list; + refcount_inc(&hiddev->refcount); return 0; @@ -897,6 +899,7 @@ int hiddev_connect(struct hid_device *hid, unsigned int force) INIT_LIST_HEAD(&hiddev->list); spin_lock_init(&hiddev->list_lock); mutex_init(&hiddev->existancelock); + refcount_set(&hiddev->refcount, 1); hid->hiddev = hiddev; hiddev->hid = hid; hiddev->exist = 1; @@ -937,9 +940,7 @@ void hiddev_disconnect(struct hid_device *hid) if (hiddev->open) { hid_hw_close(hiddev->hid); wake_up_interruptible(&hiddev->wait); - mutex_unlock(&hiddev->existancelock); - } else { - mutex_unlock(&hiddev->existancelock); - kfree(hiddev); } + mutex_unlock(&hiddev->existancelock); + hiddev_put(hiddev); } diff --git a/include/linux/hiddev.h b/include/linux/hiddev.h index 2164c03d2c72..8e9f8a33e359 100644 --- a/include/linux/hiddev.h +++ b/include/linux/hiddev.h @@ -13,6 +13,7 @@ #ifndef _HIDDEV_H #define _HIDDEV_H +#include #include @@ -24,6 +25,7 @@ struct hiddev { int minor; int exist; int open; + refcount_t refcount; struct mutex existancelock; wait_queue_head_t wait; struct hid_device *hid; From 45c8ab60f8b8cbea46e8717ec548b41c0096c951 Mon Sep 17 00:00:00 2001 From: Yousef Alhouseen Date: Tue, 30 Jun 2026 23:18:45 +0200 Subject: [PATCH 2/6] HID: hiddev: set report-init policy before device registration usb_register_dev() publishes the hiddev character device before hiddev_connect() initializes the report policy. A process that opens the new node immediately can issue a usage ioctl while initialized is still false and trigger usbhid_init_reports(), bypassing HID_QUIRK_NO_INIT_REPORTS. Set initialized before registering the character device so every opener observes the quirk-selected policy. Signed-off-by: Yousef Alhouseen Signed-off-by: Jiri Kosina --- drivers/hid/usbhid/hiddev.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/drivers/hid/usbhid/hiddev.c b/drivers/hid/usbhid/hiddev.c index 21396481995b..8430655c00fe 100644 --- a/drivers/hid/usbhid/hiddev.c +++ b/drivers/hid/usbhid/hiddev.c @@ -903,6 +903,8 @@ int hiddev_connect(struct hid_device *hid, unsigned int force) hid->hiddev = hiddev; hiddev->hid = hid; hiddev->exist = 1; + /* Honor NO_INIT_REPORTS before the character device becomes visible. */ + hiddev->initialized = hid->quirks & HID_QUIRK_NO_INIT_REPORTS; retval = usb_register_dev(usbhid->intf, &hiddev_class); if (retval) { hid_err(hid, "Not able to get a minor for this device\n"); @@ -911,12 +913,6 @@ int hiddev_connect(struct hid_device *hid, unsigned int force) return retval; } - /* - * If HID_QUIRK_NO_INIT_REPORTS is set, make sure we don't initialize - * the reports. - */ - hiddev->initialized = hid->quirks & HID_QUIRK_NO_INIT_REPORTS; - hiddev->minor = usbhid->intf->minor; return 0; From 0fd3187e622765c7a6371105ab0328f2da479648 Mon Sep 17 00:00:00 2001 From: Pawel Zalewski Date: Tue, 8 Sep 2026 10:19:03 +0100 Subject: [PATCH 3/6] HID: hid-google-hammer: use named initializers for acpi_device_id Use a designated initializer for the acpi_device_id fields which makes the code more readable and consistent with how lists are initialized in the rest of the kernel code base. Also drop explicitly setting fields to 0 where it is redundant. Signed-off-by: Pawel Zalewski Signed-off-by: Jiri Kosina --- drivers/hid/hid-google-hammer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hid/hid-google-hammer.c b/drivers/hid/hid-google-hammer.c index c99c3c0d442e..847b1ddefeec 100644 --- a/drivers/hid/hid-google-hammer.c +++ b/drivers/hid/hid-google-hammer.c @@ -268,7 +268,7 @@ static void cbas_ec_remove(struct platform_device *pdev) #ifdef CONFIG_ACPI static const struct acpi_device_id cbas_ec_acpi_ids[] = { - { "GOOG000B", 0 }, + { .id = "GOOG000B" }, { } }; MODULE_DEVICE_TABLE(acpi, cbas_ec_acpi_ids); From cc1aef15d1f028e1a66029b7d4f7660f149c9e95 Mon Sep 17 00:00:00 2001 From: Pawel Zalewski Date: Tue, 8 Sep 2026 10:19:04 +0100 Subject: [PATCH 4/6] HID: i2c-hid: use named initializers for acpi_device_id Use a named initializer for the acpi_device_id fields which makes the code more readable and consistent with how lists are initialized in the rest of the kernel code base. Signed-off-by: Pawel Zalewski Signed-off-by: Jiri Kosina --- drivers/hid/i2c-hid/i2c-hid-acpi.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/hid/i2c-hid/i2c-hid-acpi.c b/drivers/hid/i2c-hid/i2c-hid-acpi.c index 13f977d6aab6..9371db200fc4 100644 --- a/drivers/hid/i2c-hid/i2c-hid-acpi.c +++ b/drivers/hid/i2c-hid/i2c-hid-acpi.c @@ -39,12 +39,12 @@ static const struct acpi_device_id i2c_hid_acpi_blacklist[] = { * The CHPN0001 ACPI device, which is used to describe the Chipone * ICN8505 controller, has a _CID of PNP0C50 but is not HID compatible. */ - { "CHPN0001" }, + { .id = "CHPN0001" }, /* * The IDEA5002 ACPI device causes high interrupt usage and spurious * wakeups from suspend. */ - { "IDEA5002" }, + { .id = "IDEA5002" }, { } }; @@ -93,8 +93,8 @@ static int i2c_hid_acpi_probe(struct i2c_client *client) } static const struct acpi_device_id i2c_hid_acpi_match[] = { - { "ACPI0C50" }, - { "PNP0C50" }, + { .id = "ACPI0C50" }, + { .id = "PNP0C50" }, { } }; MODULE_DEVICE_TABLE(acpi, i2c_hid_acpi_match); From 143b379a041e013e244b42805ca3b6e4c1555d51 Mon Sep 17 00:00:00 2001 From: Pawel Zalewski Date: Tue, 8 Sep 2026 10:19:05 +0100 Subject: [PATCH 5/6] HID: surface-hid: use named initializers for acpi_device_id Use a named initializer for the acpi_device_id fields which makes the code more readable and consistent with how lists are initialized in the rest of the kernel code base. Unify the list terminator to have a single space between the brackets and no trailing comma. Signed-off-by: Pawel Zalewski Signed-off-by: Jiri Kosina --- drivers/hid/surface-hid/surface_kbd.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/hid/surface-hid/surface_kbd.c b/drivers/hid/surface-hid/surface_kbd.c index 0be01b5e7425..036829ba47d4 100644 --- a/drivers/hid/surface-hid/surface_kbd.c +++ b/drivers/hid/surface-hid/surface_kbd.c @@ -277,8 +277,8 @@ static void surface_kbd_remove(struct platform_device *pdev) } static const struct acpi_device_id surface_kbd_match[] = { - { "MSHW0096" }, - { }, + { .id = "MSHW0096" }, + { } }; MODULE_DEVICE_TABLE(acpi, surface_kbd_match); From 5fe63b415c636c7f4f6848a8e53bdd4bbf0c300d Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Sat, 10 Oct 2026 13:44:15 -0400 Subject: [PATCH 6/6] HID: core: Fix out-of-bounds left shift by 32 in s32ton() Syzbot reported an out-of-bounds shift (32-bit integer shifted left by 32) in the s32ton() routine in the HID core. This routine converts a signed 32-bit integer to a signed n-bit integer, and it caps the value of n at 32. But when n == 32 there's no need to do anything; it can simply return the input value. This will avoid calculating (1 << n) when n is 32, which was the cause of the invalid shift. Signed-off-by: Alan Stern Fixes: 69c02ffde6ed ("HID: core: clamp report_size in s32ton() to avoid undefined shift") Reported-by: syzbot+25a1d2907885b2182275@syzkaller.appspotmail.com Closes: https://lore.kernel.org/linux-usb/6ac9cbd8.a150a118.27e199.0025.GAE@google.com/ Tested-by: syzbot+25a1d2907885b2182275@syzkaller.appspotmail.com Cc: stable@vger.kernel.org Link: https://patch.msgid.link/dfa0aa1c-e55e-4316-8307-f72a07383634@rowland.harvard.edu Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-core.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index a3ff0514f9cd..a71ddaa810a0 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -71,8 +71,8 @@ static u32 s32ton(__s32 value, unsigned int n) if (!value || !n) return 0; - if (n > 32) - n = 32; + if (n >= 32) + return (u32) value; a = value >> (n - 1); if (a && a != -1)