All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v5 0/4] leds: trigger: netdev: fix sysfs_update_group() races
@ 2026-10-20 12:46 A. Sverdlin
  2026-10-20 12:46 ` [PATCH v5 1/4] leds: trigger: netdev: Put netdev on activate error path A. Sverdlin
                   ` (3 more replies)
  0 siblings, 4 replies; 10+ messages in thread
From: A. Sverdlin @ 2026-10-20 12:46 UTC (permalink / raw)
  To: linux-leds
  Cc: Alexander Sverdlin, Lee Jones, Pavel Machek, Andrew Lunn,
	Mike Marciniszyn (Meta), Jakub Kicinski, Kees Cook,
	Marek Behún, Christian Marangi, linux-kernel

From: Alexander Sverdlin <alexander.sverdlin@siemens.com>

The netdev LED trigger refreshes the link_speed attribute group with
sysfs_update_group() from several contexts (the NETDEV_CHANGE notifier,
device_name writes and, indirectly, trigger (de)activation) that share no
common lock. On a board that emits PHY link events while the trigger is
being (re)armed during boot this is observed as a hard sysfs failure:

  sysfs: cannot create duplicate filename '...green:lan/link_10'
  ...
  led_trigger_set
  led_trigger_write

The window between activate() and device_add_groups() in led_trigger_set()
is narrow, so to reproduce it reliably I artificially widened it with the
debug patch below:

--- a/drivers/leds/led-triggers.c
+++ b/drivers/leds/led-triggers.c
@@ -12,6 +12,7 @@
 #include <linux/list.h>
 #include <linux/spinlock.h>
 #include <linux/device.h>
+#include <linux/delay.h>
 #include <linux/timer.h>
 #include <linux/rwsem.h>
 #include <linux/leds.h>
@@ -223,6 +224,8 @@ int led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trig)
 		if (ret)
 			goto err_activate;

+		msleep(5000);
+
 		ret = device_add_groups(led_cdev->dev, trig->groups);
 		if (ret) {
 			dev_err(led_cdev->dev, "Failed to add trigger attributes\n");

With that msleep() in place, writing the trigger while a NETDEV_CHANGE is
delivered from the linkwatch workqueue reproduces the duplicate-filename
splat every time.

The whole series is LOCKDEP-verified.

Changelog:
v5:
- removed unset_device_name()
v4:
- introduced netdev_trig_sw_blink() and used it in netdev_trig_work()
- https://lore.kernel.org/all/20261001131206.2513248-1-alexander.sverdlin@siemens.com/
v3:
- introduced unset_device_name() as a counterpart of set_device_name()
- guard() has been converted to either explicit mutex_lock()/mutex_unlock()
  or scoped_guard()
- rebased onto next-20260925
- https://lore.kernel.org/all/20260929120902.2793138-1-alexander.sverdlin@siemens.com/
v2:
- addressed pre-existing issues found by Sashiko (patches 1&2)
- reduced verbosity both in comments and in commit messages
- patch 3 is a combined rework of patches 1&2 from v1
- dropped sync from cancel_delayed_work() (worker now takes the lock)
- https://lore.kernel.org/all/20260921093249.3481413-1-alexander.sverdlin@siemens.com/
v1:
- https://lore.kernel.org/all/20260914132356.807004-1-alexander.sverdlin@siemens.com/


Alexander Sverdlin (4):
  leds: trigger: netdev: Put netdev on activate error path
  leds: trigger: netdev: Access net_dev under trigger_data->lock in the
    worker
  leds: trigger: netdev: Fix sysfs_update_group() races
  leds: trigger: netdev: Serialize mode/interval stores with trigger
    lock

 drivers/leds/trigger/ledtrig-netdev.c | 123 ++++++++++++++++++--------
 1 file changed, 85 insertions(+), 38 deletions(-)

-- 
2.56.0


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

* [PATCH v5 1/4] leds: trigger: netdev: Put netdev on activate error path
  2026-10-20 12:46 [PATCH v5 0/4] leds: trigger: netdev: fix sysfs_update_group() races A. Sverdlin
@ 2026-10-20 12:46 ` A. Sverdlin
  2026-10-20 12:51   ` sashiko-bot
  2026-10-20 12:46 ` [PATCH v5 2/4] leds: trigger: netdev: Access net_dev under trigger_data->lock in the worker A. Sverdlin
                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 10+ messages in thread
From: A. Sverdlin @ 2026-10-20 12:46 UTC (permalink / raw)
  To: linux-leds
  Cc: Alexander Sverdlin, Lee Jones, Pavel Machek, Andrew Lunn,
	Mike Marciniszyn (Meta), Jakub Kicinski, Kees Cook,
	Marek Behún, Christian Marangi, linux-kernel, stable

From: Alexander Sverdlin <alexander.sverdlin@siemens.com>

When hw control is active, set_device_name() takes a reference on the
netdev via dev_get_by_name(). The register_netdevice_notifier() failure
path frees trigger_data without dropping it, leaking the netdev.

Cc: stable@vger.kernel.org
Closes: https://lore.kernel.org/all/20260914140045.B8F4C1F000FF@smtp.kernel.org/
Fixes: 0316cc5629d1 ("leds: trigger: netdev: init mode if hw control already active")
Signed-off-by: Alexander Sverdlin <alexander.sverdlin@siemens.com>
---
Changelog:
v5:
- removed unset_device_name()
v3:
- introduced unset_device_name() as a counterpart of set_device_name()

 drivers/leds/trigger/ledtrig-netdev.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/drivers/leds/trigger/ledtrig-netdev.c b/drivers/leds/trigger/ledtrig-netdev.c
index 5b0132484594c..cfaf130157b3d 100644
--- a/drivers/leds/trigger/ledtrig-netdev.c
+++ b/drivers/leds/trigger/ledtrig-netdev.c
@@ -776,8 +776,10 @@ static int netdev_trig_activate(struct led_classdev *led_cdev)
 	led_set_trigger_data(led_cdev, trigger_data);
 
 	rc = register_netdevice_notifier(&trigger_data->notifier);
-	if (rc)
+	if (rc) {
+		dev_put(trigger_data->net_dev);
 		kfree(trigger_data);
+	}
 
 	return rc;
 }
-- 
2.56.0


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

* [PATCH v5 2/4] leds: trigger: netdev: Access net_dev under trigger_data->lock in the worker
  2026-10-20 12:46 [PATCH v5 0/4] leds: trigger: netdev: fix sysfs_update_group() races A. Sverdlin
  2026-10-20 12:46 ` [PATCH v5 1/4] leds: trigger: netdev: Put netdev on activate error path A. Sverdlin
@ 2026-10-20 12:46 ` A. Sverdlin
  2026-10-20 13:02   ` sashiko-bot
  2026-10-20 12:46 ` [PATCH v5 3/4] leds: trigger: netdev: Fix sysfs_update_group() races A. Sverdlin
  2026-10-20 12:46 ` [PATCH v5 4/4] leds: trigger: netdev: Serialize mode/interval stores with trigger lock A. Sverdlin
  3 siblings, 1 reply; 10+ messages in thread
From: A. Sverdlin @ 2026-10-20 12:46 UTC (permalink / raw)
  To: linux-leds
  Cc: Alexander Sverdlin, Lee Jones, Pavel Machek, Andrew Lunn,
	Mike Marciniszyn (Meta), Jakub Kicinski, Kees Cook,
	Marek Behún, Christian Marangi, linux-kernel, stable

From: Alexander Sverdlin <alexander.sverdlin@siemens.com>

netdev_trig_work() dereferences trigger_data->net_dev without the lock,
while set_device_name() and netdev_trig_notify() dev_put() and replace it
under trigger_data->lock. On NETDEV_UNREGISTER the worker can run
dev_get_stats() on a netdev being freed (UAF).

Take trigger_data->lock in the worker. cancel_delayed_work_sync() must
then never run under that lock.

Cc: stable@vger.kernel.org
Closes: https://lore.kernel.org/all/20260914142116.6DAC51F000FF@smtp.kernel.org/
Fixes: 06f502f57d0d ("leds: trigger: Introduce a NETDEV trigger")
Signed-off-by: Alexander Sverdlin <alexander.sverdlin@siemens.com>
---
Changelog:
v3:
- explicit mutex_lock()/mutex_unlock() instead of guard()

 drivers/leds/trigger/ledtrig-netdev.c | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/drivers/leds/trigger/ledtrig-netdev.c b/drivers/leds/trigger/ledtrig-netdev.c
index cfaf130157b3d..e72e850e0070f 100644
--- a/drivers/leds/trigger/ledtrig-netdev.c
+++ b/drivers/leds/trigger/ledtrig-netdev.c
@@ -676,9 +676,12 @@ static void netdev_trig_work(struct work_struct *work)
 	unsigned long interval;
 	int invert;
 
+	mutex_lock(&trigger_data->lock);
+
 	/* If we dont have a device, insure we are off */
 	if (!trigger_data->net_dev) {
 		led_set_brightness(trigger_data->led_cdev, LED_OFF);
+		mutex_unlock(&trigger_data->lock);
 		return;
 	}
 
@@ -686,8 +689,10 @@ static void netdev_trig_work(struct work_struct *work)
 	if (!test_bit(TRIGGER_NETDEV_TX, &trigger_data->mode) &&
 	    !test_bit(TRIGGER_NETDEV_RX, &trigger_data->mode) &&
 	    !test_bit(TRIGGER_NETDEV_TX_ERR, &trigger_data->mode) &&
-	    !test_bit(TRIGGER_NETDEV_RX_ERR, &trigger_data->mode))
+	    !test_bit(TRIGGER_NETDEV_RX_ERR, &trigger_data->mode)) {
+		mutex_unlock(&trigger_data->lock);
 		return;
+	}
 
 	dev_stats = dev_get_stats(trigger_data->net_dev, &temp);
 	new_activity =
@@ -728,6 +733,8 @@ static void netdev_trig_work(struct work_struct *work)
 
 	schedule_delayed_work(&trigger_data->work,
 			(atomic_read(&trigger_data->interval)*2));
+
+	mutex_unlock(&trigger_data->lock);
 }
 
 static int netdev_trig_activate(struct led_classdev *led_cdev)
-- 
2.56.0


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

* [PATCH v5 3/4] leds: trigger: netdev: Fix sysfs_update_group() races
  2026-10-20 12:46 [PATCH v5 0/4] leds: trigger: netdev: fix sysfs_update_group() races A. Sverdlin
  2026-10-20 12:46 ` [PATCH v5 1/4] leds: trigger: netdev: Put netdev on activate error path A. Sverdlin
  2026-10-20 12:46 ` [PATCH v5 2/4] leds: trigger: netdev: Access net_dev under trigger_data->lock in the worker A. Sverdlin
