Thread (8 messages) 8 messages, 3 authors, 14d ago

Re: [PATCH v6 1/3] HID: lamparray: add new LampArray helper module

From: sashiko-bot@kernel.org
Date: 2026-09-16 15:00:29

Thank you for your contribution! Sashiko AI review found 8 potential issue(s) to consider:
- [High] hid: lamparray: out-of-bounds access on field->value array
- [Medium] hid: lamparray: mixing goto and scope-based cleanup
- [Medium] hid: lamparray: silent state update failures due to unverified shared reports
- [Medium] hid: lamparray: TOCTOU race in unregister leaves hardware in wrong state
- [Medium] hid: lamparray: error path fails to revert autonomous mode
- [Medium] hid: lamparray: data race on LED class device state
- [Medium] hid: lamparray: initialization race due to early sysfs exposure
- [Low] leds: lamparray: prints success log messages
--

commit dcca692c120b5bda2e0a125483a058dc7b60dad2
Author: Aaron Erhardt [off-list ref]

HID: lamparray: add new LampArray helper module

Add a new hid-lamparray helper module that provides basic support for
devices exposing a Lighting/LampArray application collection. It registers a
single-zone RGB LED representation via the LED subsystem.
quoted hunk ↗ jump to hunk
diff --git a/drivers/hid/hid-lamparray.c b/drivers/hid/hid-lamparray.c
--- /dev/null
+++ b/drivers/hid/hid-lamparray.c
[ ... ]
+static int lamparray_read_attributes_report(struct lamparray_device *ldev)
+{
+	struct hid_device *hdev = ldev->hdev;
+	struct hid_report *report;
+	int ret;
+	size_t len;
+
+	if (!ldev->lamp_count.field) {
+		hid_warn(hdev, "No LampCount field found\n");
+		return -ENODEV;
+	}
+
+	if (!ldev->lamparray_kind.field) {
+		hid_warn(hdev, "No LampArrayKind field found\n");
+		return -ENODEV;
+	}
+
+	report = ldev->lamp_count.field->report;
+
+	if (!report) {
[Severity: Medium]
Can lamparray_kind read stale or zeroed memory here? By only fetching the
report associated with lamp_count, the lamparray_kind field may not be
updated if it resides in a different HID report than lamp_count.
+		hid_warn(hdev, "LampCount field has no report\n");
+		return -ENODEV;
+	}
[ ... ]
+static int lamparray_parse_update_report(struct lamparray_device *ldev)
+{
+	struct hid_device *hdev = ldev->hdev;
+	struct hid_report_enum *re;
+	struct hid_report *report;
+	struct hid_field *field;
+	int i, j;
+	int ret = 0;
+
+	re = &hdev->report_enum[HID_FEATURE_REPORT];
+
+	list_for_each_entry(report, &re->report_list, list) {
+		for (i = 0; i < report->maxfield; i++) {
+			field = report->field[i];
+			if (!field)
+				continue;
+
+			if (!field->usage || !field->maxusage)
+				continue;
+
+			for (j = 0; j < field->maxusage; j++) {
+				u32 usage = field->usage[j].hid;
[Severity: High]
Could this loop overflow the field->value array? The loop iterates up to
field->maxusage, storing the loop index in the field_value structs.
However, the field->value array is allocated by the HID core based on
field->report_count. If a crafted HID descriptor specifies a maxusage
larger than report_count, the subsequent reads and writes in
get_field_value() and set_field_value() can overflow the array.
+				u32 collection_idx = field->usage[j].collection_index;
+				u32 collection_usage = hdev->collection[collection_idx].usage;
[ ... ]
+static int lamparray_hw_set_state(struct lamparray_device *ldev, u8 r, u8 g,
+				  u8 b, u8 intensity)
+{
+	struct hid_device *hdev = ldev->hdev;
+	struct hid_report *report;
+
+	if (!lamparray_color_fields_is_complete(&ldev->color_update))
+		return -ENODEV;
+
+	if (ldev->range_start.field && ldev->range_end.field) {
+		set_field_value(&ldev->range_start, 0);
+		set_field_value(&ldev->range_end, ldev->lamp_count_value - 1);
+	}
+
+	set_field_value(&ldev->color_update.red, r);
+	set_field_value(&ldev->color_update.green, g);
+	set_field_value(&ldev->color_update.blue, b);
+	set_field_value(&ldev->color_update.intensity, intensity);
+
+	report = ldev->color_update.red.field->report;
+	hid_hw_request(hdev, report, HID_REQ_SET_REPORT);
+
+	return 0;
[Severity: Medium]
Does this drop updates for the other color channels and range fields? If
green, blue, intensity, or the range fields reside in a different report
than the red field, sending only color_update.red.field->report will skip
their updates.
+}
[ ... ]
+static int lamparray_restore_state(struct lamparray_device *ldev)
+{
+	u8 r, g, b;
+	int ret;
+	enum led_brightness brightness;
+
+	if (!ldev->use_leds_uapi)
+		return 0;
+
+	guard(mutex)(&ldev->dev_lock);
+
+	r = ldev->last_r;
+	g = ldev->last_g;
+	b = ldev->last_b;
+	brightness = ldev->last_brightness;
+
+	ldev->mc_cdev.subled_info[0].intensity = r;
+	ldev->mc_cdev.subled_info[1].intensity = g;
+	ldev->mc_cdev.subled_info[2].intensity = b;
+	ldev->mc_cdev.led_cdev.brightness = brightness;
[Severity: Medium]
Can these updates race with concurrent sysfs writes? Modifying these LED
class device fields without holding the led_cdev->led_access mutex while
the sysfs interface is active can cause inconsistent brightness or color
states if userspace writes to them simultaneously.
+
+	/*
+	 * Quirk for devices ignoring intensity values, see
+	 * lamparray_led_brightness_set for more information.
+	 */
[ ... ]
+static ssize_t use_leds_uapi_store(struct device *dev,
+				   struct device_attribute *attr,
+				   const char *buf, size_t count)
+{
+	struct lamparray_device *ldev = lamparray_ldev_from_sysfs_dev(dev);
+	bool val, old_val;
+	int ret;
+
+	if (!ldev)
+		return -ENODEV;
+
+	ret = kstrtobool(buf, &val);
+	if (ret)
+		return ret;
+
+	/* Make sure only one concurrent write is possible */
+	guard(mutex)(&ldev->sysfs_lock);
+	old_val = ldev->use_leds_uapi;
+
+	if (val == old_val)
+		return count;
+
+	ldev->use_leds_uapi = val;
+
+	/*
+	 * Take the device out of autonomous mode before exposing the LED
+	 * class device, and hand it back afterwards, so that control is
+	 * transferred in both directions rather than only at probe.
+	 */
+	if (val == 1) {
+		ret = lamparray_hw_set_autonomous(ldev, false);
+		if (ret)
+			goto err_revert;
[Severity: Medium]
Does this function mix goto-based error handling with scope-based cleanup?
The cleanup subsystem guidelines request converting all resources to
scope-based cleanup if using __free() or guard(). Mixing the paradigms in
functions like lamparray_register() and use_leds_uapi_store() can create
ownership semantics confusion.
+		ret = lamparray_register_led(ldev);
+		if (ret) {
+			lamparray_hw_set_autonomous(ldev, true);
+			goto err_revert;
[ ... ]
+struct lamparray *
+lamparray_register(struct hid_device *hdev,
+		   const struct lamparray_init_state *led_init_state)
+{
+	int ret;
+	struct lamparray_device *ldev;
+
+	if (!hdev)
+		return ERR_PTR(-ENODEV);
+
+	struct lamparray *la __free(kfree) = kzalloc_obj(*la, GFP_KERNEL);
+	if (!la)
+		return ERR_PTR(-ENOMEM);
+
+	ldev = &la->ldev;
[ ... ]
+	ret = xa_err(xa_store(&lamparray_by_hdev, (unsigned long)hdev, ldev,
+			      GFP_KERNEL));
+	if (ret)
+		goto err_unregister_led;
+
+	ret = lamparray_register_sysfs(ldev);
+	if (ret)
+		goto err_xa_erase;
+
+	ret = lamparray_hw_set_autonomous(ldev, false);
[Severity: Medium]
Could exposing the sysfs file before hardware initialization completes
create a race condition? If userspace writes 0 to use_leds_uapi right
after this call, the store handler will unregister the LED and set
autonomous to true. This probe thread then resumes, unconditionally setting
autonomous to false and skipping lamparray_restore_state(), leaving the
device in a broken state.
+	if (ret) {
+		hid_err(hdev, "Could not disable autonomous mode: %d", ret);
+		goto err_remove_sysfs;
+	}
+
+	hid_info(hdev, "LampArray device registered\n");
[Severity: Low]
Should this log message be removed? The LED subsystem guidelines specify
that drivers should only log errors or warnings, avoiding success messages
on probe.
+
+	ret = lamparray_restore_state(ldev);
+	if (ret) {
+		hid_err(hdev, "Failed to set default state: %d", ret);
+		goto err_remove_sysfs;
+	}
[Severity: Medium]
Does this error path leave the hardware in a non-autonomous state? If
lamparray_restore_state() fails, the code jumps to err_remove_sysfs and
cleans up software state, but does not revert the device back to autonomous
mode.
+
+	return_ptr(la);
+
+err_remove_sysfs:
+	lamparray_remove_sysfs(ldev);
+err_xa_erase:
+	xa_erase(&lamparray_by_hdev, (unsigned long)hdev);
[ ... ]
+void lamparray_unregister(struct lamparray *la)
+{
+	struct lamparray_device *ldev;
+
+	if (!la)
+		return;
+
+	ldev = &la->ldev;
+
+	lamparray_hw_set_autonomous(ldev, true);
+
+	lamparray_remove_sysfs(ldev);
[Severity: Medium]
Can this sequence permanently leave the hardware in a non-autonomous state?
Calling lamparray_hw_set_autonomous(..., true) before removing the sysfs
interface creates a window where a concurrent sysfs write to use_leds_uapi
can switch the device back to non-autonomous mode right before the device
is completely unregistered.
+	xa_erase(&lamparray_by_hdev, (unsigned long)ldev->hdev);
+
+	mutex_lock(&ldev->sysfs_lock);
+	lamparray_unregister_led(ldev);
-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260916144838.456239-1-aer@tuxedocomputers.com?part=1
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help