* [PATCH v2 1/4] HID: roccat: fix locking in roccat_connect() and roccat_disconnect()
@ 2026-09-14 12:02 Dmitry Antipov
2026-09-14 12:02 ` [PATCH v2 2/4] HID: roccat: fix device access in roccat_release() Dmitry Antipov
` (3 more replies)
0 siblings, 4 replies; 8+ messages in thread
From: Dmitry Antipov @ 2026-09-14 12:02 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: 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] 8+ messages in thread
* [PATCH v2 2/4] HID: roccat: fix device access in roccat_release()
2026-09-14 12:02 [PATCH v2 1/4] HID: roccat: fix locking in roccat_connect() and roccat_disconnect() Dmitry Antipov
@ 2026-09-14 12:02 ` Dmitry Antipov
2026-09-14 12:44 ` sashiko-bot
2026-09-14 12:02 ` [PATCH v2 3/4] HID: roccat: examine readers to check whether the device is opened Dmitry Antipov
` (2 subsequent siblings)
3 siblings, 1 reply; 8+ messages in thread
From: Dmitry Antipov @ 2026-09-14 12:02 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>
---
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 5deb6da8d4f7..007778922c21 100644
--- a/drivers/hid/hid-roccat.c
+++ b/drivers/hid/hid-roccat.c
@@ -211,24 +211,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_ONCE(!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 (device->exist) {
--
2.55.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH v2 3/4] HID: roccat: examine readers to check whether the device is opened
2026-09-14 12:02 [PATCH v2 1/4] HID: roccat: fix locking in roccat_connect() and roccat_disconnect() Dmitry Antipov
2026-09-14 12:02 ` [PATCH v2 2/4] HID: roccat: fix device access in roccat_release() Dmitry Antipov
@ 2026-09-14 12:02 ` Dmitry Antipov
2026-09-14 12:53 ` sashiko-bot
2026-09-14 12:02 ` [PATCH v2 4/4] HID: roccat: use kref to manage device instances Dmitry Antipov
2026-09-14 12:26 ` [PATCH v2 1/4] HID: roccat: fix locking in roccat_connect() and roccat_disconnect() sashiko-bot
3 siblings, 1 reply; 8+ messages in thread
From: Dmitry Antipov @ 2026-09-14 12:02 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires; +Cc: linux-input, lvc-project, Dmitry Antipov
Use list_empty() to check whether the device has active readers,
thus dropping explicit 'open' flag from 'struct roccat_device'.
Signed-off-by: Dmitry Antipov <dmantipov@yandex.ru>
---
v2: 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 007778922c21..d1df4ec34a1e 100644
--- a/drivers/hid/hid-roccat.c
+++ b/drivers/hid/hid-roccat.c
@@ -39,7 +39,6 @@ struct roccat_report {
struct roccat_device {
unsigned int minor;
int report_size;
- int open;
int exist;
wait_queue_head_t wait;
struct device *dev;
@@ -177,18 +176,15 @@ static int roccat_open(struct inode *inode, struct file *file)
mutex_lock(&device->readers_lock);
- if (!device->open++) {
+ if (list_empty(&device->readers)) {
/* power on device on adding first reader */
error = hid_hw_power(device->hid, PM_HINT_FULLON);
- if (error < 0) {
- --device->open;
+ if (error < 0)
goto exit_err_readers;
- }
error = hid_hw_open(device->hid);
if (error < 0) {
hid_hw_power(device->hid, PM_HINT_NORMAL);
- --device->open;
goto exit_err_readers;
}
}
@@ -213,18 +209,20 @@ static int roccat_release(struct inode *inode, struct file *file)
{
struct roccat_reader *reader = file->private_data;
struct roccat_device *device = reader->device;
+ bool open;
if (WARN_ON_ONCE(!device))
return -ENODEV;
mutex_lock(&device->readers_lock);
list_del(&reader->node);
+ open = !list_empty(&device->readers);
mutex_unlock(&device->readers_lock);
kfree(reader);
mutex_lock(&devices_lock);
- if (!--device->open) {
+ if (!open) {
/* removing last reader */
if (device->exist) {
hid_hw_power(device->hid, PM_HINT_NORMAL);
@@ -360,6 +358,7 @@ EXPORT_SYMBOL_GPL(roccat_connect);
void roccat_disconnect(int minor)
{
struct roccat_device *device;
+ bool open;
mutex_lock(&devices_lock);
device = devices[minor];
@@ -370,7 +369,11 @@ void roccat_disconnect(int minor)
devices[minor] = NULL;
- if (device->open) {
+ mutex_lock(&device->readers_lock);
+ open = !list_empty(&device->readers);
+ mutex_unlock(&device->readers_lock);
+
+ if (open) {
hid_hw_close(device->hid);
wake_up_interruptible(&device->wait);
} else {
--
2.55.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH v2 4/4] HID: roccat: use kref to manage device instances
2026-09-14 12:02 [PATCH v2 1/4] HID: roccat: fix locking in roccat_connect() and roccat_disconnect() Dmitry Antipov
2026-09-14 12:02 ` [PATCH v2 2/4] HID: roccat: fix device access in roccat_release() Dmitry Antipov
2026-09-14 12:02 ` [PATCH v2 3/4] HID: roccat: examine readers to check whether the device is opened Dmitry Antipov
@ 2026-09-14 12:02 ` Dmitry Antipov
2026-09-14 13:07 ` sashiko-bot
2026-09-14 12:26 ` [PATCH v2 1/4] HID: roccat: fix locking in roccat_connect() and roccat_disconnect() sashiko-bot
3 siblings, 1 reply; 8+ messages in thread
From: Dmitry Antipov @ 2026-09-14 12:02 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
UaF-triggering race between roccat_open()/roccat_release()
and roccat_connect()/roccat_disconnect() pairs.
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>
---
v2: unconditionally get/put device reference during
first open and last close, respectively (Sashiko)
---
drivers/hid/hid-roccat.c | 16 +++++++++++-----
1 file changed, 11 insertions(+), 5 deletions(-)
diff --git a/drivers/hid/hid-roccat.c b/drivers/hid/hid-roccat.c
index d1df4ec34a1e..32927b20bcfe 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 exist;
+ struct kref ref;
wait_queue_head_t wait;
struct device *dev;
struct hid_device *hid;
@@ -69,10 +70,12 @@ static struct roccat_device *devices[ROCCAT_MAX_DEVICES];
/* protects modifications of devices array */
static DEFINE_MUTEX(devices_lock);
-static void roccat_free_device(struct roccat_device *device)
+static void roccat_free_device(struct kref *ref)
{
+ struct roccat_device *device;
int i;
+ device = container_of(ref, struct roccat_device, ref);
for (i = 0; i < ROCCAT_CBUF_SIZE; i++)
kfree(device->cbuf[i].value);
kfree(device);
@@ -187,6 +190,7 @@ static int roccat_open(struct inode *inode, struct file *file)
hid_hw_power(device->hid, PM_HINT_NORMAL);
goto exit_err_readers;
}
+ kref_get(&device->ref);
}
reader->device = device;
@@ -227,9 +231,8 @@ static int roccat_release(struct inode *inode, struct file *file)
if (device->exist) {
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);
@@ -341,6 +344,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->exist = 1;
@@ -362,6 +366,8 @@ void roccat_disconnect(int minor)
mutex_lock(&devices_lock);
device = devices[minor];
+ if (!device)
+ goto out;
device->exist = 0; /* TODO exist maybe not needed */
@@ -376,10 +382,10 @@ void roccat_disconnect(int minor)
if (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);
--
2.55.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* Re: [PATCH v2 1/4] HID: roccat: fix locking in roccat_connect() and roccat_disconnect()
2026-09-14 12:02 [PATCH v2 1/4] HID: roccat: fix locking in roccat_connect() and roccat_disconnect() Dmitry Antipov
` (2 preceding siblings ...)
2026-09-14 12:02 ` [PATCH v2 4/4] HID: roccat: use kref to manage device instances Dmitry Antipov
@ 2026-09-14 12:26 ` sashiko-bot
3 siblings, 0 replies; 8+ messages in thread
From: sashiko-bot @ 2026-09-14 12:26 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:
Pre-existing issues:
- [High] `roccat_report_event()` calls `mutex_lock()` while executing in atomic context.
--
commit 8cefeedf72bd07a9332b484733a5ce24fa525195
Author: Dmitry Antipov <dmantipov@yandex.ru>
HID: roccat: fix locking in roccat_connect() and roccat_disconnect()
This commit extends the critical section in roccat_connect() to avoid
exposing a partially initialized device in the devices list. It also
updates roccat_disconnect() to prevent racy access against
roccat_release().
> diff --git a/drivers/hid/hid-roccat.c b/drivers/hid/hid-roccat.c
> index 4f15eb9510396..5deb6da8d4f74 100644
> --- a/drivers/hid/hid-roccat.c
> +++ b/drivers/hid/hid-roccat.c
[ ... ]
[Severity: High]
This is a pre-existing issue, but does roccat_report_event() call
mutex_lock() while executing in an atomic context?
The roccat_report_event() function is invoked by the driver's raw_event
handler, which is called synchronously by the HID core from the USB URB
completion handler. Since URB completion handlers execute in atomic context,
acquiring sleeping locks here triggers a 'scheduling while atomic' bug.
drivers/hid/hid-roccat.c:roccat_report_event() {
...
mutex_lock(&device->readers_lock);
mutex_lock(&device->cbuf_lock);
...
}
This appears to be reachable whenever a supported Roccat device sends
specific hardware events, such as when a user presses a profile switch
button on the mouse. Should this code use a spinlock or defer the work
instead?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260914120240.1625442-1-dmantipov@yandex.ru?part=1
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v2 2/4] HID: roccat: fix device access in roccat_release()
2026-09-14 12:02 ` [PATCH v2 2/4] HID: roccat: fix device access in roccat_release() Dmitry Antipov
@ 2026-09-14 12:44 ` sashiko-bot
0 siblings, 0 replies; 8+ messages in thread
From: sashiko-bot @ 2026-09-14 12:44 UTC (permalink / raw)
To: Dmitry Antipov; +Cc: linux-input, dmitry.torokhov
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
Pre-existing issues:
- [Critical] HID roccat sub-drivers free their private driver data before calling hid_hw_stop(), causing a Use-After-Free in the raw_event callback.
- [High] roccat_report_event() accesses devices[minor] locklessly and does not check for NULL, leading to a NULL pointer dereference during device disconnect.
- [High] roccat_report_event() acquires sleepable mutexes from an atomic interrupt context.
--
commit a846f43d2e5b4bec36ac11b09237b5b9c9ebabb4
Author: Dmitry Antipov <dmantipov@yandex.ru>
HID: roccat: fix device access in roccat_release()
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.
Link: https://sashiko.dev/#/patchset/20260902094551.200587-1-dmantipov@yandex.ru?part=2
> diff --git a/drivers/hid/hid-roccat.c b/drivers/hid/hid-roccat.c
> index 5deb6da8d4f74..007778922c21a 100644
> --- a/drivers/hid/hid-roccat.c
> +++ b/drivers/hid/hid-roccat.c
[ ... ]
> int roccat_report_event(int minor, u8 const *data)
> {
> struct roccat_device *device;
> struct roccat_reader *reader;
> struct roccat_report *report;
> uint8_t *new_value;
>
> device = devices[minor];
>
> new_value = kmemdup(data, device->report_size, GFP_ATOMIC);
[Severity: High]
This isn't a bug introduced by this patch, but does roccat_report_event()
access devices[minor] locklessly and without checking for NULL?
If a device is disconnected, roccat_disconnect() sets devices[minor] = NULL
and drops the device reference. Because sub-drivers like hid-roccat-kone fail
to call hid_hw_stop() before roccat_disconnect(), incoming USB interrupts can
still trigger roccat_report_event() concurrently.
Could this lead to a NULL pointer dereference when kmemdup() accesses
device->report_size?
> if (!new_value)
> return -ENOMEM;
>
> mutex_lock(&device->readers_lock);
> mutex_lock(&device->cbuf_lock);
[Severity: High]
This is a pre-existing issue, but does this code acquire sleepable mutexes from
an atomic interrupt context?
roccat_report_event() is called by raw_event callbacks in HID sub-drivers,
which execute in atomic interrupt context (URB completion handlers like
usbhid_irq_in).
Can calling mutex_lock() here cause a "scheduling while atomic" panic, or
a deadlock if the lock is contended?
> drivers/hid/hid-roccat-kone.c:
> static void kone_remove_specials(struct hid_device *hdev)
> {
> struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
> struct kone_device *kone;
>
> if (intf->cur_altsetting->desc.bInterfaceProtocol
> == USB_INTERFACE_PROTOCOL_MOUSE) {
> kone = hid_get_drvdata(hdev);
> if (kone->roccat_claimed)
> roccat_disconnect(kone->chrdev_minor);
> kfree(hid_get_drvdata(hdev));
> }
> }
>
> static void kone_remove(struct hid_device *hdev)
> {
> kone_remove_specials(hdev);
> hid_hw_stop(hdev);
> }
[Severity: Critical]
This isn't a bug introduced by this patch, but do HID roccat sub-drivers free
their private driver data before calling hid_hw_stop()?
During device unbinding/removal, kone_remove_specials() is called which frees
the driver data with kfree(hid_get_drvdata(hdev)). However, hid_hw_stop() is
called after this free.
Since hid_hw_stop() is responsible for stopping hardware interrupts, an incoming
USB report can trigger kone_raw_event() concurrently:
drivers/hid/hid-roccat-kone.c:kone_raw_event() {
struct kone_device *kone = hid_get_drvdata(hdev);
...
if (kone == NULL)
return 0;
...
if (kone->roccat_claimed)
kone_report_to_chrdev(kone, event);
}
Can this result in a Use-After-Free memory corruption when the raw_event
callback dereferences the freed driver data?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260914120240.1625442-1-dmantipov@yandex.ru?part=2
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v2 3/4] HID: roccat: examine readers to check whether the device is opened
2026-09-14 12:02 ` [PATCH v2 3/4] HID: roccat: examine readers to check whether the device is opened Dmitry Antipov
@ 2026-09-14 12:53 ` sashiko-bot
0 siblings, 0 replies; 8+ messages in thread
From: sashiko-bot @ 2026-09-14 12:53 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] Caching `list_empty()` state outside `devices_lock` in `roccat_release()` creates a TOCTOU race with `roccat_open()` and `roccat_disconnect()`, leading to device state corruption and leaked hardware open counts.
--
commit 8f711f83c5a599f7dd523be3cd4112f138217007
Author: Dmitry Antipov <dmantipov@yandex.ru>
HID: roccat: examine readers to check whether the device is opened
This patch replaces the explicit open counter in struct roccat_device with
a check against list_empty(&device->readers). The goal is to determine if
active readers exist without maintaining a separate integer count.
> diff --git a/drivers/hid/hid-roccat.c b/drivers/hid/hid-roccat.c
> index 007778922c21a..d1df4ec34a1e6 100644
> --- a/drivers/hid/hid-roccat.c
> +++ b/drivers/hid/hid-roccat.c
[ ... ]
> @@ -213,18 +209,20 @@ static int roccat_release(struct inode *inode, struct file *file)
> {
> struct roccat_reader *reader = file->private_data;
> struct roccat_device *device = reader->device;
> + bool open;
>
> if (WARN_ON_ONCE(!device))
> return -ENODEV;
>
> mutex_lock(&device->readers_lock);
> list_del(&reader->node);
> + open = !list_empty(&device->readers);
> mutex_unlock(&device->readers_lock);
[Severity: High]
Does evaluating the list state here and dropping the lock introduce a race
condition?
If another thread calls roccat_open() before devices_lock is acquired below,
it will observe an empty list, power on the device, and add a new reader.
> kfree(reader);
>
> mutex_lock(&devices_lock);
>
> - if (!--device->open) {
> + if (!open) {
> /* removing last reader */
> if (device->exist) {
> hid_hw_power(device->hid, PM_HINT_NORMAL);
> hid_hw_close(device->hid);
> }
[Severity: High]
Since this uses the cached open flag, could it unconditionally power down and
close the hardware here, cutting off the newly added reader from the
concurrent roccat_open() call?
[ ... ]
> @@ -370,7 +369,11 @@ void roccat_disconnect(int minor)
>
> devices[minor] = NULL;
>
> - if (device->open) {
> + mutex_lock(&device->readers_lock);
> + open = !list_empty(&device->readers);
> + mutex_unlock(&device->readers_lock);
> +
> + if (open) {
> hid_hw_close(device->hid);
> wake_up_interruptible(&device->wait);
> } else {
[Severity: High]
Can a concurrent roccat_disconnect() and roccat_release() race here,
resulting in neither function closing the hardware?
If roccat_release() removes the last reader, it evaluates the local open
variable to false. If roccat_disconnect() then acquires readers_lock, it will
also evaluate open to false since the list is already empty, and it will skip
calling hid_hw_close().
When roccat_release() subsequently acquires devices_lock, it observes that
device->exist is 0 (set earlier by roccat_disconnect()) and also skips calling
hid_hw_close().
Will this result in a permanent leak of the HID hardware open count?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260914120240.1625442-1-dmantipov@yandex.ru?part=3
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v2 4/4] HID: roccat: use kref to manage device instances
2026-09-14 12:02 ` [PATCH v2 4/4] HID: roccat: use kref to manage device instances Dmitry Antipov
@ 2026-09-14 13:07 ` sashiko-bot
0 siblings, 0 replies; 8+ messages in thread
From: sashiko-bot @ 2026-09-14 13:07 UTC (permalink / raw)
To: Dmitry Antipov; +Cc: linux-input, dmitry.torokhov
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
New issues:
- [High] A TOCTOU race condition between `roccat_release` and `roccat_open` causes a new reader's device to be incorrectly powered down and closed.
Pre-existing issues:
- [High] Data path UAF and NULL pointer dereference in `roccat_report_event` due to lockless device access.
- [High] Custom Roccat drivers violate HID subsystem lifecycle rules by freeing their private `drvdata` before stopping the hardware, causing a UAF in their `raw_event` handlers.
- [High] A race condition between `roccat_release` and `roccat_disconnect` causes `hid_hw_close()` and `hid_hw_power()` to be skipped entirely, permanently leaking `ll_open_count` and power references.
--
commit 0e2654cfadc18d56d9b77ac333344436bcca55c7
Author: Dmitry Antipov <dmantipov@yandex.ru>
HID: roccat: use kref to manage device instances
Use kref to manage 'struct roccat_device' instances and fix
UaF-triggering race between roccat_open()/roccat_release()
and roccat_connect()/roccat_disconnect() pairs.
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
> diff --git a/drivers/hid/hid-roccat.c b/drivers/hid/hid-roccat.c
> index d1df4ec34a1e6..32927b20bcfe0 100644
> --- a/drivers/hid/hid-roccat.c
> +++ b/drivers/hid/hid-roccat.c
[ ... ]
> @@ -227,9 +231,8 @@ static int roccat_release(struct inode *inode, struct file *file)
> if (device->exist) {
> 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);
[Severity: High]
Can a newly opened reader's device be incorrectly closed and powered down due
to a TOCTOU race?
Looking at roccat_release(), the open status is determined early while holding
device->readers_lock. A concurrent roccat_open() can then observe the empty
list, power on the device, and add its reader node.
When roccat_release() later acquires devices_lock, it acts on the stale open
variable (which is false):
CPU1: roccat_release()
mutex_lock(&device->readers_lock);
open = !list_empty(&device->readers); // false
mutex_unlock(&device->readers_lock);
CPU2: roccat_open()
mutex_lock(&devices_lock);
mutex_lock(&device->readers_lock);
// list is empty, power on and open hardware
hid_hw_power(device->hid, PM_HINT_FULLON);
...
mutex_unlock(&device->readers_lock);
mutex_unlock(&devices_lock);
CPU1: roccat_release() (resuming)
mutex_lock(&devices_lock);
if (!open) {
hid_hw_power(device->hid, PM_HINT_NORMAL);
hid_hw_close(device->hid);
}
Does this clobber the concurrent open and leave the newly connected reader with
a powered down, closed hardware state?
> @@ -362,6 +366,8 @@ void roccat_disconnect(int minor)
>
> mutex_lock(&devices_lock);
> device = devices[minor];
> + if (!device)
> + goto out;
[Severity: High]
This isn't a bug introduced by this patch, but does roccat_report_event() risk
a NULL pointer dereference or use-after-free because it accesses devices[minor]
locklessly?
When the device is unplugged, roccat_disconnect() sets devices[minor] = NULL
and drops the kref. Since the transport hardware hasn't been stopped yet (due
to the delayed hid_hw_close), incoming USB events can concurrently trigger
kone_raw_event(), which calls into roccat_report_event():
drivers/hid/hid-roccat.c:roccat_report_event() {
...
device = devices[minor];
new_value = kmemdup(data, device->report_size, GFP_ATOMIC);
...
}
If devices[minor] is read as NULL here, accessing device->report_size will
trigger a panic. If it is read right before kref_put frees it, does this result
in a use-after-free, defeating the intent of the kref fix?
> @@ -376,10 +382,10 @@ void roccat_disconnect(int minor)
> if (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);
> }
[Severity: High]
This is a pre-existing issue, but can a race between roccat_release() and
roccat_disconnect() permanently leak the ll_open_count and power references by
skipping hid_hw_close() entirely?
If roccat_disconnect() sets device->exist = 0 concurrently with the last reader
calling roccat_release(), both paths appear to bypass the mandatory cleanup:
CPU1: roccat_disconnect()
device->exist = 0;
// open is false because reader is removing itself
if (open) { ... } // skipped
CPU2: roccat_release()
// open is false
if (!open) {
if (device->exist) {
// skipped because exist is 0
hid_hw_close(device->hid);
}
}
Does this leave the hardware permanently active and prevent proper unbinding?
While reviewing the UAF fixes in the roccat driver, I noticed another
pre-existing UAF in the kone driver's lifecycle handling:
[Severity: High]
This isn't a bug introduced by this patch, but does the kone driver violate
HID lifecycle rules by freeing its private driver data before stopping the
hardware?
Because kfree(hid_get_drvdata(hdev)) is called before hid_hw_stop(hdev),
incoming USB events can still be delivered during this window.
drivers/hid/hid-roccat-kone.c:kone_remove_specials() {
...
kfree(hid_get_drvdata(hdev));
...
}
drivers/hid/hid-roccat-kone.c:kone_remove() {
kone_remove_specials(hdev);
hid_hw_stop(hdev);
}
If an interrupt arrives before hid_hw_stop(), kone_raw_event() accesses the
freed kone structure:
drivers/hid/hid-roccat-kone.c:kone_raw_event() {
struct kone_device *kone = hid_get_drvdata(hdev);
struct kone_mouse_event *event = (struct kone_mouse_event *)data;
...
if (memcmp(&kone->last_mouse_event.tilt, &event->tilt, 5))
...
}
Can this use-after-free be triggered locally by unplugging the device while
USB events are active?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260914120240.1625442-1-dmantipov@yandex.ru?part=4
^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2026-09-14 13:07 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-14 12:02 [PATCH v2 1/4] HID: roccat: fix locking in roccat_connect() and roccat_disconnect() Dmitry Antipov
2026-09-14 12:02 ` [PATCH v2 2/4] HID: roccat: fix device access in roccat_release() Dmitry Antipov
2026-09-14 12:44 ` sashiko-bot
2026-09-14 12:02 ` [PATCH v2 3/4] HID: roccat: examine readers to check whether the device is opened Dmitry Antipov
2026-09-14 12:53 ` sashiko-bot
2026-09-14 12:02 ` [PATCH v2 4/4] HID: roccat: use kref to manage device instances Dmitry Antipov
2026-09-14 13:07 ` sashiko-bot
2026-09-14 12:26 ` [PATCH v2 1/4] HID: roccat: fix locking in roccat_connect() and roccat_disconnect() sashiko-bot
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.