Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

leds: led-class: Only Add LED to leds_list when it is fully ready

Before this change the LED was added to leds_list before led_init_core()
gets called adding it the list before led_classdev.set_brightness_work gets
initialized.

This leaves a window where led_trigger_register() of a LED's default
trigger will call led_trigger_set() which calls led_set_brightness()
which in turn will end up queueing the *uninitialized*
led_classdev.set_brightness_work.

This race gets hit by the lenovo-thinkpad-t14s EC driver which registers
2 LEDs with a default trigger provided by snd_ctl_led.ko in quick
succession. The first led_classdev_register() causes an async modprobe of
snd_ctl_led to run and that async modprobe manages to exactly hit
the window where the second LED is on the leds_list without led_init_core()
being called for it, resulting in:

------------[ cut here ]------------
WARNING: CPU: 11 PID: 5608 at kernel/workqueue.c:4234 __flush_work+0x344/0x390
Hardware name: LENOVO 21N2S01F0B/21N2S01F0B, BIOS N42ET93W (2.23 ) 09/01/2025
...
Call trace:
__flush_work+0x344/0x390 (P)
flush_work+0x2c/0x50
led_trigger_set+0x1c8/0x340
led_trigger_register+0x17c/0x1c0
led_trigger_register_simple+0x84/0xe8
snd_ctl_led_init+0x40/0xf88 [snd_ctl_led]
do_one_initcall+0x5c/0x318
do_init_module+0x9c/0x2b8
load_module+0x7e0/0x998

Close the race window by moving the adding of the LED to leds_list to
after the led_init_core() call.

Cc: stable@vger.kernel.org
Fixes: d23a22a74fde ("leds: delay led_set_brightness if stopping soft-blink")
Signed-off-by: Hans de Goede <johannes.goede@oss.qualcomm.com>
Reviewed-by: Sebastian Reichel <sre@kernel.org>
Link: https://patch.msgid.link/20251211163727.366441-1-johannes.goede@oss.qualcomm.com
Signed-off-by: Lee Jones <lee@kernel.org>

authored by

Hans de Goede and committed by
Lee Jones
d1883cef 8f0b4cce

+5 -5
+5 -5
drivers/leds/led-class.c
··· 560 560 #ifdef CONFIG_LEDS_BRIGHTNESS_HW_CHANGED 561 561 led_cdev->brightness_hw_changed = -1; 562 562 #endif 563 - /* add to the list of leds */ 564 - down_write(&leds_list_lock); 565 - list_add_tail(&led_cdev->node, &leds_list); 566 - up_write(&leds_list_lock); 567 - 568 563 if (!led_cdev->max_brightness) 569 564 led_cdev->max_brightness = LED_FULL; 570 565 ··· 568 573 led_cdev->wq = leds_wq; 569 574 570 575 led_init_core(led_cdev); 576 + 577 + /* add to the list of leds */ 578 + down_write(&leds_list_lock); 579 + list_add_tail(&led_cdev->node, &leds_list); 580 + up_write(&leds_list_lock); 571 581 572 582 #ifdef CONFIG_LEDS_TRIGGERS 573 583 led_trigger_set_default(led_cdev);