[PATCH v2 4/4] leds: trigger: netdev: Serialize mode/interval stores with trigger lock
From: A. Sverdlin
Date: Mon Sep 21 2026 - 05:43:15 EST
From: Alexander Sverdlin <alexander.sverdlin@xxxxxxxxxxx>
netdev_led_attr_store() and interval_store() update ->mode and run
set_baseline_state() without trigger_data->lock. kernfs only serializes
writes to the same file, so two attribute writes race on the non-atomic
read-modify-write of ->mode:
CPU0 (echo 1 > link_10) CPU1 (echo 1 > link_100)
----------------------- ------------------------
mode = trigger_data->mode;
mode = trigger_data->mode;
set_bit(LINK_10, &mode);
set_bit(LINK_100, &mode);
trigger_data->mode = mode;
trigger_data->mode = mode; // LINK_10 lost
They also race the notifier's link-state and ->hw_control updates.
Take trigger_data->lock in both stores. netdev_trig_work() also holds it,
so use the async cancel_delayed_work() (a sync cancel would deadlock).
Cc: stable@xxxxxxxxxxxxxxx
Fixes: d5e01266e7f5 ("leds: trigger: netdev: add additional specific link speed mode")
Signed-off-by: Alexander Sverdlin <alexander.sverdlin@xxxxxxxxxxx>
---
Changelog:
v2:
- reduced verbosity both in the comments and in commit message
- dropped sync from cancel_delayed_work() (worker now takes the lock)
drivers/leds/trigger/ledtrig-netdev.c | 14 +++++++++++---
1 file changed, 11 insertions(+), 3 deletions(-)
diff --git a/drivers/leds/trigger/ledtrig-netdev.c b/drivers/leds/trigger/ledtrig-netdev.c
index 54c6913f3afe3..92a6a456130c9 100644
--- a/drivers/leds/trigger/ledtrig-netdev.c
+++ b/drivers/leds/trigger/ledtrig-netdev.c
@@ -385,7 +385,7 @@ static ssize_t netdev_led_attr_store(struct device *dev, const char *buf,
{
struct led_netdev_data *trigger_data = led_trigger_get_drvdata(dev);
struct led_classdev *led_cdev = trigger_data->led_cdev;
- unsigned long state, mode = trigger_data->mode;
+ unsigned long state, mode;
int ret;
int bit;
@@ -417,6 +417,10 @@ static ssize_t netdev_led_attr_store(struct device *dev, const char *buf,
return -EINVAL;
}
+ /* async cancel: the worker takes this lock */
+ guard(mutex)(&trigger_data->lock);
+
+ mode = trigger_data->mode;
if (state)
set_bit(bit, &mode);
else
@@ -435,7 +439,7 @@ static ssize_t netdev_led_attr_store(struct device *dev, const char *buf,
test_bit(TRIGGER_NETDEV_LINK_100000, &mode)))
return -EINVAL;
- cancel_delayed_work_sync(&trigger_data->work);
+ cancel_delayed_work(&trigger_data->work);
trigger_data->mode = mode;
trigger_data->hw_control = can_hw_control(trigger_data);
@@ -506,10 +510,14 @@ static ssize_t interval_store(struct device *dev,
/* impose some basic bounds on the timer interval */
if (value >= 5 && value <= 10000) {
- cancel_delayed_work_sync(&trigger_data->work);
+ mutex_lock(&trigger_data->lock);
+
+ cancel_delayed_work(&trigger_data->work);
atomic_set(&trigger_data->interval, msecs_to_jiffies(value));
set_baseline_state(trigger_data); /* resets timer */
+
+ mutex_unlock(&trigger_data->lock);
}
return size;
--
2.55.0