Linux PCI subsystem development
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Markus Probst" <markus.probst@posteo.de>
Cc: lee@kernel.org, linux-pci@vger.kernel.org,
	linux-leds@vger.kernel.org, ojeda@kernel.org
Subject: Re: [PATCH v22 3/3] rust: leds: add multicolor classdev abstractions
Date: Sun, 12 Jul 2026 17:40:29 +0000	[thread overview]
Message-ID: <20260712174029.93A541F000E9@smtp.kernel.org> (raw)
In-Reply-To: <20260712-rust_leds-v22-3-055b0459a079@posteo.de>

Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [High] Data race and Undefined Behavior in `MultiColorSubLed` field accessors due to plain non-volatile reads.
- [High] Fwnode reference leak and potential Use-After-Free due to conditional failure to pass `init_data`.
- [High] `led_mc_calc_color_components` is invoked without driver locks, causing data races on `subled_info`.
- [High] The `blink_set_callback` constructs `&mut usize` references to `delay_on` and `delay_off` while they may be concurrently read by the C subsystem, violating Rust's strict aliasing rules.
- [Low] The commit message capitalization does not follow the LED subsystem guidelines.
--

--- Patch [3]: [PATCH v22 3/3] rust: leds: add multicolor classdev abstractions ---
commit d8a204f9781a723e65e93fe2f08ed4aaa56bb20f
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 led::MultiColor mode implementation, safe wrappers
for led_classdev_mc and mc_subled, and a device registration function.

> rust: leds: add multicolor classdev abstractions

[Severity: Low]
This isn't a bug, but does the commit message capitalization follow the LED
subsystem guidelines? 

The guidelines require capitalizing the description after the subsystem prefix,
such as "rust: leds: Add multicolor classdev abstractions".

> diff --git a/rust/kernel/led/multicolor.rs b/rust/kernel/led/multicolor.rs
> new file mode 100644
> index 0000000000000..82fe96fd5273b
> --- /dev/null
> +++ b/rust/kernel/led/multicolor.rs
[ ... ]
> +    /// The intensity of the sub led.
> +    #[inline]
> +    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]
Can this lead to a data race and undefined behavior? 

The C LED subsystem might mutate the intensity concurrently, for instance via
sysfs writes like multi_intensity_store().

In Rust's memory model, concurrent non-atomic reads and writes to identical
memory locations constitute a data race regardless of alignment. 

Would it be safer to use core::ptr::read_volatile() here?