@ 2026-10-20 12:46 ` A. Sverdlin
  2026-10-20 12:56   ` sashiko-bot
  2026-10-20 12:46 ` [PATCH v5 4/4] leds: trigger: netdev: Serialize mode/interval stores with trigger lock A. Sverdlin
  3 siblings, 1 reply; 10+ messages in thread
From: A. Sverdlin @ 2026-10-20 12:46 UTC (permalink / raw)
  To: linux-leds
  Cc: Alexander Sverdlin, Lee Jones, Pavel Machek, Andrew Lunn,
	Mike Marciniszyn (Meta), Jakub Kicinski, Kees Cook,
	Marek Behún, Christian Marangi, linux-kernel, stable

From: Alexander Sverdlin <alexander.sverdlin@siemens.com>

The link_speed attribute group was listed in netdev_led_trigger.groups,
so the LED core creates and destroys it via device_add_groups() /
device_remove_groups() in led_trigger_set() (under trigger_lock), while
the trigger also refreshes it with sysfs_update_group() from
netdev_trig_notify() and device_name writes. With no shared lock this was
observed as a sysfs splat when the trigger is re-armed during PHY link-up:

  sysfs: cannot create duplicate filename '...green:lan/link_10'

    CPU 0 (led_trigger_set)        CPU 1 (linkwatch workqueue)
    -----------------------        ---------------------------
    activate():
      register_netdevice_notifier()
    .                              netdev_trig_notify(NETDEV_CHANGE):
    .                                sysfs_update_group() creates "link_10"
    device_add_groups()
      creates "link_10"  <- EEXIST!

The mirror case (device_remove_groups() racing the notifier) leaves an
orphaned sysfs file pointing at freed trigger_data, i.e. a UAF.

Manage the group in the trigger: drop it from netdev_led_trigger.groups
and create/destroy it in activate()/deactivate(), so the LED core never
touches it. The two remaining refreshers, netdev_trig_notify() and
device_name_store(), are serialized against each other by a dedicated
attr_lock, and the is_visible callback takes trigger_data->lock for its
supported_link_modes read. sysfs_update_group() is never called under
trigger_data->lock, so it cannot deadlock against a concurrent link_*
store that takes that lock.

Cc: stable@vger.kernel.org
Fixes: 06cdca014eca ("leds: trigger: netdev: Display only supported link speed attribute")
Signed-off-by: Alexander Sverdlin <alexander.sverdlin@siemens.com>
---
Changelog:
v3:
- changed guard() to scoped_guard() and dev_put() to unset_device_name()
v2:
- this patch is a combined rework of patches 1&2 from v1

 drivers/leds/trigger/ledtrig-netdev.c | 74 +++++++++++++++++----------
 1 file changed, 47 insertions(+), 27 deletions(-)

diff --git a/drivers/leds/trigger/ledtrig-netdev.c b/drivers/leds/trigger/ledtrig-netdev.c
index e72e850e0070f..bf9a5ec245a43 100644
--- a/drivers/leds/trigger/ledtrig-netdev.c
+++ b/drivers/leds/trigger/ledtrig-netdev.c
@@ -57,6 +57,8 @@
 
 struct led_netdev_data {
 	struct mutex lock;
+	/* Serializes link_speed group refreshes; never taken by attr stores */
+	struct mutex attr_lock;
 
 	struct delayed_work work;
 	struct notifier_block notifier;
@@ -336,8 +338,9 @@ static ssize_t device_name_store(struct device *dev,
 	if (ret < 0)
 		return ret;
 
-	/* Refresh link_speed visibility */
-	sysfs_update_group(&dev->kobj, &netdev_trig_link_speed_attrs_group);
+	/* Serialize the link_speed visibility refresh against netdev_trig_notify() */
+	scoped_guard(mutex, &trigger_data->attr_lock)
+		sysfs_update_group(&dev->kobj, &netdev_trig_link_speed_attrs_group);
 
 	return size;
 }
@@ -544,22 +547,23 @@ static umode_t netdev_trig_link_speed_visible(struct kobject *kobj,
 	 * Stop at the first matching entry as we care only to check if a particular
 	 * speed is supported and not the kind.
 	 */
-	for_each_set_bit(mode, supported_link_modes, __ETHTOOL_LINK_MODE_MASK_NBITS) {
-		struct ethtool_link_ksettings link_ksettings;
-
-		ethtool_params_from_link_mode(&link_ksettings, mode);
-
-		CHECK_LINK_MODE_ATTR(10);
-		CHECK_LINK_MODE_ATTR(100);
-		CHECK_LINK_MODE_ATTR(1000);
-		CHECK_LINK_MODE_ATTR(2500);
-		CHECK_LINK_MODE_ATTR(5000);
-		CHECK_LINK_MODE_ATTR(10000);
-		CHECK_LINK_MODE_ATTR(25000);
-		CHECK_LINK_MODE_ATTR(40000);
-		CHECK_LINK_MODE_ATTR(50000);
-		CHECK_LINK_MODE_ATTR(100000);
-	}
+	scoped_guard(mutex, &trigger_data->lock)
+		for_each_set_bit(mode, supported_link_modes, __ETHTOOL_LINK_MODE_MASK_NBITS) {
+			struct ethtool_link_ksettings link_ksettings;
+
+			ethtool_params_from_link_mode(&link_ksettings, mode);
+
+			CHECK_LINK_MODE_ATTR(10);
+			CHECK_LINK_MODE_ATTR(100);
+			CHECK_LINK_MODE_ATTR(1000);
+			CHECK_LINK_MODE_ATTR(2500);
+			CHECK_LINK_MODE_ATTR(5000);
+			CHECK_LINK_MODE_ATTR(10000);
+			CHECK_LINK_MODE_ATTR(25000);
+			CHECK_LINK_MODE_ATTR(40000);
+			CHECK_LINK_MODE_ATTR(50000);
+			CHECK_LINK_MODE_ATTR(100000);
+		}
 
 	return 0;
 }
@@ -603,7 +607,6 @@ static const struct attribute_group netdev_trig_attrs_group = {
 
 static const struct attribute_group *netdev_trig_groups[] = {
 	&netdev_trig_attrs_group,
-	&netdev_trig_link_speed_attrs_group,
 	NULL,
 };
 
@@ -651,10 +654,6 @@ static int netdev_trig_notify(struct notifier_block *nb,
 		fallthrough;
 	case NETDEV_CHANGE:
 		get_device_state(trigger_data);
-		/* Refresh link_speed visibility */
-		if (evt == NETDEV_CHANGE)
-			sysfs_update_group(&led_cdev->dev->kobj,
-					   &netdev_trig_link_speed_attrs_group);
 		break;
 	}
 
@@ -662,6 +661,12 @@ static int netdev_trig_notify(struct notifier_block *nb,
 
 	mutex_unlock(&trigger_data->lock);
 
+	if (evt == NETDEV_CHANGE) {
+		scoped_guard(mutex, &trigger_data->attr_lock)
+			sysfs_update_group(&led_cdev->dev->kobj,
+					   &netdev_trig_link_speed_attrs_group);
+	}
+
 	return NOTIFY_DONE;
 }
 
@@ -749,6 +754,7 @@ static int netdev_trig_activate(struct led_classdev *led_cdev)
 		return -ENOMEM;
 
 	mutex_init(&trigger_data->lock);
+	mutex_init(&trigger_data->attr_lock);
 
 	trigger_data->notifier.notifier_call = netdev_trig_notify;
 	trigger_data->notifier.priority = 10;
@@ -782,12 +788,23 @@ static int netdev_trig_activate(struct led_classdev *led_cdev)
 
 	led_set_trigger_data(led_cdev, trigger_data);
 
+	rc = sysfs_create_group(&led_cdev->dev->kobj,
+				&netdev_trig_link_speed_attrs_group);
+	if (rc)
+		goto err_free;
+
 	rc = register_netdevice_notifier(&trigger_data->notifier);
-	if (rc) {
-		dev_put(trigger_data->net_dev);
-		kfree(trigger_data);
-	}
+	if (rc)
+		goto err_remove_group;
+
+	return 0;
 
+err_remove_group:
+	sysfs_remove_group(&led_cdev->dev->kobj,
+			   &netdev_trig_link_speed_attrs_group);
+err_free:
+	dev_put(trigger_data->net_dev);
+	kfree(trigger_data);
 	return rc;
 }
 
@@ -797,6 +814,9 @@ static void netdev_trig_deactivate(struct led_classdev *led_cdev)
 
 	unregister_netdevice_notifier(&trigger_data->notifier);
 
+	sysfs_remove_group(&led_cdev->dev->kobj,
+			   &netdev_trig_link_speed_attrs_group);
+
 	cancel_delayed_work_sync(&trigger_data->work);
 
 	dev_put(trigger_data->net_dev);
-- 
2.56.0


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

* [PATCH v5 4/4] leds: trigger: netdev: Serialize mode/interval stores with trigger lock
  2026-10-20 12:46 [PATCH v5 0/4] leds: trigger: netdev: fix sysfs_update_group() races A. Sverdlin
                   ` (2 preceding siblings ...)
  2026-10-20 12:46 ` [PATCH v5 3/4] leds: trigger: netdev: Fix sysfs_update_group() races A. Sverdlin
@ 2026-10-20 12:46 ` A. Sverdlin
  2026-10-20 12:59   ` sashiko-bot
  3 siblings, 1 reply; 10+ messages in thread
From: A. Sverdlin @ 2026-10-20 12:46 UTC (permalink / raw)
  To: linux-leds
  Cc: Alexander Sverdlin, Lee Jones, Pavel Machek, Andrew Lunn,
	Mike Marciniszyn (Meta), Jakub Kicinski, Kees Cook,
	Marek Behún, Christian Marangi, linux-kernel, stable

From: Alexander Sverdlin <alexander.sverdlin@siemens.com>

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().

The async cancel cannot stop a worker already blocked on the lock: it may
resume after a store enabled hw_control and keep software-blinking and
rescheduling itself. Gate the worker and its arming in set_baseline_state()
on a shared netdev_trig_sw_blink() helper, so it stops once offload is
active or RX/TX monitoring is off.

Cc: stable@vger.kernel.org
Fixes: d5e01266e7f5 ("leds: trigger: netdev: add additional specific link speed mode")
Signed-off-by: Alexander Sverdlin <alexander.sverdlin@siemens.com>
---
Changelog:
v4:
- introduced netdev_trig_sw_blink() and used it in netdev_trig_work()
v3:
- explicit mutex_lock()/mutex_unlock() instead of guard()
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 | 46 +++++++++++++++++++--------
 1 file changed, 32 insertions(+), 14 deletions(-)

diff --git a/drivers/leds/trigger/ledtrig-netdev.c b/drivers/leds/trigger/ledtrig-netdev.c
index bf9a5ec245a43..6859783422f5d 100644
--- a/drivers/leds/trigger/ledtrig-netdev.c
+++ b/drivers/leds/trigger/ledtrig-netdev.c
@@ -81,6 +81,16 @@ struct led_netdev_data {
 
 static const struct attribute_group netdev_trig_link_speed_attrs_group;
 
+/* True while the software activity worker should keep polling and blinking. */
+static bool netdev_trig_sw_blink(struct led_netdev_data *trigger_data)
+{
+	return trigger_data->net_dev && !trigger_data->hw_control &&
+	       (test_bit(TRIGGER_NETDEV_TX, &trigger_data->mode) ||
+		test_bit(TRIGGER_NETDEV_RX, &trigger_data->mode) ||
+		test_bit(TRIGGER_NETDEV_TX_ERR, &trigger_data->mode) ||
+		test_bit(TRIGGER_NETDEV_RX_ERR, &trigger_data->mode));
+}
+
 static void set_baseline_state(struct led_netdev_data *trigger_data)
 {
 	int current_brightness;
@@ -164,10 +174,7 @@ static void set_baseline_state(struct led_netdev_data *trigger_data)
 		/* If we are looking for RX/TX start periodically
 		 * checking stats
 		 */
-		if (test_bit(TRIGGER_NETDEV_TX, &trigger_data->mode) ||
-		    test_bit(TRIGGER_NETDEV_RX, &trigger_data->mode) ||
-		    test_bit(TRIGGER_NETDEV_TX_ERR, &trigger_data->mode) ||
-		    test_bit(TRIGGER_NETDEV_RX_ERR, &trigger_data->mode))
+		if (netdev_trig_sw_blink(trigger_data))
 			schedule_delayed_work(&trigger_data->work, 0);
 	}
 }
@@ -385,7 +392,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 +424,10 @@ static ssize_t netdev_led_attr_store(struct device *dev, const char *buf,
 		return -EINVAL;
 	}
 
+	/* async cancel: the worker takes this lock */
+	mutex_lock(&trigger_data->lock);
+
+	mode = trigger_data->mode;
 	assign_bit(bit, &mode, state);
 
 	if (test_bit(TRIGGER_NETDEV_LINK, &mode) &&
@@ -429,20 +440,26 @@ static ssize_t netdev_led_attr_store(struct device *dev, const char *buf,
 	     test_bit(TRIGGER_NETDEV_LINK_25000, &mode) ||
 	     test_bit(TRIGGER_NETDEV_LINK_40000, &mode) ||
 	     test_bit(TRIGGER_NETDEV_LINK_50000, &mode) ||
-	     test_bit(TRIGGER_NETDEV_LINK_100000, &mode)))
+	     test_bit(TRIGGER_NETDEV_LINK_100000, &mode))) {
+		mutex_unlock(&trigger_data->lock);
 		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);
 
 	if (!led_cdev->brightness_set && !led_cdev->brightness_set_blocking &&
-	    !trigger_data->hw_control)
+	    !trigger_data->hw_control) {
+		mutex_unlock(&trigger_data->lock);
 		return -EOPNOTSUPP;
+	}
 
 	set_baseline_state(trigger_data);
 
+	mutex_unlock(&trigger_data->lock);
+
 	return size;
 }
 
@@ -503,10 +520,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;
@@ -690,11 +711,8 @@ static void netdev_trig_work(struct work_struct *work)
 		return;
 	}
 
-	/* If we are not looking for RX/TX then return  */
-	if (!test_bit(TRIGGER_NETDEV_TX, &trigger_data->mode) &&
-	    !test_bit(TRIGGER_NETDEV_RX, &trigger_data->mode) &&
-	    !test_bit(TRIGGER_NETDEV_TX_ERR, &trigger_data->mode) &&
-	    !test_bit(TRIGGER_NETDEV_RX_ERR, &trigger_data->mode)) {
+	/* Nothing to do under hw offload or without RX/TX monitoring */
+	if (!netdev_trig_sw_blink(trigger_data)) {
 		mutex_unlock(&trigger_data->lock);
 		return;
 	}
-- 
2.56.0


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

* Re: [PATCH v5 1/4] leds: trigger: netdev: Put netdev on activate error path
  2026-10-20 12:46 ` [PATCH v5 1/4] leds: trigger: netdev: Put netdev on activate error path A. Sverdlin
