From: Heitor Alves de Siqueira <halves@igalia.com>
To: Jiri Kosina <jikos@kernel.org>, Benjamin Tissoires <bentiss@kernel.org>
Cc: kernel-dev@igalia.com, linux-usb@vger.kernel.org,
linux-input@vger.kernel.org, linux-kernel@vger.kernel.org,
syzbot+563191a4939ddbfe73d4@syzkaller.appspotmail.com
Subject: [PATCH] HID: hiddev: Use kref to track struct hiddev lifetime
Date: Mon, 08 Jun 2026 13:33:03 -0300 [thread overview]
Message-ID: <20260608-hiddev_kref-v1-1-cd240c95423f@igalia.com> (raw)
If a USB HID device is disconnected while userspace still holds the
hiddev node open, hiddev_disconnect() and hiddev_release() can race on
the embedded existancelock mutex. Syzbot has triggered this with kfree()
happening during the mutex slow path.
Fix by introducing a kref in struct hiddev, and moving kfree() into a
dedicated release callback. This way, struct hiddev will only be freed
after both hiddev_release() and hiddev_disconnect() are done.
Fixes: 6cb4b040795c ("HID: hiddev: fix race between hiddev_disconnect and hiddev_release")
Reported-by: syzbot+563191a4939ddbfe73d4@syzkaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?extid=563191a4939ddbfe73d4
Signed-off-by: Heitor Alves de Siqueira <halves@igalia.com>
---
drivers/hid/usbhid/hiddev.c | 36 ++++++++++++++++++++++++------------
include/linux/hiddev.h | 1 +
2 files changed, 25 insertions(+), 12 deletions(-)
diff --git a/drivers/hid/usbhid/hiddev.c b/drivers/hid/usbhid/hiddev.c
index 6378801b22c6..1f2802920bee 100644
--- a/drivers/hid/usbhid/hiddev.c
+++ b/drivers/hid/usbhid/hiddev.c
@@ -46,6 +46,13 @@ struct hiddev_list {
struct mutex thread_lock;
};
+static void hiddev_kref_free(struct kref *kref)
+{
+ struct hiddev *hiddev = container_of(kref, struct hiddev, kref);
+
+ 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
@@ -227,15 +234,11 @@ static int hiddev_release(struct inode * inode, struct file * file)
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_unlock(&list->hiddev->existancelock);
+
+ kref_put(&list->hiddev->kref, hiddev_kref_free);
vfree(list);
return 0;
@@ -298,10 +301,20 @@ static int hiddev_open(struct inode *inode, struct file *file)
hid = usb_get_intfdata(intf);
hiddev = hid->hiddev;
+ /*
+ * kref_get_unless_zero() checks if we've already dropped
+ * the last reference; bail out early in this case
+ */
+ if (!hiddev || !kref_get_unless_zero(&hiddev->kref))
+ return -ENODEV;
+
mutex_lock(&hiddev->existancelock);
res = hiddev->exist ? __hiddev_open(hiddev, file) : -ENODEV;
mutex_unlock(&hiddev->existancelock);
+ if (res < 0)
+ kref_put(&hiddev->kref, hiddev_kref_free);
+
return res;
}
@@ -893,6 +906,7 @@ int hiddev_connect(struct hid_device *hid, unsigned int force)
if (!(hiddev = kzalloc_obj(struct hiddev)))
return -ENOMEM;
+ kref_init(&hiddev->kref);
init_waitqueue_head(&hiddev->wait);
INIT_LIST_HEAD(&hiddev->list);
spin_lock_init(&hiddev->list_lock);
@@ -904,7 +918,7 @@ int hiddev_connect(struct hid_device *hid, unsigned int force)
if (retval) {
hid_err(hid, "Not able to get a minor for this device\n");
hid->hiddev = NULL;
- kfree(hiddev);
+ kref_put(&hiddev->kref, hiddev_kref_free);
return retval;
}
@@ -933,13 +947,11 @@ void hiddev_disconnect(struct hid_device *hid)
mutex_lock(&hiddev->existancelock);
hiddev->exist = 0;
-
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);
+
+ kref_put(&hiddev->kref, hiddev_kref_free);
}
diff --git a/include/linux/hiddev.h b/include/linux/hiddev.h
index 2164c03d2c72..1613c9c37ab7 100644
--- a/include/linux/hiddev.h
+++ b/include/linux/hiddev.h
@@ -24,6 +24,7 @@ struct hiddev {
int minor;
int exist;
int open;
+ struct kref kref;
struct mutex existancelock;
wait_queue_head_t wait;
struct hid_device *hid;
---
base-commit: e43ffb69e0438cddd72aaa30898b4dc446f664f8
change-id: 20260603-hiddev_kref-52f5507d4e42
Best regards,
--
Heitor Alves de Siqueira <halves@igalia.com>
next reply other threads:[~2026-06-08 16:33 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-06-08 16:33 Heitor Alves de Siqueira [this message]
2026-06-08 18:44 ` Dmitry Torokhov
2026-06-11 12:36 ` Heitor Alves de Siqueira
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260608-hiddev_kref-v1-1-cd240c95423f@igalia.com \
--to=halves@igalia.com \
--cc=bentiss@kernel.org \
--cc=jikos@kernel.org \
--cc=kernel-dev@igalia.com \
--cc=linux-input@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-usb@vger.kernel.org \
--cc=syzbot+563191a4939ddbfe73d4@syzkaller.appspotmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®