> +    }
> +
> +    /// The brightness of the sub led.
> +    #[inline]
> +    pub const fn brightness(&self) -> u32 {
> +        // SAFETY:
> +        // - `self.brightness.get()` is a valid pointer to `u32`.
> +        // - We don't have exclusive or immutable access to `self.brightness`,
> +        //   but the alignment should prevent "load tearing".
> +        unsafe { *self.brightness.get() }

[Severity: High]
Similarly, could concurrent sysfs writes mutate the brightness while it's
being read here? 

Using a plain read instead of read_volatile might lead to data races and
undefined behavior for the same reasons as intensity().

[ ... ]
> +                // 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 sequence potentially cause a memory leak or a use-after-free of the
fwnode?

When a custom name is used (self.name.is_some()), init_data is omitted and
null_mut() is passed to led_classdev_multicolor_register_ext(), preventing
the C device from inheriting the fwnode.

However, core::mem::forget(self.fwnode) is called unconditionally.

[ ... ]
> +#[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) };
> +
> +        let _fwnode = dev
> +            .fwnode()
> +            // SAFETY: the reference count of `fwnode` has previously been
> +            // incremented in `led::DeviceBuilder::build_multicolor`.
> +            .map(|fwnode| unsafe { ARef::from_raw(NonNull::from(fwnode)) });

[Severity: High]
If init_data wasn't passed during registration, won't dev.fwnode() either
return null (permanently leaking the ARef) or return an inherited fwnode
from a parent device?

If it inherits a parent's fwnode, this code seems like it would drop a
reference it never owned, resulting in a use-after-free.

[ ... ]
> +    unsafe extern "C" fn brightness_set_callback(
> +        led_cdev: *mut bindings::led_classdev,
> +        brightness: u32,
> +    ) {
> +        // SAFETY: The function's contract guarantees that `led_cdev` is a valid pointer to a
> +        // `led_classdev` embedded within a `led::MultiColorDevice`.
> +        let classdev = unsafe { MultiColorDevice::<T>::from_raw(led_cdev) };
> +        // SAFETY: `classdev.parent()` is guaranteed to be contained in `T::Bus`.
> +        let parent = unsafe { T::Bus::from_device(classdev.parent()) };
> +
> +        // SAFETY: `classdev.classdev.get()` is guaranteed to be a pointer to a valid
> +        // `led_classdev_mc`.
> +        unsafe { bindings::led_mc_calc_color_components(classdev.classdev.get(), brightness) };
> +
> +        let _ = classdev.ops.brightness_set(parent, classdev, brightness);
> +    }

[Severity: High]
Could calling led_mc_calc_color_components unconditionally before dispatching
to the driver's brightness_set callback lead to data races?

The LED core doesn't serialize brightness_set calls. Hardware triggers and
sysfs writes could occur concurrently.

Normally in C drivers, led_mc_calc_color_components is invoked manually
inside the driver's lock to protect concurrent writes to
subled_info[i].brightness. 

By executing it here outside the driver's callback, doesn't it make it
impossible for the driver to protect the calculation?

[ ... ]
> +    unsafe extern "C" fn blink_set_callback(
> +        led_cdev: *mut bindings::led_classdev,
> +        delay_on: *mut usize,
> +        delay_off: *mut usize,
> +    ) -> i32 {
> +        from_result(|| {
> +            // SAFETY: The function's contract guarantees that `led_cdev` is a valid pointer to a
> +            // `led_classdev` embedded within a `led::MultiColorDevice`.
> +            let classdev = unsafe { MultiColorDevice::<T>::from_raw(led_cdev) };
> +            // SAFETY: `classdev.parent()` is guaranteed to be contained in `T::Bus`.
> +            let parent = unsafe { T::Bus::from_device(classdev.parent()) };
> +
> +            classdev.ops.blink_set(
> +                parent,
> +                classdev,
> +                // SAFETY: The function's contract guarantees that `delay_on` points to a `usize`
> +                // and is exclusive for the period of this function.
> +                unsafe { &mut *delay_on },
> +                // SAFETY: The function's contract guarantees that `delay_off` points to a `usize`
> +                // and is exclusive for the period of this function.
> +                unsafe { &mut *delay_off },
> +            )?;

[Severity: High]
Does casting delay_on and delay_off to exclusive mutable references 
(&mut usize) violate strict aliasing?

These FFI pointers point to led_cdev->blink_delay_on and blink_delay_off,
which the C subsystem allows to be read locklessly via sysfs
(e.g., delay_on_show()).

Since Rust's &mut T guarantees absolutely exclusive access, a concurrent
read from C would invalidate this contract and cause undefined behavior.

Should these be passed as raw pointers or use interior mutability types?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260712-rust_leds-v22-0-055b0459a079@posteo.de?part=3

      reply	other threads:[~2026-07-12 17:40 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-12 17:26 [PATCH v22 0/3] rust: leds: add led classdev abstractions Markus Probst via B4 Relay
2026-07-12 17:26 ` [PATCH v22 1/3] rust: leds: add basic " Markus Probst via B4 Relay
2026-07-12 17:42   ` sashiko-bot
2026-07-12 17:26 ` [PATCH v22 2/3] rust: leds: add Mode trait Markus Probst via B4 Relay
2026-07-12 17:35   ` sashiko-bot
2026-07-12 17:26 ` [PATCH v22 3/3] rust: leds: add multicolor classdev abstractions Markus Probst via B4 Relay
2026-07-12 17:40   ` 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=20260712174029.93A541F000E9@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