@ 2026-10-20 12:51   ` sashiko-bot
  0 siblings, 0 replies; 10+ messages in thread
From: sashiko-bot @ 2026-10-20 12:51 UTC (permalink / raw)
  To: A. Sverdlin, linux-leds; +Cc: lee

> From: Alexander Sverdlin <alexander.sverdlin@siemens.com>
> 
> When hw control is active, set_device_name() takes a reference on the
> netdev via dev_get_by_name(). The register_netdevice_notifier() failure
> path frees trigger_data without dropping it, leaking the netdev.
> 
> Cc: stable@vger.kernel.org
> Closes: https://lore.kernel.org/all/20260914140045.B8F4C1F000FF@smtp.kernel.org/
> Fixes: 0316cc5629d1 ("leds: trigger: netdev: init mode if hw control already active")
> Signed-off-by: Alexander Sverdlin <alexander.sverdlin@siemens.com>

Sashiko has reviewed this patch and found no issues.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261020124651.1987272-1-alexander.sverdlin@siemens.com?part=1


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

* Re: [PATCH v5 3/4] leds: trigger: netdev: Fix sysfs_update_group() races
  2026-10-20 12:46 ` [PATCH v5 3/4] leds: trigger: netdev: Fix sysfs_update_group() races A. Sverdlin
@ 2026-10-20 12:56   ` sashiko-bot
  0 siblings, 0 replies; 10+ messages in thread
From: sashiko-bot @ 2026-10-20 12:56 UTC (permalink / raw)
  To: A. Sverdlin, linux-leds; +Cc: lee

> From: Alexander Sverdlin <alexander.sverdlin@siemens.com>
> 
> The link_speed attribute group was listed in netdev_led_trigger.groups,
> so the LED core creates and destroys it via device_add_groups() /
> device_remove_groups() in led_trigger_set() (under trigger_lock), while
> the trigger also refreshes it with sysfs_update_group() from
> netdev_trig_notify() and device_name writes. With no shared lock this was
> observed as a sysfs splat when the trigger is re-armed during PHY link-up:
> 
>   sysfs: cannot create duplicate filename '...green:lan/link_10'
> 
>     CPU 0 (led_trigger_set)        CPU 1 (linkwatch workqueue)
>     -----------------------        ---------------------------
>     activate():
>       register_netdevice_notifier()
> [ ... ]
> store that takes that lock.
> 
> Cc: stable@vger.kernel.org
> Fixes: 06cdca014eca ("leds: trigger: netdev: Display only supported link speed attribute")
> Signed-off-by: Alexander Sverdlin <alexander.sverdlin@siemens.com>

Sashiko has reviewed this patch and found no issues.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261020124651.1987272-1-alexander.sverdlin@siemens.com?part=3


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

* Re: [PATCH v5 4/4] leds: trigger: netdev: Serialize mode/interval stores with trigger lock
  2026-10-20 12:46 ` [PATCH v5 4/4] leds: trigger: netdev: Serialize mode/interval stores with trigger lock A. Sverdlin
