mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH 1/2] HID: sensor: custom: Fix use-after-free when the node is open across removal
@ 2026-11-06 12:49 Chad Aldreda
  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
  0 siblings, 2 replies; 3+ messages in thread
From: Chad Aldreda @ 2026-11-06 12:49 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires
  Cc: Jonathan Cameron, Srinivas Pandruvada, linux-input, linux-iio,
	linux-kernel

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



^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2026-11-06 12:56 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-06 12:49 [PATCH 1/2] HID: sensor: custom: Fix use-after-free when the node is open across removal Chad Aldreda
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

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®