From: Chad Aldreda <ca@modelmiser.com>
To: Jiri Kosina <jikos@kernel.org>, Benjamin Tissoires <bentiss@kernel.org>
Cc: Jonathan Cameron <jic23@kernel.org>,
Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>,
linux-input@vger.kernel.org, linux-iio@vger.kernel.org,
linux-kernel@vger.kernel.org
Subject: [PATCH 1/2] HID: sensor: custom: Fix use-after-free when the node is open across removal
Date: Fri, 06 Nov 2026 12:49:36 +0000 [thread overview]
Message-ID: <20261106124914.2892604-1-ca@modelmiser.com> (raw)
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@vger.kernel.org
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Chad Aldreda <ca@modelmiser.com>
---
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
next reply other threads:[~2026-11-06 12:49 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-06 12:49 Chad Aldreda [this message]
2026-11-06 12:49 ` [PATCH 2/2] HID: sensor: custom: Set the owner of the misc file operations Chad Aldreda
2026-11-06 12:56 ` [PATCH 1/2] HID: sensor: custom: Fix use-after-free when the node is open across removal Chad Aldreda
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=20261106124914.2892604-1-ca@modelmiser.com \
--to=ca@modelmiser.com \
--cc=bentiss@kernel.org \
--cc=jic23@kernel.org \
--cc=jikos@kernel.org \
--cc=linux-iio@vger.kernel.org \
--cc=linux-input@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=srinivas.pandruvada@linux.intel.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®