From: sashiko-bot@kernel.org
To: "Markus Probst" <markus.probst@posteo.de>
Cc: linux-pci@vger.kernel.org, lee@kernel.org,
linux-leds@vger.kernel.org, ojeda@kernel.org
Subject: Re: [PATCH v20 3/3] rust: leds: add multicolor classdev abstractions
Date: Mon, 29 Jun 2026 10:34:00 +0000 [thread overview]
Message-ID: <20260629103401.7F7A61F000E9@smtp.kernel.org> (raw)
In-Reply-To: <20260629-rust_leds-v20-3-f493ab053b4f@posteo.de>
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] Unsynchronized lockless reads in `intensity()` and `brightness()` cause Undefined Behavior.
- [High] Dangling pointer in `led_cdev.name` due to builder lifetime mismatch.
- [High] Incorrect fwnode reference counting leads to memory leaks and Use-After-Free.
- [Low] Copy-paste error in safety comment for MultiColorSubLed::brightness().
--
commit e54d5ad0bd9b464f2f25c0f6cb8f040e94fe0402
Author: Markus Probst <markus.probst@posteo.de>
rust: leds: add multicolor classdev abstractions
This commit implements the abstractions needed for multicolor LED class
devices. It introduces the MultiColor mode, MultiColorSubLed,
MultiColorDevice, and the DeviceBuilder::build_multicolor function for
registration.
> diff --git a/rust/kernel/led/multicolor.rs b/rust/kernel/led/multicolor.rs
> new file mode 100644
> index 0000000000000..9a341b3336489
> --- /dev/null
> +++ b/rust/kernel/led/multicolor.rs
[ ... ]
> + pub const fn intensity(&self) -> u32 {
> + // SAFETY:
> + // - `self.intensity.get()` is a valid pointer to `u32`.
> + // - We don't have exclusive or immutable access to `self.intensity`,
> + // but the alignment should prevent "load tearing".
> + unsafe { *self.intensity.get() }
> + }
[Severity: High]
Could this plain read cause undefined behavior?
The C LED subsystem updates the intensity and brightness fields directly,
often while holding the led_access mutex. Since this read from the
UnsafeCell is performed without atomic synchronization or read_volatile,
can it race with C-side updates if called from an unsynchronized driver
context?
> + /// The brightness of the sub led.
> + #[inline]
> + pub const fn brightness(&self) -> u32 {
> + // SAFETY:
> + // - `self.intensity.get()` is a valid pointer to `u32`.
[Severity: Low]
This isn't a bug, but there is a typo in this safety comment. It references
self.intensity.get() instead of self.brightness.get().
> + // - We don't have exclusive or immutable access to `self.brightness`,
> + // but the alignment should prevent "load tearing".
> + unsafe { *self.brightness.get() }
> + }
[Severity: High]
Similar to the intensity() method, does this plain unsynchronized read of
brightness risk undefined behavior if the C side modifies it concurrently?
[ ... ]
> +impl<'a, S: DeviceBuilderState> DeviceBuilder<'a, S> {
> + /// Registers a new [`MulticolorDevice`].
> + pub fn build_multicolor<'bound: 'a, T: LedOps<Mode = MultiColor> + 'bound>(
> + self,
> + parent: &'bound T::Bus,
> + ops: impl PinInit<T, Error> + 'a,
> + subleds: &'a [MultiColorSubLed],
> + ) -> impl PinInit<MultiColorDevice<'bound, T>, Error> + 'a {
[ ... ]
> + // SAFETY: `try_ffi_init` guarantees that `ptr` is valid for write.
> + // `led_classdev_mc` gets fully initialized in-place by
> + // `led_classdev_multicolor_register_ext` including `mutex` and `list_head`.
> + unsafe {
> + ptr.write(bindings::led_classdev_mc {
> + led_cdev: bindings::led_classdev {
> + brightness_set: (!T::BLOCKING)
> + .then_some(Adapter::<T>::brightness_set_callback),
> + brightness_set_blocking: T::BLOCKING
> + .then_some(Adapter::<T>::brightness_set_blocking_callback),
> + brightness_get: T::HAS_BRIGHTNESS_GET
> + .then_some(Adapter::<T>::brightness_get_callback),
> + blink_set: T::HAS_BLINK_SET.then_some(Adapter::<T>::blink_set_callback),
> + max_brightness: T::MAX_BRIGHTNESS,
> + brightness: self.initial_brightness,
> + color: self.color as u32,
> + name: self.name.map_or(core::ptr::null(), CStrExt::as_char_ptr),
[Severity: High]
Is there a risk of a dangling pointer here?
The DeviceBuilder borrows self.name for a lifetime 'a, but the resulting
MultiColorDevice only captures the 'bound lifetime. This drops the 'a
constraint, meaning the caller could safely drop the original CStr
immediately after initialization.
Since the C subsystem bypasses internal name generation and retains this
pointer if self.name is provided, could this lead to a use-after-free
when sysfs accesses the name later?
> + ..bindings::led_classdev::default()
> + },
[ ... ]
> + let mut init_data = bindings::led_init_data {
> + fwnode: self
> + .fwnode
> + .as_ref()
> + .map_or(core::ptr::null_mut(), |fwnode| fwnode.as_raw()),
> + default_label: core::ptr::null(),
> + devicename: self
> + .devicename
> + .map_or(core::ptr::null(), CStrExt::as_char_ptr),
> + devname_mandatory: self.devname_mandatory,
> + };
> +
> + // SAFETY:
> + // - `parent.as_ref().as_raw()` is guaranteed to be a pointer to a valid
> + // `device`.
> + // - `ptr` is guaranteed to be a pointer to an initialized `led_classdev_mc`.
> + to_result(unsafe {
> + bindings::led_classdev_multicolor_register_ext(
> + parent.as_ref().as_raw(),
> + ptr,
> + if self.name.is_none() {
> + &raw mut init_data
> + } else {
> + core::ptr::null_mut()
> + },
> + )
> + })?;
> +
> + subled_guard.dismiss();
> +
> + core::mem::forget(self.fwnode); // keep the reference count incremented
[Severity: High]
Does this cause a memory leak when self.name is provided?
If self.name.is_some(), the register call is passed a null pointer instead
of the init_data. This prevents the C subsystem from attaching the fwnode
to the device.
However, core::mem::forget(self.fwnode) is still called unconditionally,
which seems like it would permanently leak the reference count for the
fwnode when the subsystem didn't actually take ownership of it.
> +
> + Ok::<_, Error>(())
> + }),
[ ... ]
> +#[pinned_drop]
> +impl<'bound, T: LedOps<Mode = MultiColor> + 'bound> PinnedDrop for MultiColorDevice<'bound, T> {
> + fn drop(self: Pin<&mut Self>) {
> + let raw = self.classdev.get();
> + // SAFETY: The existence of `self` guarantees that `self.classdev.get()` is a pointer to a
> + // valid `led_classdev_mc`.
> + let dev: &device::Device = unsafe { device::Device::from_raw((*raw).led_cdev.dev) };
> +
> + if let Some(fwnode) = dev.fwnode() {
> + // SAFETY: the reference count of `fwnode` has previously been incremented in
> + // `led::DeviceBuilder::build_multicolor`.
> + unsafe { FwNode::dec_ref(NonNull::from(fwnode)) };
> + }
> +
> + // SAFETY: The existence of `self` guarantees that `self.classdev` has previously been
> + // successfully registered with `led_classdev_multicolor_register_ext`.
> + unsafe { bindings::led_classdev_multicolor_unregister(raw) };
[Severity: High]
Could dropping the fwnode reference before unregistering the device lead
to a use-after-free?
Unregistering the device calls device_del(), which may access dev->fwnode
for kobject_uevent or fwnode_links_read.
Since FwNode::dec_ref() is called first, the fwnode might already be freed
by the time the unregister function accesses it.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260629-rust_leds-v20-0-f493ab053b4f@posteo.de?part=3
prev parent reply other threads:[~2026-06-29 10:34 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-06-29 10:15 [PATCH v20 0/3] rust: leds: add led classdev abstractions Markus Probst
2026-06-29 10:15 ` [PATCH v20 1/3] rust: leds: add basic " Markus Probst
2026-06-29 10:31 ` sashiko-bot
2026-06-29 10:15 ` [PATCH v20 2/3] rust: leds: add Mode trait Markus Probst
2026-06-29 10:20 ` sashiko-bot
2026-06-29 10:15 ` [PATCH v20 3/3] rust: leds: add multicolor classdev abstractions Markus Probst
2026-06-29 10:34 ` sashiko-bot [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260629103401.7F7A61F000E9@smtp.kernel.org \
--to=sashiko-bot@kernel.org \
--cc=lee@kernel.org \
--cc=linux-leds@vger.kernel.org \
--cc=linux-pci@vger.kernel.org \
--cc=markus.probst@posteo.de \
--cc=ojeda@kernel.org \
--cc=sashiko-reviews@lists.linux.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox