* [PATCH v4 1/4] HID: roccat: fix locking in roccat_connect() and roccat_disconnect()
@ 2026-09-16 15:47 Dmitry Antipov
2026-09-16 15:47 ` [PATCH v4 2/4] HID: roccat: use device_is_registered() to check whether device is available Dmitry Antipov
` (2 more replies)
0 siblings, 3 replies; 5+ messages in thread
From: Dmitry Antipov @ 2026-09-16 15:47 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires; +Cc: linux-input, lvc-project, Dmitry Antipov
Extend critical section in roccat_connect() to ensure that partially
initialized 'struct roccat_device' is never exposed in 'devices' list,
and do the same in roccat_disconnect() to avoid racy 'devices' access
against roccat_release().
Signed-off-by: Dmitry Antipov <dmantipov@yandex.ru>
---
v2 and upwards: unchanged since v1
---
drivers/hid/hid-roccat.c | 8 +++-----
1 file changed, 3 insertions(+), 5 deletions(-)
diff --git a/drivers/hid/hid-roccat.c b/drivers/hid/hid-roccat.c
index 4f15eb951039..5deb6da8d4f7 100644
--- a/drivers/hid/hid-roccat.c
+++ b/drivers/hid/hid-roccat.c
@@ -344,8 +344,6 @@ int roccat_connect(const struct class *klass, struct hid_device *hid, int report
return temp;
}
- mutex_unlock(&devices_lock);
-
init_waitqueue_head(&device->wait);
INIT_LIST_HEAD(&device->readers);
mutex_init(&device->readers_lock);
@@ -356,6 +354,7 @@ int roccat_connect(const struct class *klass, struct hid_device *hid, int report
device->cbuf_end = 0;
device->report_size = report_size;
+ mutex_unlock(&devices_lock);
return minor;
}
EXPORT_SYMBOL_GPL(roccat_connect);
@@ -369,15 +368,12 @@ void roccat_disconnect(int minor)
mutex_lock(&devices_lock);
device = devices[minor];
- mutex_unlock(&devices_lock);
device->exist = 0; /* TODO exist maybe not needed */
device_destroy(device->dev->class, MKDEV(roccat_major, minor));
- mutex_lock(&devices_lock);
devices[minor] = NULL;
- mutex_unlock(&devices_lock);
if (device->open) {
hid_hw_close(device->hid);
@@ -385,6 +381,8 @@ void roccat_disconnect(int minor)
} else {
roccat_free_device(device);
}
+
+ mutex_unlock(&devices_lock);
}
EXPORT_SYMBOL_GPL(roccat_disconnect);
--
2.55.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH v4 2/4] HID: roccat: use device_is_registered() to check whether device is available
2026-09-16 15:47 [PATCH v4 1/4] HID: roccat: fix locking in roccat_connect() and roccat_disconnect() Dmitry Antipov
@ 2026-09-16 15:47 ` Dmitry Antipov
2026-09-16 15:56 ` sashiko-bot
2026-09-16 15:47 ` [PATCH v4 3/4] HID: roccat: fix device access in roccat_release() Dmitry Antipov
2026-09-16 15:47 ` [PATCH v4 4/4] HID: roccat: use kref to manage device instances Dmitry Antipov
2 siblings, 1 reply; 5+ messages in thread
From: Dmitry Antipov @ 2026-09-16 15:47 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires; +Cc: linux-input, lvc-project, Dmitry Antipov
Introduce roccat_device_available() to check whether device
is actually available (i.e. not disconnected), thus removing
explicit 'exist' flag from 'struct roccat_device'.
Signed-off-by: Dmitry Antipov <dmantipov@yandex.ru>
---
v4: use READ_ONCE() and WRITE_ONCE() for device access
v3: initial version to join the series
---
drivers/hid/hid-roccat.c | 19 +++++++++++--------
1 file changed, 11 insertions(+), 8 deletions(-)
diff --git a/drivers/hid/hid-roccat.c b/drivers/hid/hid-roccat.c
index 5deb6da8d4f7..96aa508111a1 100644
--- a/drivers/hid/hid-roccat.c
+++ b/drivers/hid/hid-roccat.c
@@ -40,7 +40,6 @@ struct roccat_device {
unsigned int minor;
int report_size;
int open;
- int exist;
wait_queue_head_t wait;
struct device *dev;
struct hid_device *hid;
@@ -70,6 +69,13 @@ static struct roccat_device *devices[ROCCAT_MAX_DEVICES];
/* protects modifications of devices array */
static DEFINE_MUTEX(devices_lock);
+static bool roccat_device_available(struct roccat_device *device)
+{
+ struct device *dev = READ_ONCE(device->dev);
+
+ return dev ? device_is_registered(dev) : false;
+}
+
static void roccat_free_device(struct roccat_device *device)
{
int i;
@@ -105,7 +111,7 @@ static ssize_t roccat_read(struct file *file, char __user *buffer,
retval = -ERESTARTSYS;
break;
}
- if (!device->exist) {
+ if (!roccat_device_available(device)) {
retval = -EIO;
break;
}
@@ -149,7 +155,7 @@ static __poll_t roccat_poll(struct file *file, poll_table *wait)
poll_wait(file, &reader->device->wait, wait);
if (reader->cbuf_start != reader->device->cbuf_end)
return EPOLLIN | EPOLLRDNORM;
- if (!reader->device->exist)
+ if (!roccat_device_available(reader->device))
return EPOLLERR | EPOLLHUP;
return 0;
}
@@ -231,7 +237,7 @@ static int roccat_release(struct inode *inode, struct file *file)
if (!--device->open) {
/* removing last reader */
- if (device->exist) {
+ if (roccat_device_available(device)) {
hid_hw_power(device->hid, PM_HINT_NORMAL);
hid_hw_close(device->hid);
} else {
@@ -350,7 +356,6 @@ int roccat_connect(const struct class *klass, struct hid_device *hid, int report
mutex_init(&device->cbuf_lock);
device->minor = minor;
device->hid = hid;
- device->exist = 1;
device->cbuf_end = 0;
device->report_size = report_size;
@@ -369,10 +374,8 @@ void roccat_disconnect(int minor)
mutex_lock(&devices_lock);
device = devices[minor];
- device->exist = 0; /* TODO exist maybe not needed */
-
device_destroy(device->dev->class, MKDEV(roccat_major, minor));
-
+ WRITE_ONCE(device->dev, NULL);
devices[minor] = NULL;
if (device->open) {
--
2.55.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH v4 3/4] HID: roccat: fix device access in roccat_release()
2026-09-16 15:47 [PATCH v4 1/4] HID: roccat: fix locking in roccat_connect() and roccat_disconnect() Dmitry Antipov
2026-09-16 15:47 ` [PATCH v4 2/4] HID: roccat: use device_is_registered() to check whether device is available Dmitry Antipov
@ 2026-09-16 15:47 ` Dmitry Antipov
2026-09-16 15:47 ` [PATCH v4 4/4] HID: roccat: use kref to manage device instances Dmitry Antipov
2 siblings, 0 replies; 5+ messages in thread
From: Dmitry Antipov @ 2026-09-16 15:47 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires
Cc: linux-input, lvc-project, Dmitry Antipov, Sashiko
In roccat_release(), access the device using file-specific
reader data rather than global array, thus preventing the
case when original device was disconnected and a new device
has connected using the same slot indexed by minor number.
Reported-by: Sashiko <sashiko-bot@kernel.org>
Link: https://sashiko.dev/#/patchset/20260902094551.200587-1-dmantipov@yandex.ru?part=2
Signed-off-by: Dmitry Antipov <dmantipov@yandex.ru>
---
v3 and upwards: unchanged
v2: initial version to join the series
---
drivers/hid/hid-roccat.c | 13 ++++---------
1 file changed, 4 insertions(+), 9 deletions(-)
diff --git a/drivers/hid/hid-roccat.c b/drivers/hid/hid-roccat.c
index eb6b71417175..7890bf079a3b 100644
--- a/drivers/hid/hid-roccat.c
+++ b/drivers/hid/hid-roccat.c
@@ -216,24 +216,19 @@ static int roccat_open(struct inode *inode, struct file *file)
static int roccat_release(struct inode *inode, struct file *file)
{
- unsigned int minor = iminor(inode);
struct roccat_reader *reader = file->private_data;
- struct roccat_device *device;
-
- mutex_lock(&devices_lock);
+ struct roccat_device *device = reader->device;
- device = devices[minor];
- if (!device) {
- mutex_unlock(&devices_lock);
- pr_emerg("roccat device with minor %d doesn't exist\n", minor);
+ if (WARN_ON(!device))
return -ENODEV;
- }
mutex_lock(&device->readers_lock);
list_del(&reader->node);
mutex_unlock(&device->readers_lock);
kfree(reader);
+ mutex_lock(&devices_lock);
+
if (!--device->open) {
/* removing last reader */
if (roccat_device_available(device)) {
--
2.55.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH v4 4/4] HID: roccat: use kref to manage device instances
2026-09-16 15:47 [PATCH v4 1/4] HID: roccat: fix locking in roccat_connect() and roccat_disconnect() Dmitry Antipov
2026-09-16 15:47 ` [PATCH v4 2/4] HID: roccat: use device_is_registered() to check whether device is available Dmitry Antipov
2026-09-16 15:47 ` [PATCH v4 3/4] HID: roccat: fix device access in roccat_release() Dmitry Antipov
@ 2026-09-16 15:47 ` Dmitry Antipov
2 siblings, 0 replies; 5+ messages in thread
From: Dmitry Antipov @ 2026-09-16 15:47 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires
Cc: linux-input, lvc-project, Dmitry Antipov,
syzbot+d632e93ffcd1452bc61e
Use kref to manage 'struct roccat_device' instances and fix both memory
leaks and UaF-triggering races between roccat_open()/roccat_release()
and roccat_connect()/roccat_disconnect() pairs. To avoid the scenario
when opened device is disconnected and its slot indexed by minor number
is reused by another device, release the slot in roccat_free_device()
rather than in roccat_disconnect(). This way, there is a time frame when
disconnected device may still occupy the slot; to reject such a device,
add extra roccat_device_available() checks to roccat_open() and
roccat_ioctl(). Add extra WARN_ON() device check to roccat_disconnect()
and a few debugging quirks to roccat_free_device() as well.
Reported-by: syzbot+d632e93ffcd1452bc61e@syzkaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?extid=d632e93ffcd1452bc61e
Link: https://sashiko.dev/#/patchset/20260902094551.200587-1-dmantipov@yandex.ru?part=2
Signed-off-by: Dmitry Antipov <dmantipov@yandex.ru>
---
v4: unchanged
v3: release device slot in roccat_free_device(), add required checks
to roccat_open() and roccat_ioctl(), few more debugging quirks
v2: unconditionally get/put device reference during
first open and last close, respectively (Sashiko)
---
drivers/hid/hid-roccat.c | 33 +++++++++++++++++++++++----------
1 file changed, 23 insertions(+), 10 deletions(-)
diff --git a/drivers/hid/hid-roccat.c b/drivers/hid/hid-roccat.c
index 7890bf079a3b..f9b09f58f601 100644
--- a/drivers/hid/hid-roccat.c
+++ b/drivers/hid/hid-roccat.c
@@ -40,6 +40,7 @@ struct roccat_device {
unsigned int minor;
int report_size;
int open;
+ struct kref ref;
wait_queue_head_t wait;
struct device *dev;
struct hid_device *hid;
@@ -75,12 +76,20 @@ static bool roccat_device_available(struct roccat_device *device)
return dev ? device_is_registered(dev) : false;
}
-static void roccat_free_device(struct roccat_device *device)
+static void roccat_free_device(struct kref *ref)
{
+ struct roccat_device *device;
int i;
+ WARN_ON(!mutex_is_locked(&devices_lock));
+
+ device = container_of(ref, struct roccat_device, ref);
for (i = 0; i < ROCCAT_CBUF_SIZE; i++)
kfree(device->cbuf[i].value);
+
+ devices[device->minor] = NULL;
+ mutex_destroy(&device->readers_lock);
+ mutex_destroy(&device->cbuf_lock);
kfree(device);
}
@@ -174,7 +183,7 @@ static int roccat_open(struct inode *inode, struct file *file)
device = devices[minor];
- if (!device) {
+ if (!device || !roccat_device_available(device)) {
pr_emerg("roccat device with minor %d doesn't exist\n", minor);
error = -ENODEV;
goto exit_err_devices;
@@ -196,6 +205,7 @@ static int roccat_open(struct inode *inode, struct file *file)
--device->open;
goto exit_err_readers;
}
+ kref_get(&device->ref);
}
reader->device = device;
@@ -234,9 +244,8 @@ static int roccat_release(struct inode *inode, struct file *file)
if (roccat_device_available(device)) {
hid_hw_power(device->hid, PM_HINT_NORMAL);
hid_hw_close(device->hid);
- } else {
- roccat_free_device(device);
}
+ kref_put(&device->ref, roccat_free_device);
}
mutex_unlock(&devices_lock);
@@ -348,6 +357,7 @@ int roccat_connect(const struct class *klass, struct hid_device *hid, int report
INIT_LIST_HEAD(&device->readers);
mutex_init(&device->readers_lock);
mutex_init(&device->cbuf_lock);
+ kref_init(&device->ref);
device->minor = minor;
device->hid = hid;
device->cbuf_end = 0;
@@ -367,18 +377,21 @@ void roccat_disconnect(int minor)
mutex_lock(&devices_lock);
device = devices[minor];
+ if (WARN_ON(!device))
+ goto out;
- device_destroy(device->dev->class, MKDEV(roccat_major, minor));
- WRITE_ONCE(device->dev, NULL);
- devices[minor] = NULL;
+ if (!WARN_ON(!roccat_device_available(device))) {
+ device_destroy(device->dev->class, MKDEV(roccat_major, minor));
+ WRITE_ONCE(device->dev, NULL);
+ }
if (device->open) {
hid_hw_close(device->hid);
wake_up_interruptible(&device->wait);
- } else {
- roccat_free_device(device);
}
+ kref_put(&device->ref, roccat_free_device);
+out:
mutex_unlock(&devices_lock);
}
EXPORT_SYMBOL_GPL(roccat_disconnect);
@@ -393,7 +406,7 @@ static long roccat_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
mutex_lock(&devices_lock);
device = devices[minor];
- if (!device) {
+ if (!device || !roccat_device_available(device)) {
retval = -ENODEV;
goto out;
}
--
2.55.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH v4 2/4] HID: roccat: use device_is_registered() to check whether device is available
2026-09-16 15:47 ` [PATCH v4 2/4] HID: roccat: use device_is_registered() to check whether device is available Dmitry Antipov
@ 2026-09-16 15:56 ` sashiko-bot
0 siblings, 0 replies; 5+ messages in thread
From: sashiko-bot @ 2026-09-16 15:56 UTC (permalink / raw)
To: Dmitry Antipov; +Cc: linux-input, dmitry.torokhov
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] HID: roccat: use-after-free of device->dev in roccat_device_available()
--
commit 10e7416f7d2b9142317f413c7988155b077d1914
Author: Dmitry Antipov <dmantipov@yandex.ru>
HID: roccat: use device_is_registered() to check whether device is available
This commit introduces roccat_device_available() to check if the device
is registered rather than relying on a boolean exist flag. It replaces
the flag checks in the read, poll, and release paths with the new helper.
> diff --git a/drivers/hid/hid-roccat.c b/drivers/hid/hid-roccat.c
> index 5deb6da8d4f74..96aa508111a1b 100644
> --- a/drivers/hid/hid-roccat.c
> +++ b/drivers/hid/hid-roccat.c
[ ... ]
> @@ -70,6 +69,13 @@ static struct roccat_device *devices[ROCCAT_MAX_DEVICES];
> /* protects modifications of devices array */
> static DEFINE_MUTEX(devices_lock);
>
> +static bool roccat_device_available(struct roccat_device *device)
> +{
> + struct device *dev = READ_ONCE(device->dev);
> +
> + return dev ? device_is_registered(dev) : false;
[Severity: High]
Does this lockless check risk a use-after-free of the dev pointer in
drivers/hid/hid-roccat.c?
If a userspace process calls roccat_read() or roccat_poll() on the character
device while the physical device is simultaneously unplugged,
roccat_disconnect() can run concurrently. Since roccat_disconnect() unregisters
and drops the reference to the device, dev could be freed after the
READ_ONCE() but before device_is_registered() accesses
dev->kobj.state_in_sysfs.
> +}
> +
[ ... ]
> @@ -369,10 +374,8 @@ void roccat_disconnect(int minor)
> mutex_lock(&devices_lock);
> device = devices[minor];
>
> - device->exist = 0; /* TODO exist maybe not needed */
> -
> device_destroy(device->dev->class, MKDEV(roccat_major, minor));
> -
> + WRITE_ONCE(device->dev, NULL);
[Severity: High]
Could this synchronously free the struct device while concurrent readers
still hold the raw pointer in roccat_disconnect() in drivers/hid/hid-roccat.c?
device_destroy() synchronously unregisters and drops the final reference to
the struct device, freeing it. Because roccat_device_available() doesn't
hold a reference or lock when checking device_is_registered(), this opens a
time-of-check to time-of-use (TOCTOU) race window.
> devices[minor] = NULL;
>
> if (device->open) {
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260916154733.78464-1-dmantipov@yandex.ru?part=2
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-09-16 15:56 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-16 15:47 [PATCH v4 1/4] HID: roccat: fix locking in roccat_connect() and roccat_disconnect() Dmitry Antipov
2026-09-16 15:47 ` [PATCH v4 2/4] HID: roccat: use device_is_registered() to check whether device is available Dmitry Antipov
2026-09-16 15:56 ` sashiko-bot
2026-09-16 15:47 ` [PATCH v4 3/4] HID: roccat: fix device access in roccat_release() Dmitry Antipov
2026-09-16 15:47 ` [PATCH v4 4/4] HID: roccat: use kref to manage device instances Dmitry Antipov
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.