All of lore.kernel.org
 help / color / mirror / Atom feed
From: Markus Probst <markus.probst@posteo.de>
To: sashiko-reviews@lists.linux.dev
Cc: ojeda@kernel.org, linux-serial@vger.kernel.org
Subject: Re: [PATCH] rust: serdev: Synchronize receive callback before calling unbind
Date: Thu, 03 Sep 2026 22:31:33 +0000	[thread overview]
Message-ID: <c284466700477e9c69cd433db90baf273bef764b.camel@posteo.de> (raw)
In-Reply-To: <20260903222159.70A911F000E9@smtp.kernel.org>

[-- Attachment #1: Type: text/plain, Size: 6647 bytes --]

On Thu, 2026-09-03 at 22:21 +0000, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
> 
> New issues:
> - [High] Holding the `active` mutex during `T::unbind` causes an inescapable deadlock for drivers that synchronously communicate with the device.
> - [Low] Logically dead code inside the error-handling `ScopeGuard` in `probe_callback`.
> 
> Pre-existing issues:
> - [High] Race condition between drvdata_obtain and receive_buf_callback causes NULL pointer dereference or Use-After-Free.
> --
> 
> commit 6f561db35778bb34c19e46b1a5df116263c0adbc
> Author: Markus Probst <markus.probst@posteo.de>
> Subject: rust: serdev: Synchronize receive callback before calling unbind
> 
> The commit changes the receive and unbind callbacks to provide exclusive
> access to driver private data by handing out mutable references. It removes
> the Sync requirement for Data and introduces locking for synchronization.
> 
> > diff --git a/rust/kernel/serdev.rs b/rust/kernel/serdev.rs
> > index 17ca504b7f8d6..44f029ed93fd1 100644
> > --- a/rust/kernel/serdev.rs
> > +++ b/rust/kernel/serdev.rs
> 
> [ ... ]
> 
> > @@ -127,16 +127,6 @@ pub struct PrivateData<'bound, T: Driver> {
> >  #[pinned_drop]
> >  impl<T: Driver> PinnedDrop for PrivateData<'_, T> {
> >      fn drop(self: Pin<&mut Self>) {
> > -        let mut active = self.active.lock();
> > -        if *active {
> > -            // SAFETY:
> > -            // - We have exclusive access to `self.driver`.
> > -            // - `self.driver` is guaranteed to be initialized.
> > -            unsafe { (*self.driver.get()).assume_init_drop() };
> > -            *active = false;
> > -        }
> > -        drop(active);
> > -
> >          // SAFETY: We have exclusive access to `self.open`.
> >          if unsafe { *self.open.get() } {
> 
> [Severity: High]
> This isn't a bug introduced by this patch, but does the timing of closing the
> device cause a use-after-free or NULL pointer dereference?
> 
> If probe fails after the device is opened, or when the driver is unbound,
> drvdata_obtain() clears the underlying C driver_data pointer to NULL and
> drops the data. However, bindings::serdev_device_close(self.sdev.as_raw())
> is only called later inside PrivateData::drop().
> 
> Because the device is still open while the pointer is cleared, incoming
> serial data can trigger receive_buf_callback() in the TTY workqueue.
> 
> receive_buf_callback() calls drvdata_borrow(), which reads the NULL pointer
> and blindly dereferences it, or reads it right before it's cleared and
> accesses freed memory.
> 
> Should the device be closed before clearing the drvdata pointer?
This definitely needs to be addressed, yes.
> 
> [ ... ]
> 
> > @@ -176,7 +166,20 @@ extern "C" fn probe_callback(sdev: *mut bindings::serdev_device) -> kernel::ffi:
> >              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>>() });
> > +                let private_data = unsafe {
> > +                    sdev.as_ref()
> > +                        .drvdata_obtain::<PrivateData<'_, T>>()
> > +                        .unwrap_unchecked()
> > +                };
> > +
> > +                let mut active = private_data.active.lock();
> > +                if *active {
> 
> [Severity: Low]
> Is this block of code logically dead?
> 
> The ScopeGuard closure checks if *active is true to selectively call
> assume_init_drop(). However, *active is only set to true if the driver
> initialization succeeds.
> 
> Immediately after setting *active = true, private_data.dismiss() is called,
> which consumes the guard and prevents this closure from running.
> 
> Therefore, this closure only runs on the error path, where *active is
> strictly false, meaning the assume_init_drop() block will never execute.
True.

> 
> > +                    // SAFETY:
> > +                    // - We have exclusive access to `private_data.driver`.
> > +                    // - `private_data.driver` is guaranteed to be initialized.
> > +                    unsafe { (*private_data.driver.get()).assume_init_drop() };
> > +                    *active = false;
> > +                }
> >              });
> >              let mut active = private_data.active.lock();
> 
> [ ... ]
> 
> > @@ -222,15 +225,21 @@ extern "C" fn remove_callback(sdev: *mut bindings::serdev_device) {
> >          // and stored a `Pin<KBox<PrivateData<'_, T>>>`.
> >          let private_data = unsafe { sdev.as_ref().drvdata_borrow::<PrivateData<'_, T>>() };
> >  
> > -        // SAFETY: No one has exclusive access to `private_data.driver`.
> > -        let data = unsafe { &*private_data.driver.get() };
> > +        let mut active = private_data.active.lock();
> > +
> > +        // SAFETY: We have exclusive access to `private_data.driver`.
> > +        let data = unsafe { &mut *private_data.driver.get() };
> >          // SAFETY:
> >          // - `private_data.driver` is pinned.
> >          // - `remove_callback` is only ever called after a successful call to `probe_callback`,
> >          //   hence it's guaranteed that `private_data.driver` was initialized.
> > -        let data_pinned = unsafe { Pin::new_unchecked(data.assume_init_ref()) };
> > +        let data_pinned = unsafe { Pin::new_unchecked(data.assume_init_mut()) };
> >  
> >          T::unbind(sdev, data_pinned);
> 
> [Severity: High]
> Does holding the active mutex during T::unbind() cause a deadlock for
> drivers that synchronously communicate with the device?
> 
> If the driver's unbind() callback sends a shutdown command to the device and
> synchronously waits for an acknowledgment, the incoming serial data for the
> acknowledgment will be routed to receive_buf_callback().
> 
> receive_buf_callback() attempts to acquire this same active mutex to process
> the response, completing the deadlock because remove_callback() is blocked
> waiting for the acknowledgment.
That would be a driver issue.

And there shouldn't be a reason for a driver to wait for an
acknowledgement.

> 
> > +
> > +        // SAFETY: We already established that `data` is guaranteed to be initialized.
> > +        unsafe { data.assume_init_drop() };
> > +        *active = false;
> >      }

[-- Attachment #2: This is a digitally signed message part --]
[-- Type: application/pgp-signature, Size: 870 bytes --]

  reply	other threads:[~2026-09-03 22:31 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-03 22:03 [PATCH] rust: serdev: Synchronize receive callback before calling unbind Markus Probst
2026-09-03 22:21 ` sashiko-bot
2026-09-03 22:31   ` Markus Probst [this message]
2026-09-03 22:34 ` 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=c284466700477e9c69cd433db90baf273bef764b.camel@posteo.de \
    --to=markus.probst@posteo.de \
    --cc=linux-serial@vger.kernel.org \
    --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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.