Linux Serial subsystem development
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Markus Probst" <markus.probst@posteo.de>
Cc: ojeda@kernel.org, linux-serial@vger.kernel.org
Subject: Re: [PATCH 3/5] rust: serdev: Simplify callbacks
Date: Sun, 06 Sep 2026 16:13:58 +0000	[thread overview]
Message-ID: <20260906161359.713361F00A3A@smtp.kernel.org> (raw)
In-Reply-To: <20260906-rust_serdev_probe_refactor-v1-3-69cdae0074ec@posteo.de>

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] TOCTOU race condition causing a NULL pointer dereference in `receive_buf_callback` during device probe.
- [Low] Missing `#[inline]` annotations on abstraction functions `PrivateData::driver_data` and `OpenGuard::drop`.
--

commit 4244bd311fa3c091fa7a05ad05f18ce4e08829f6
Author: Markus Probst <markus.probst@posteo.de>
Subject: rust: serdev: Simplify callbacks

This commit simplifies serdev callbacks by initializing private data
directly on PrivateData. It also introduces OpenGuard for resource cleanup.

> diff --git a/rust/kernel/serdev.rs b/rust/kernel/serdev.rs
> index c16d6593a8d2d..66543108ec2fa 100644
> --- a/rust/kernel/serdev.rs
> +++ b/rust/kernel/serdev.rs
[ ... ]
> @@ -92,24 +88,35 @@ unsafe fn unregister(sdrv: &Opaque<Self::DriverType>) {
>      }
>  }
>  
> +struct OpenGuard<'bound> {
> +    sdev: &'bound Device<device::Bound>,
> +}
> +
> +impl Drop for OpenGuard<'_> {
> +    fn drop(&mut self) {
> +        // SAFETY:
> +        // - `self.sdev.as_raw()` is guaranteed to be a pointer to a valid
> +        //   `struct serdev_device`.
> +        // - The existence of self proves that the device is open.
> +        unsafe { bindings::serdev_device_close(self.sdev.as_raw()) };
> +    }
> +}

[Severity: Low]
Does this drop implementation need an #[inline] annotation?

The Rust subsystem guidelines state that in abstractions like the kernel crate,
small functions or functions forwarding to a C binding call should be annotated
with #[inline].

> +
>  #[doc(hidden)]
> -#[pin_data(PinnedDrop)]
> +#[pin_data]
>  pub struct PrivateData<'bound, T: Driver> {
> -    sdev: &'bound Device<device::Bound>,
>      #[pin]
> -    driver: UnsafeCell<MaybeUninit<T::Data<'bound>>>,
> -    open: UnsafeCell<bool>,
> +    driver: T::Data<'bound>,
> +    open: OpenGuard<'bound>,
>  }
>  
> -#[pinned_drop]
> -impl<T: Driver> PinnedDrop for PrivateData<'_, T> {
> -    fn drop(self: Pin<&mut Self>) {
> -        // SAFETY: We have exclusive access to `self.open`.
> -        if unsafe { *self.open.get() } {
> -            // SAFETY: `self.sdev.as_raw()` is guaranteed to be a pointer to a valid
> -            // `struct serdev_device`.
> -            unsafe { bindings::serdev_device_close(self.sdev.as_raw()) };
> -        }
> +impl<'bound, T: Driver> PrivateData<'bound, T> {
> +    fn driver_data(self: Pin<&Self>) -> Pin<&T::Data<'bound>> {
> +        // SAFETY: We treat the result as pinned.
> +        let inner = unsafe { Pin::into_inner_unchecked(self) };
> +
> +        // SAFETY: `self.driver` is pinned.
> +        unsafe { Pin::new_unchecked(&inner.driver) }
>      }
>  }

[Severity: Low]
Does this helper function also need an #[inline] annotation?

Similar to OpenGuard::drop above, this appears to be a small projection
function in an abstraction crate, which the subsystem guidelines suggest
annotating with #[inline].

