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 --]
next prev parent 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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox