[PATCH 1/2] HID: sensor: custom: Fix use-after-free when the node is open across removal

From: Chad Aldreda

Date: Fri Nov 06 2026 - 07:54:23 EST


hid_sensor_custom_probe() allocates its state, which embeds the
miscdevice, with devm_kzalloc(). When the sensor hub goes away, for
example on USB disconnect, remove() deregisters the miscdevice and
devres frees the state, but a file that is still open keeps
file->private_data pointing into it: release() then writes the freed
misc_opened bit, and a reader blocked in read() is woken by remove()
but finds the kfifo empty and goes back to sleep on a wait queue that
devres then frees.

Reference count the state: probe and every open hold a reference, and
the last one frees the kfifo and the state. remove() marks the state
removed before waking readers, so a blocked or later read returns
-ENODEV and poll() reports EPOLLHUP, and the kfifo is no longer freed
before the hub callback is unregistered.

Found with a coccinelle check for device nodes embedded in devm_
memory. Reproduced in QEMU with a uhid program that creates a sensor
hub with one custom-sensor collection, opens the misc node, blocks a
reader in read(), destroys the uhid device and closes the node. Before
this change KASAN reports nine slab-use-after-frees: eight in
hid_sensor_custom_read() and the wait-queue code when the stuck reader
is finally killed, and a write in hid_sensor_custom_release(). The
reader stays blocked until killed. After it there are no reports and
the blocked read() returns -ENODEV. Not tested on real sensor hub
hardware, and the known-custom-sensor path that registers a separate
platform device was not exercised.

Fixes: 4a7de0519df5 ("HID: sensor: Custom and Generic sensor support")
Cc: stable@xxxxxxxxxxxxxxx
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Chad Aldreda <ca@xxxxxxxxxxxxxx>
---
drivers/hid/hid-sensor-custom.c | 45 ++++++++++++++++++++++++++++-----
1 file changed, 38 insertions(+), 7 deletions(-)

diff --git a/drivers/hid/hid-sensor-custom.c b/drivers/hid/hid-sensor-custom.c
index c2b425afd9..47800efdaf 100644
--- a/drivers/hid/hid-sensor-custom.c
+++ b/drivers/hid/hid-sensor-custom.c
@@ -15,6 +15,7 @@
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/bsearch.h>
+#include <linux/kref.h>
#include <linux/platform_device.h>
#include <linux/hid-sensor-hub.h>

@@ -54,8 +55,25 @@ struct hid_sensor_custom {
unsigned long misc_opened;
wait_queue_head_t wait;
struct platform_device *custom_pdev;
+ /* Held by the driver binding and by each open file. */
+ struct kref kref;
+ bool removed;
};

+static void hid_sensor_custom_free(struct kref *kref)
+{
+ struct hid_sensor_custom *sensor_inst =
+ container_of(kref, struct hid_sensor_custom, kref);
+
+ kfifo_free(&sensor_inst->data_fifo);
+ kfree(sensor_inst);
+}
+
+static void hid_sensor_custom_put(struct hid_sensor_custom *sensor_inst)
+{
+ kref_put(&sensor_inst->kref, hid_sensor_custom_free);
+}
+
/* Header for each sample to user space via dev interface */
struct hid_sensor_sample {
u32 usage_id;
@@ -658,11 +676,14 @@ static ssize_t hid_sensor_custom_read(struct file *file, char __user *buf,

do {
if (kfifo_is_empty(&sensor_inst->data_fifo)) {
+ if (READ_ONCE(sensor_inst->removed))
+ return -ENODEV;
if (file->f_flags & O_NONBLOCK)
return -EAGAIN;

ret = wait_event_interruptible(sensor_inst->wait,
- !kfifo_is_empty(&sensor_inst->data_fifo));
+ !kfifo_is_empty(&sensor_inst->data_fifo) ||
+ READ_ONCE(sensor_inst->removed));
if (ret)
return ret;
}
@@ -684,6 +705,7 @@ static int hid_sensor_custom_release(struct inode *inode, struct file *file)
struct hid_sensor_custom, custom_dev);

clear_bit(0, &sensor_inst->misc_opened);
+ hid_sensor_custom_put(sensor_inst);

return 0;
}
@@ -698,6 +720,8 @@ static int hid_sensor_custom_open(struct inode *inode, struct file *file)
if (test_and_set_bit(0, &sensor_inst->misc_opened))
return -EBUSY;

+ kref_get(&sensor_inst->kref);
+
return stream_open(inode, file);
}

@@ -714,6 +738,8 @@ static __poll_t hid_sensor_custom_poll(struct file *file,

if (!kfifo_is_empty(&sensor_inst->data_fifo))
mask = EPOLLIN | EPOLLRDNORM;
+ else if (READ_ONCE(sensor_inst->removed))
+ mask = EPOLLHUP | EPOLLERR;

return mask;
}
@@ -751,10 +777,9 @@ static int hid_sensor_custom_dev_if_add(struct hid_sensor_custom *sensor_inst)
static void hid_sensor_custom_dev_if_remove(struct hid_sensor_custom
*sensor_inst)
{
+ WRITE_ONCE(sensor_inst->removed, true);
wake_up(&sensor_inst->wait);
misc_deregister(&sensor_inst->custom_dev);
- kfifo_free(&sensor_inst->data_fifo);
-
}

/*
@@ -977,11 +1002,12 @@ static int hid_sensor_custom_probe(struct platform_device *pdev)
int ret;
const struct hid_sensor_custom_match *match;

- sensor_inst = devm_kzalloc(&pdev->dev, sizeof(*sensor_inst),
- GFP_KERNEL);
+ sensor_inst = kzalloc_obj(*sensor_inst);
if (!sensor_inst)
return -ENOMEM;

+ kref_init(&sensor_inst->kref);
+
sensor_inst->callbacks.capture_sample = hid_sensor_capture_sample;
sensor_inst->callbacks.send_event = hid_sensor_send_event;
sensor_inst->callbacks.pdev = pdev;
@@ -999,7 +1025,7 @@ static int hid_sensor_custom_probe(struct platform_device *pdev)
if (ret) {
dev_err(&pdev->dev,
"register_platform_device failed\n");
- return ret;
+ goto err_put;
}

return 0;
@@ -1009,7 +1035,7 @@ static int hid_sensor_custom_probe(struct platform_device *pdev)
&sensor_inst->callbacks);
if (ret < 0) {
dev_err(&pdev->dev, "callback reg failed\n");
- return ret;
+ goto err_put;
}

ret = hid_sensor_custom_add_attributes(sensor_inst);
@@ -1034,6 +1060,8 @@ static int hid_sensor_custom_probe(struct platform_device *pdev)
hid_sensor_custom_remove_attributes(sensor_inst);
err_remove_callback:
sensor_hub_remove_callback(hsdev, hsdev->usage);
+err_put:
+ hid_sensor_custom_put(sensor_inst);

return ret;
}
@@ -1045,6 +1073,7 @@ static void hid_sensor_custom_remove(struct platform_device *pdev)

if (sensor_inst->custom_pdev) {
platform_device_unregister(sensor_inst->custom_pdev);
+ hid_sensor_custom_put(sensor_inst);
return;
}

@@ -1054,6 +1083,8 @@ static void hid_sensor_custom_remove(struct platform_device *pdev)
&enable_sensor_attr_group);
hid_sensor_custom_remove_attributes(sensor_inst);
sensor_hub_remove_callback(hsdev, hsdev->usage);
+ /* An open file may still hold the last reference. */
+ hid_sensor_custom_put(sensor_inst);
}

static const struct platform_device_id hid_sensor_custom_ids[] = {
--
2.43.0