Re: [PATCH v15 1/3] rust: leds: add basic led classdev abstractions

Markus Probst <[email protected]>
Newsgroups dev.linux.lists.sashiko
Message-ID <[email protected]>
On Wed, 2026-04-29 at 20:56 +0000, [email protected] wrote:
> Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
> - [High] DeviceBuilder::name lifetime erasure causes Use-After-Free
> - [High] Unsound fwnode reference count management causes underflow and Use-After-Free
> - [High] Aliasing violation and Null pointer dereference (Undefined Behavior) in blink_set_callback
> - [Medium] LedOps::brightness_get prevents propagating hardware errors
> --
> 
> commit a78848106d199e2b6993093a6b1e1aad9d0557e7
> Author: Markus Probst <[email protected]>
> 
> rust: leds: add basic led classdev abstractions
> 
> This commit introduces basic Rust abstractions for the LED subsystem,
> including a LedOps trait for device operations, a DeviceBuilder to register
> class devices, and a safe wrapper Device around led_classdev.
> 
> > diff --git a/rust/kernel/led.rs b/rust/kernel/led.rs
> > new file mode 100644
> > index 000000000000..6cbaa917deb4
> > --- /dev/null
> > +++ b/rust/kernel/led.rs
> 
> [ ... ]
> 
> > +impl<'a> DeviceBuilder<'a, Named> {
> > +    /// Sets the name of the led.
> > +    ///
> > +    /// Setting this will prevent the fwnode from being used and prevents automatic name
> > +    /// composition.
> > +    #[inline]
> > +    pub fn name(self, name: &'a CStr) -> Self {
> > +        Self {
> > +            name: Some(name),
> > +            ..self
> > +        }
> > +    }
> > +}
> 
> Is it possible for the name string to be freed while the C subsystem still
> holds a pointer to it? DeviceBuilder::name accepts a &'a CStr and assigns
> its raw pointer to the name field of bindings::led_classdev.
> 
> Because Device<T> has no lifetime parameters, DeviceBuilder::build returns a
> Devres<Device<T>> that outlives the 'a lifetime.
> 
> If a caller dynamically allocates a string, registers the device, and drops
> the string, the C subsystem might retain a dangling pointer in led_cdev->name
> since it does not copy the name when init_data is bypassed. This could lead
> to a use-after-free when sysfs or led_classdev_unregister accesses the name.
> 
> [ ... ]
> 
> > +    /// Gets the current brightness level.
> > +    fn brightness_get(&self, dev: &Self::Bus, classdev: &Device<Self>) -> u32 {
> > +        let _ = (dev, classdev);
> > +        build_error!(VTABLE_DEFAULT_ERROR)
> > +    }
> 
> Does this signature prevent Rust LED drivers from safely propagating hardware
> read errors?
> 
> The C callback brightness_get returns an enum led_brightness, which the C
> subsystem treats as an int capable of holding negative error codes like
> -EIO.
> 
> Functions in the C core explicitly check for negative return values to
> gracefully handle hardware failures. Returning a u32 forces drivers to
> return bogus brightness values instead of an error. Could this be changed
> to return a Result<u32>?
Yes.
Bindgen interprets `enum led_brightness` as u32, so it will require
casting.

Only this issue is new. All other reports have already been dealt with
the last revision.

Thanks
- Markus Probst

> 
> > diff --git a/rust/kernel/led/normal.rs b/rust/kernel/led/normal.rs
> > new file mode 100644
> > index 000000000000..b457ddb742c7
> > --- /dev/null
> > +++ b/rust/kernel/led/normal.rs
> 
> [ ... ]
> 
> > +    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::Device`.
> > +            let classdev = unsafe { Device::<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 },
> > +            )?;
> 
> The C API permits callers to pass NULL for these arguments to request
> default blink delays. Would dereferencing a null pointer here to create a
> Rust reference cause immediate undefined behavior?
> 
> Additionally, when the C core passes pointers to the blink_delay_on and
> blink_delay_off fields inside the led_classdev struct, the callback
> simultaneously creates an immutable reference &Device<T> to the entire
> struct.
> 
> Does creating a mutable reference to a field while holding an immutable
> reference to the parent struct violate strict aliasing rules?
> 
> [ ... ]
> 
> > +#[pinned_drop]
> > +impl<T: LedOps> PinnedDrop for Device<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`.
> > +        let dev: &device::Device = unsafe { device::Device::from_raw((*raw).dev) };
> > +
> > +        let _fwnode = dev
> > +            .fwnode()
> > +            // SAFETY: the reference count of `fwnode` has previously been
> > +            // incremented in `led::Device::new`.
> > +            .map(|fwnode| unsafe { ARef::from_raw(NonNull::from(fwnode)) });
> 
> Can this underflow the fwnode reference count if self.fwnode was None
> during device building?
> 
> In DeviceBuilder::build, the code attempts to leak the FwNode reference by
> calling core::mem::forget(self.fwnode). If self.fwnode was None, nothing is
> leaked.
> 
> If the C driver core later automatically assigns an fwnode to the device,
> dev.fwnode() will return Some. This would cause PinnedDrop to construct an
> ARef and drop it, decrementing a reference count that it never incremented
> or owned.
signature.asc (application/pgp-signature, 870 B)
-----BEGIN PGP SIGNATURE-----

iQJPBAABCAA5FiEEgnQYxPSsWOdyMMRzNHYf+OetQ9IFAmn0krIbFIAAAAAABAAO
bWFudTIsMi41KzEuMTIsMiwyAAoJEDR2H/jnrUPSmEEP/jg7+tKcSRIACij4pfic
8/W+aZsQRnF4R/RddWCAJqnP4knb/l3Wfl6QXCAVpr9Q7AJaBRZJ6M158xNfrUDT
0Fgwyw7nLrRgwoFOJEbq/vcTawo69/5IdG4OSUcb5Asb//MjFrGedab3WzAAXyeD
jrL4Bb0RUZyXqhhtmyvgZfrQKuAoAjngPCSSpyEN0IIw2gXqG5HOetndqMYuDYwT
AhbCrG3SNNdeJG2Qcvt4QbEqp3Qmt+PCt/teKc52oNHJcBZBvs++/JAm1B/4+xbL
BEe7ksjAhqmDpTsQt/oXNUQ0n09iyv6OSZPdpWSBLjjxua2zXPhqB4qG9gVqkiBU
ak+PydS9yBqCgZEgnBnGQ5kVJzXt8h90AJRUE1F5+Ap65eNea2NrkiWdqJ/t+gP2
Vwlanp41mlFBJt1R8PzvT+omCtov/7KsWgB+BR98JkRuRD/EklhufnkI3Fobtq1c
SWg/5Upsi5S4UkJ2EI59uIV5/9cpz3B+ztq4eMvvyXfpXNfqRMuxl6eSV3HX6YsS
plM4wM3fJ5w2PqhAoDzdT8kvzOZn1RlFi4rWekUBmE6p/5F7UPBkwbwBLteGgLPb
bFxJiqUujmXPY4T75se5zG9OBajKpujFECpEXO9r243iJj7V87XoJYlGdMhpBxex
y0AwzfE7G2djPA2PFF1Z8xxu
=6Mol
-----END PGP SIGNATURE-----
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.