@ 2026-10-20 12:59   ` sashiko-bot
  2026-10-20 14:40     ` Sverdlin, Alexander
  0 siblings, 1 reply; 10+ messages in thread
From: sashiko-bot @ 2026-10-20 12:59 UTC (permalink / raw)
  To: A. Sverdlin, linux-leds; +Cc: lee

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] leds: trigger: netdev: unlocked hw_control check in interval_store()
- [Medium] leds: trigger: netdev: missing carrier_link_up check in netdev_trig_sw_blink()
--

commit fa618832fa8e967d013f87c080cded7d0b155827
Author: Alexander Sverdlin <alexander.sverdlin@siemens.com>

leds: trigger: netdev: Serialize mode/interval stores with trigger lock

This commit serializes sysfs mode and interval attribute stores in the
netdev LED trigger using trigger_data->lock, switches to asynchronous
work cancellation to avoid deadlocking with netdev_trig_work(), and adds
a netdev_trig_sw_blink() helper to gate software activity polling.

> diff --git a/drivers/leds/trigger/ledtrig-netdev.c b/drivers/leds/trigger/ledtrig-netdev.c
> index bf9a5ec245a43..6859783422f5d 100644
> --- a/drivers/leds/trigger/ledtrig-netdev.c
> +++ b/drivers/leds/trigger/ledtrig-netdev.c
> @@ -81,6 +81,16 @@ struct led_netdev_data {
>  
>  static const struct attribute_group netdev_trig_link_speed_attrs_group;
>  
> +/* True while the software activity worker should keep polling and blinking. */
> +static bool netdev_trig_sw_blink(struct led_netdev_data *trigger_data)
> +{
> +	return trigger_data->net_dev && !trigger_data->hw_control &&
> +	       (test_bit(TRIGGER_NETDEV_TX, &trigger_data->mode) ||
> +		test_bit(TRIGGER_NETDEV_RX, &trigger_data->mode) ||
> +		test_bit(TRIGGER_NETDEV_TX_ERR, &trigger_data->mode) ||
> +		test_bit(TRIGGER_NETDEV_RX_ERR, &trigger_data->mode));
> +}

[Severity: Medium]
Should netdev_trig_sw_blink() also check trigger_data->carrier_link_up?

In set_baseline_state(), trigger_data->work is only scheduled when
trigger_data->carrier_link_up is true, and it is not cancelled when
carrier_link_up is false:

drivers/leds/trigger/ledtrig-netdev.c:set_baseline_state() {
	if (!trigger_data->carrier_link_up) {
		led_set_brightness(led_cdev, LED_OFF);
	} else {
		...
		if (netdev_trig_sw_blink(trigger_data))
			schedule_delayed_work(&trigger_data->work, 0);
	}
}

Both netdev_trig_notify() and set_device_name() call
cancel_delayed_work_sync(&trigger_data->work) before acquiring
trigger_data->lock:

drivers/leds/trigger/ledtrig-netdev.c:netdev_trig_notify() {
	cancel_delayed_work_sync(&trigger_data->work);

	mutex_lock(&trigger_data->lock);

	trigger_data->carrier_link_up = false;
	...
	set_baseline_state(trigger_data);

	mutex_unlock(&trigger_data->lock);
}

If a concurrent netdev_led_attr_store() or interval_store() acquires
trigger_data->lock in the window between cancel_delayed_work_sync() and
mutex_lock(&trigger_data->lock) while carrier_link_up is still true,
set_baseline_state() will re-arm trigger_data->work.

When netdev_trig_notify() or set_device_name() then acquires
trigger_data->lock, it sets carrier_link_up = false and sets the LED to
LED_OFF without cancelling the newly scheduled work. When netdev_trig_work()
runs, netdev_trig_sw_blink() returns true despite carrier_link_up being
false, allowing the worker to keep polling, blinking via
led_blink_set_oneshot(), and rescheduling itself while the link is down
(and leaving the LED on when any link or duplex mode bit sets invert = 1).

[ ... ]

> @@ -503,10 +520,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;

[Severity: Medium]
Can interval_store() race with concurrent trigger_data->hw_control updates?

At the start of interval_store(), trigger_data->hw_control is checked
before acquiring trigger_data->lock:

drivers/leds/trigger/ledtrig-netdev.c:interval_store() {
	if (trigger_data->hw_control)
		return -EINVAL;
	...
	if (value >= 5 && value <= 10000) {
		mutex_lock(&trigger_data->lock);
}

If trigger_data->hw_control is false during that unlocked check, a
concurrent netdev_led_attr_store() or netdev_trig_notify() (on NETDEV_UP)
can acquire trigger_data->lock first, see the default interval in
can_hw_control(), and set trigger_data->hw_control = true.

When interval_store() then acquires trigger_data->lock, it does not
re-check trigger_data->hw_control before updating trigger_data->interval
and calling set_baseline_state(). Doesn't this allow trigger_data->interval
to be changed to a non-default value while hw_control is active, causing
subsequent can_hw_control() checks to unexpectedly drop out of hardware
offload?

[ ... ]

>
> base-commit: 3d229ce89862b8e82d71a9e11d6f840ca272a6d6

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261020124651.1987272-1-alexander.sverdlin@siemens.com?part=4

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

* Re: [PATCH v5 2/4] leds: trigger: netdev: Access net_dev under trigger_data->lock in the worker
  2026-10-20 12:46 ` [PATCH v5 2/4] leds: trigger: netdev: Access net_dev under trigger_data->lock in the worker A. Sverdlin
@ 2026-10-20 13:02   ` sashiko-bot
  0 siblings, 0 replies; 10+ messages in thread
From: sashiko-bot @ 2026-10-20 13:02 UTC (permalink / raw)
  To: A. Sverdlin, linux-leds; +Cc: lee

> From: Alexander Sverdlin <alexander.sverdlin@siemens.com>
> 
> netdev_trig_work() dereferences trigger_data->net_dev without the lock,
> while set_device_name() and netdev_trig_notify() dev_put() and replace it
> under trigger_data->lock. On NETDEV_UNREGISTER the worker can run
> dev_get_stats() on a netdev being freed (UAF).
> 
> Take trigger_data->lock in the worker. cancel_delayed_work_sync() must
> then never run under that lock.
> 
> Cc: stable@vger.kernel.org
> Closes: https://lore.kernel.org/all/20260914142116.6DAC51F000FF@smtp.kernel.org/
> Fixes: 06f502f57d0d ("leds: trigger: Introduce a NETDEV trigger")
> Signed-off-by: Alexander Sverdlin <alexander.sverdlin@siemens.com>

Sashiko has reviewed this patch and found no issues.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261020124651.1987272-1-alexander.sverdlin@siemens.com?part=2


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

* Re: [PATCH v5 4/4] leds: trigger: netdev: Serialize mode/interval stores with trigger lock
  2026-10-20 12:59   ` sashiko-bot
@ 2026-10-20 14:40     ` Sverdlin, Alexander
  0 siblings, 0 replies; 10+ messages in thread
From: Sverdlin, Alexander @ 2026-10-20 14:40 UTC (permalink / raw)
  To: sashiko-reviews@lists.linux.dev, linux-leds@vger.kernel.org
  Cc: lee@kernel.org

Hi all,

On Tue, 2026-10-20 at 12:59 +0000, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
> - [Medium] leds: trigger: netdev: unlocked hw_control check in interval_store()
> - [Medium] leds: trigger: netdev: missing carrier_link_up check in netdev_trig_sw_blink()

ACK, both findings will be fixed in v6.

> --
> 
> commit fa618832fa8e967d013f87c080cded7d0b155827
> Author: Alexander Sverdlin <alexander.sverdlin@siemens.com>
> 
> leds: trigger: netdev: Serialize mode/interval stores with trigger lock
> 
> This commit serializes sysfs mode and interval attribute stores in the
> netdev LED trigger using trigger_data->lock, switches to asynchronous
> work cancellation to avoid deadlocking with netdev_trig_work(), and adds
> a netdev_trig_sw_blink() helper to gate software activity polling.
> 
> > diff --git a/drivers/leds/trigger/ledtrig-netdev.c b/drivers/leds/trigger/ledtrig-netdev.c
> > index bf9a5ec245a43..6859783422f5d 100644
> > --- a/drivers/leds/trigger/ledtrig-netdev.c
> > +++ b/drivers/leds/trigger/ledtrig-netdev.c
> > @@ -81,6 +81,16 @@ struct led_netdev_data {
> >  
> >  static const struct attribute_group netdev_trig_link_speed_attrs_group;
> >  
> > +/* True while the software activity worker should keep polling and blinking. */
> > +static bool netdev_trig_sw_blink(struct led_netdev_data *trigger_data)
> > +{
> > +	return trigger_data->net_dev && !trigger_data->hw_control &&
> > +	       (test_bit(TRIGGER_NETDEV_TX, &trigger_data->mode) ||
> > +		test_bit(TRIGGER_NETDEV_RX, &trigger_data->mode) ||
> > +		test_bit(TRIGGER_NETDEV_TX_ERR, &trigger_data->mode) ||
> > +		test_bit(TRIGGER_NETDEV_RX_ERR, &trigger_data->mode));
> > +}
> 
> [Severity: Medium]
> Should netdev_trig_sw_blink() also check trigger_data->carrier_link_up?
> 
> In set_baseline_state(), trigger_data->work is only scheduled when
> trigger_data->carrier_link_up is true, and it is not cancelled when
> carrier_link_up is false:
> 
> drivers/leds/trigger/ledtrig-netdev.c:set_baseline_state() {
> 	if (!trigger_data->carrier_link_up) {
> 		led_set_brightness(led_cdev, LED_OFF);
> 	} else {
> 		...
> 		if (netdev_trig_sw_blink(trigger_data))
> 			schedule_delayed_work(&trigger_data->work, 0);
> 	}
> }
> 
> Both netdev_trig_notify() and set_device_name() call
> cancel_delayed_work_sync(&trigger_data->work) before acquiring
> trigger_data->lock:
> 
> drivers/leds/trigger/ledtrig-netdev.c:netdev_trig_notify() {
> 	cancel_delayed_work_sync(&trigger_data->work);
> 
> 	mutex_lock(&trigger_data->lock);
> 
> 	trigger_data->carrier_link_up = false;
> 	...
> 	set_baseline_state(trigger_data);
> 
> 	mutex_unlock(&trigger_data->lock);
> }
> 
> If a concurrent netdev_led_attr_store() or interval_store() acquires
> trigger_data->lock in the window between cancel_delayed_work_sync() and
> mutex_lock(&trigger_data->lock) while carrier_link_up is still true,
> set_baseline_state() will re-arm trigger_data->work.
> 
> When netdev_trig_notify() or set_device_name() then acquires
> trigger_data->lock, it sets carrier_link_up = false and sets the LED to
> LED_OFF without cancelling the newly scheduled work. When netdev_trig_work()
> runs, netdev_trig_sw_blink() returns true despite carrier_link_up being
> false, allowing the worker to keep polling, blinking via
> led_blink_set_oneshot(), and rescheduling itself while the link is down
> (and leaving the LED on when any link or duplex mode bit sets invert = 1).
> 
> [ ... ]
> 
> > @@ -503,10 +520,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;
> 
> [Severity: Medium]
> Can interval_store() race with concurrent trigger_data->hw_control updates?
> 
> At the start of interval_store(), trigger_data->hw_control is checked
> before acquiring trigger_data->lock:
> 
> drivers/leds/trigger/ledtrig-netdev.c:interval_store() {
> 	if (trigger_data->hw_control)
> 		return -EINVAL;
> 	...
> 	if (value >= 5 && value <= 10000) {
> 		mutex_lock(&trigger_data->lock);
> }
> 
> If trigger_data->hw_control is false during that unlocked check, a
> concurrent netdev_led_attr_store() or netdev_trig_notify() (on NETDEV_UP)
> can acquire trigger_data->lock first, see the default interval in
> can_hw_control(), and set trigger_data->hw_control = true.
> 
> When interval_store() then acquires trigger_data->lock, it does not
> re-check trigger_data->hw_control before updating trigger_data->interval
> and calling set_baseline_state(). Doesn't this allow trigger_data->interval
> to be changed to a non-default value while hw_control is active, causing
> subsequent can_hw_control() checks to unexpectedly drop out of hardware
> offload?
> 
> [ ... ]
> 
> > 
> > base-commit: 3d229ce89862b8e82d71a9e11d6f840ca272a6d6

-- 
Alexander Sverdlin
Siemens AG
www.siemens.com

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

end of thread, other threads:[~2026-10-20 14:40 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-20 12:46 [PATCH v5 0/4] leds: trigger: netdev: fix sysfs_update_group() races A. Sverdlin
2026-10-20 12:46 ` [PATCH v5 1/4] leds: trigger: netdev: Put netdev on activate error path A. Sverdlin
2026-10-20 12:51   ` sashiko-bot
2026-10-20 12:46 ` [PATCH v5 2/4] leds: trigger: netdev: Access net_dev under trigger_data->lock in the worker A. Sverdlin
2026-10-20 13:02   ` sashiko-bot
2026-10-20 12:46 ` [PATCH v5 3/4] leds: trigger: netdev: Fix sysfs_update_group() races A. Sverdlin
2026-10-20 12:56   ` sashiko-bot
2026-10-20 12:46 ` [PATCH v5 4/4] leds: trigger: netdev: Serialize mode/interval stores with trigger lock A. Sverdlin
2026-10-20 12:59   ` sashiko-bot
2026-10-20 14:40     ` Sverdlin, Alexander

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.