Re: [PATCH RESEND v23 1/3] rust: leds: add basic led classdev abstractions
[email protected] Wed, 05 Aug 2026 20:33:52 +0000
| Newsgroups | org.kernel.vger.linux-leds,org.kernel.vger.linux-pci |
|---|---|
| Message-ID | <[email protected]> |
Thank you for your contribution! Sashiko AI review found 3 potential issue(= s) to consider: - [High] Refcount underflow and use-after-free of `fwnode` via incorrect li= fecycle management. - [High] Lifetime erasure in `DeviceBuilder::build` leaves a dangling point= er to the LED name string, causing Use-After-Free. - [High] Mutable aliasing of shared C pointers in `blink_set_callback` trig= gers Undefined Behavior. -- --- Patch [1]: [PATCH RESEND v23 1/3] rust: leds: add basic led classdev ab= stractions --- commit 6f6f8b461cc55c0944a56c43b46dbc985385115e Author: Markus Probst <[email protected]> rust: leds: add basic led classdev abstractions Implement the core abstractions needed for led class devices, including: led::LedOps, led::DeviceBuilder, and led::Device. > diff --git a/rust/kernel/led/normal.rs b/rust/kernel/led/normal.rs > index 0000000000000..816db7c576894 > --- /dev/null > +++ b/rust/kernel/led/normal.rs > @@ -11,9 +11,6 @@ > /// This structure represents the Rust abstraction for a led class devic= e. > #[pin_data(PinnedDrop)] > pub struct Device<'bound, T: LedOps + 'bound> { > #[pin] > ops: T, > #[pin] > classdev: Opaque<bindings::led_classdev>, [Severity: High] Does this struct need to carry the 'init lifetime from DeviceBuilder? If DeviceBuilder::name takes a &'init CStr and stores it, and then=20 DeviceBuilder::build extracts this as a raw pointer: rust/kernel/led/normal.rs:DeviceBuilder::build() { ... name: self.name.map_or(core::ptr::null(), CStrExt::as_char_ptr), ... } But the resulting Device<'bound, T> does not carry the 'init lifetime.=20 This could allow the Rust borrow checker to drop the original string while= =20 the Device remains fully active. If a driver uses a local string for the na= me,=20 will it be freed, leaving led_classdev.name pointing to unallocated memory= =20 and causing a use-after-free? [ ... ] > @@ -180,18 +180,11 @@ impl<T: LedOps> Adapter<T> { > // SAFETY: `classdev.parent()` is guaranteed to be contained= in `T::Bus`. > let parent =3D unsafe { T::Bus::from_device(classdev.parent(= )) }; > =20 > classdev.ops.blink_set( > parent, > classdev, > // SAFETY: The function's contract guarantees that `dela= y_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 `dela= y_off` points to a `usize` > // and is exclusive for the period of this function. > unsafe { &mut *delay_off }, [Severity: High] Does this code create overlapping mutable references when concurrent sysfs= =20 writes occur? The LED core's led_blink_set is invoked directly from sysfs writes=20 (delay_on_store and delay_off_store) without locking. When sysfs writes occ= ur=20 concurrently, multiple threads can pass pointers to the shared=20 led_cdev->blink_delay_off or led_cdev->blink_delay_on members into=20 led_blink_set simultaneously. Casting these raw pointers into exclusive mutable references (&mut usize)=20 without synchronization could result in two active overlapping &mut=20 references existing simultaneously on different threads, which is undefined= =20 behavior in Rust. > )?; > Ok(0) > }) > } > } > =20 > #[pinned_drop] > impl<'bound, T: LedOps + 'bound> PinnedDrop for Device<'bound, T> { > fn drop(self: Pin<&mut Self>) { > let raw =3D self.classdev.get(); > // SAFETY: The existence of `self` guarantees that `self.classde= v.get()` is a pointer to a > // valid `led_classdev`. > let dev: &device::Device =3D unsafe { device::Device::from_raw((= *raw).dev) }; > =20 > let _fwnode =3D dev > .fwnode() > // SAFETY: the reference count of `fwnode` has previously be= en > // incremented in `led::DeviceBuilder::build`. > .map(|fwnode| unsafe { ARef::from_raw(NonNull::from(fwnode))= }); [Severity: High] Will this lead to a reference count underflow if DeviceBuilder was created= =20 without an fwnode (self.fwnode =3D None)? If the kernel dynamically assigns an fwnode to the device (e.g., via ACPI o= r=20 parent inheritance), dev.fwnode() during drop will return Some(fwnode).=20 The PinnedDrop implementation unconditionally wraps this pointer in=20 ARef::from_raw and drops it, stealing a reference count it never owned.=20 Since the C device_unregister function natively cleans up its own fwnode=20 reference, could this additional drop cause a critical underflow and=20 subsequent use-after-free? --=20 Sashiko AI review =C2=B7 https://sashiko.dev/#/patchset/20260805-rust_leds-= [email protected]?part=3D1