> @@ -134,46 +141,30 @@ extern "C" fn probe_callback(sdev: *mut bindings::serdev_device) -> kernel::ffi:
>  
>          from_result(|| {
>              sdev.as_ref().set_drvdata(try_pin_init!(PrivateData::<T> {
> -                sdev: &**sdev,
> -                driver: MaybeUninit::<T::Data<'_>>::zeroed().into(),
> -                open: false.into(),
> +                open: {
> +                    // SAFETY:
> +                    // - `sdev.as_raw()` is guaranteed to be a valid pointer to
> +                    //   `serdev_device`.
> +                    // - It is safe to call before open.
> +                    unsafe { bindings::serdev_device_set_client_ops(sdev.as_raw(), Self::OPS) };
> +
> +                    // SAFETY: `sdev.as_raw()` is guaranteed to be a valid pointer to
> +                    // `serdev_device`.
> +                    unsafe { bindings::serdev_device_pause_rx(sdev.as_raw()) };
> +
> +                    // SAFETY: `sdev.as_raw()` is guaranteed to be a valid pointer to
> +                    // `serdev_device`.
> +                    to_result(unsafe { bindings::serdev_device_open(sdev.as_raw()) })?;
> +
> +                    OpenGuard { sdev }
> +                },
> +                driver <- T::probe(sdev, info),
>              }))?;

[Severity: High]
Could this sequence introduce a race condition that leads to a NULL pointer
dereference in receive_buf_callback() during device probe?

Because bindings::serdev_device_open() is called inside try_pin_init!(), the
device is exposed to hardware interrupts before the initialized struct is
passed to set_drvdata(). This means dev->driver_data remains NULL during this
window.

While bindings::serdev_device_pause_rx() is called before open, and
bindings::serdev_device_resume_rx() is called after set_drvdata() below,
resume_rx() uses clear_bit(SERPORT_PAUSE_RX) which lacks a memory barrier.

On weakly-ordered CPU architectures, can the CPU reorder the store to
dev->driver_data to happen after the atomic clear?

If a concurrent RX worker thread observes SERPORT_PAUSE_RX == 0 while
dev->driver_data is still NULL, it could trigger receive_buf_callback() and
crash when dereferencing the NULL pointer.

> -            // SAFETY: We just set drvdata to `PrivateData<'_, T>`.
> -            let private_data = unsafe { sdev.as_ref().drvdata_borrow::<PrivateData<'_, T>>() };
> -            let private_data = ScopeGuard::new_with_data(private_data, |_| {
> -                // SAFETY: We just set drvdata to `PrivateData<'_, T>`.
> -                drop(unsafe { sdev.as_ref().drvdata_obtain::<PrivateData<'_, T>>() });
> -            });
> -            // SAFETY: `sdev.as_raw()` is guaranteed to be a valid pointer to `serdev_device`.
> -            unsafe { bindings::serdev_device_set_client_ops(sdev.as_raw(), Self::OPS) };
>  
>              // SAFETY: `sdev.as_raw()` is guaranteed to be a valid pointer to `serdev_device`.
> -            unsafe { bindings::serdev_device_pause_rx(sdev.as_raw()) };
> -
> -            // SAFETY: The serial device bus only ever calls the probe callback with a valid pointer
> -            // to a `serdev_device`.
> -            to_result(unsafe { bindings::serdev_device_open(sdev.as_raw()) })?;
> -
> -            // SAFETY: We have exclusive access to `private_data.open`.
> -            unsafe { *private_data.open.get() = true };
> -
> -            let data = T::probe(sdev, info);
> +            unsafe { bindings::serdev_device_resume_rx(sdev.as_raw()) };
>  
> -            // SAFETY: We have exclusive access to `private_data.driver`.
> -            let driver = unsafe { &mut *private_data.driver.get() };
> -            // SAFETY:
> -            // - `driver.as_mut_ptr()` is a valid pointer to uninitialized data.
> -            // - `private_data.driver` is pinned.
> -            let result = unsafe { pin_init::raw_try_init(driver.as_mut_ptr(), data) };
> -
> -            result.map(|()| {
> -                private_data.dismiss();
> -
> -                // SAFETY: `sdev.as_raw()` is guaranteed to be a valid pointer to `serdev_device`.
> -                unsafe { bindings::serdev_device_resume_rx(sdev.as_raw()) };
> -
> -                0
> -            })
> +            Ok(0)
>          })
>      }

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260906-rust_serdev_probe_refactor-v1-0-69cdae0074ec@posteo.de?part=3

  reply	other threads:[~2026-09-06 16:13 UTC|newest]

Thread overview: 15+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-06 15:55 [PATCH 0/5] rust: serdev: Refactor Markus Probst
2026-09-06 15:55 ` [PATCH 1/5] tty: serdev: Export functions to pause receive_buf callback calls Markus Probst
2026-09-06 16:08   ` sashiko-bot
2026-09-06 15:55 ` [PATCH 2/5] rust: serdev: Replace `active` mutex with receive pause Markus Probst
2026-09-06 16:09   ` sashiko-bot
2026-09-06 15:55 ` [PATCH 3/5] rust: serdev: Simplify callbacks Markus Probst
2026-09-06 16:13   ` sashiko-bot [this message]
2026-09-06 15:55 ` [PATCH 4/5] rust: Add `Device::drvdata_borrow_mut` Markus Probst
2026-09-06 16:08   ` sashiko-bot
2026-09-06 15:55 ` [PATCH 5/5] rust: serdev: Pause receive callback before calling unbind Markus Probst
2026-09-06 16:11   ` sashiko-bot
2026-09-06 16:20   ` Danilo Krummrich
2026-09-06 17:36     ` Markus Probst
2026-09-06 20:13     ` Gary Guo
2026-09-06 22:51       ` Markus Probst

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=20260906161359.713361F00A3A@smtp.kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=linux-serial@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