All of lore.kernel.org
 help / color / mirror / Atom feed
From: "Benno Lossin" <lossin@kernel.org>
To: "Danilo Krummrich" <dakr@kernel.org>,
	<gregkh@linuxfoundation.org>, <rafael@kernel.org>,
	<ojeda@kernel.org>, <alex.gaynor@gmail.com>,
	<boqun.feng@gmail.com>, <gary@garyguo.net>,
	<bjorn3_gh@protonmail.com>, <benno.lossin@proton.me>,
	<a.hindborg@kernel.org>, <aliceryhl@google.com>,
	<tmgross@umich.edu>
Cc: <rust-for-linux@vger.kernel.org>, <linux-kernel@vger.kernel.org>
Subject: Re: [PATCH 3/4] rust: devres: get rid of Devres' inner Arc
Date: Sun, 22 Jun 2025 09:05:51 +0200	[thread overview]
Message-ID: <DASVDU1WY5RH.1VLCIQ4TIS0FP@kernel.org> (raw)
In-Reply-To: <20250612145145.12143-4-dakr@kernel.org>

On Thu Jun 12, 2025 at 4:51 PM CEST, Danilo Krummrich wrote:
> So far Devres uses an inner memory allocation and reference count, i.e.
> an inner Arc, in order to ensure that the devres callback can't run into
> a use-after-free in case where the Devres object is dropped while the
> devres callback runs concurrently.
>
> Instead, use a completion in order to avoid a potential UAF: In
> Devres::drop(), if we detect that we can't remove the devres action
> anymore, we wait for the completion that is completed from the devres
> callback. If, in turn, we were able to successfully remove the devres
> action, we can just go ahead.
>
> This, again, allows us to get rid of the internal Arc, and instead let
> Devres consume an `impl PinInit<T, E>` in order to return an
> `impl PinInit<Devres<T>, E>`, which enables us to get away with less
> memory allocations.
>
> Additionally, having the resulting explicit synchronization in
> Devres::drop() prevents potential subtle undesired side effects of the
> devres callback dropping the final Arc reference asynchronously within
> the devres callback.
>
> Signed-off-by: Danilo Krummrich <dakr@kernel.org>

This is really nice, good to see the extra allocations gone :)

> ---
>  drivers/gpu/nova-core/driver.rs |   7 +-
>  drivers/gpu/nova-core/gpu.rs    |   6 +-
>  rust/kernel/devres.rs           | 187 +++++++++++++++-----------------
>  rust/kernel/pci.rs              |  20 ++--
>  samples/rust/rust_driver_pci.rs |  19 ++--
>  5 files changed, 117 insertions(+), 122 deletions(-)

> @@ -86,100 +76,93 @@ struct DevresInner<T> {
>  /// # fn no_run(dev: &Device<Bound>) -> Result<(), Error> {
>  /// // SAFETY: Invalid usage for example purposes.
>  /// let iomem = unsafe { IoMem::<{ core::mem::size_of::<u32>() }>::new(0xBAAAAAAD)? };
> -/// let devres = Devres::new(dev, iomem, GFP_KERNEL)?;
> +/// let devres = KBox::pin_init(Devres::new(dev, iomem), GFP_KERNEL)?;
>  ///
>  /// let res = devres.try_access().ok_or(ENXIO)?;
>  /// res.write8(0x42, 0x0);
>  /// # Ok(())
>  /// # }
>  /// ```
> -pub struct Devres<T>(Arc<DevresInner<T>>);
> -
> -impl<T> DevresInner<T> {
> -    fn new(dev: &Device<Bound>, data: T, flags: Flags) -> Result<Arc<DevresInner<T>>> {
> -        let inner = Arc::pin_init(
> -            try_pin_init!( DevresInner {
> -                dev: dev.into(),
> -                callback: Self::devres_callback,
> -                data <- Revocable::new(data),
> -                revoke <- Completion::new(),
> -            }),
> -            flags,
> -        )?;
> -
> -        // Convert `Arc<DevresInner>` into a raw pointer and make devres own this reference until
> -        // `Self::devres_callback` is called.
> -        let data = inner.clone().into_raw();
> +#[pin_data(PinnedDrop)]
> +pub struct Devres<T> {
> +    dev: ARef<Device>,
> +    callback: unsafe extern "C" fn(*mut c_void),

Do I remember correctly that we at some point talked about adding a
comment here for why this is needed? (ie it's needed, because
`Self::callback` might return different addresses?)

> +    #[pin]
> +    data: Revocable<T>,
> +    #[pin]
> +    devm: Completion,
> +    #[pin]
> +    revoke: Completion,

Probably a good idea to add some doc comments explaining what these two
completions track.

(feel free to do these in another patch or in a follow-up)

> +}
>  
> -        // SAFETY: `devm_add_action` guarantees to call `Self::devres_callback` once `dev` is
> -        // detached.
> -        let ret =
> -            unsafe { bindings::devm_add_action(dev.as_raw(), Some(inner.callback), data as _) };
> +impl<T> Devres<T> {
> +    /// Creates a new [`Devres`] instance of the given `data`. The `data` encapsulated within the

Missing double newline after the first sentence.

> +    /// returned `Devres` instance' `data` will be revoked once the device is detached.

Maybe we should link to `Revocable` on the word `revoked`?

> +    pub fn new<'a, E>(
> +        dev: &'a Device<Bound>,
> +        data: impl PinInit<T, E> + 'a,
> +    ) -> impl PinInit<Self, Error> + 'a
> +    where
> +        T: 'a,
> +        Error: From<E>,
> +    {
> +        let callback = Self::devres_callback;

> -        Ok(Devres(inner))
> +    fn remove_action(&self) -> bool {
> +        // SAFETY:
> +        // - `self.dev` is a valid `Device`,
> +        // - the `action` and `data` pointers are the exact same ones as given to devm_add_action()
> +        //   previously,
> +        // - `self` is always valid, even if the action has been released already.
> +        (unsafe {
> +            bindings::devm_remove_action_nowarn(
> +                self.dev.as_raw(),
> +                Some(self.callback),
> +                self.as_ptr().cast_mut().cast(),
> +            )
> +        } == 0)

I don't think the parenthesis are required?

>      }
>  
>      /// Obtain `&'a T`, bypassing the [`Revocable`].

> -impl<T> Drop for Devres<T> {
> -    fn drop(&mut self) {
> +#[pinned_drop]
> +impl<T> PinnedDrop for Devres<T> {
> +    fn drop(self: Pin<&mut Self>) {
>          // SAFETY: When `drop` runs, it is guaranteed that nobody is accessing the revocable data
>          // anymore, hence it is safe not to wait for the grace period to finish.
> -        if unsafe { self.0.data.revoke_nosync() } {
> -            // We revoked `self.0.data` before the devres action did, hence try to remove it.
> -            if !DevresInner::remove_action(&self.0) {
> +        if unsafe { self.data.revoke_nosync() } {
> +            // We revoked `self.data` before the devres action did, hence try to remove it.
> +            if !self.remove_action() {
>                  // We could not remove the devres action, which means that it now runs concurrently,
> -                // hence signal that `self.0.data` has been revoked successfully.
> -                self.0.revoke.complete_all();
> +                // hence signal that `self.data` has been revoked by us successfully.
> +                self.revoke.complete_all();
> +
> +                // Wait for `Self::devres_callback` to be done using this object.
> +                self.devm.wait_for_completion();
>              }
> +        } else {
> +            // `Self::devres_callback` revokes `self.data` for us, hence wait for it to be done
> +            // using this object.
> +            self.devm.wait_for_completion();

I don't understand this change, maybe it's best to move that into a
separate commit?

---
Cheers,
Benno

  reply	other threads:[~2025-06-22  7:05 UTC|newest]

Thread overview: 24+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-06-12 14:51 [PATCH 0/4] Improvements for Devres Danilo Krummrich
2025-06-12 14:51 ` [PATCH 1/4] rust: revocable: support fallible PinInit types Danilo Krummrich
2025-06-12 15:48   ` Benno Lossin
2025-06-12 15:58     ` Danilo Krummrich
2025-06-12 16:17       ` Benno Lossin
2025-06-12 16:20         ` Danilo Krummrich
2025-06-12 14:51 ` [PATCH 2/4] rust: devres: replace Devres::new_foreign_owned() Danilo Krummrich
2025-06-13  3:14   ` Viresh Kumar
2025-06-21 21:10   ` Benno Lossin
2025-06-21 21:45     ` Danilo Krummrich
2025-06-22  7:42       ` Benno Lossin
2025-06-22  9:55         ` Danilo Krummrich
2025-06-22 20:18           ` Benno Lossin
2025-06-12 14:51 ` [PATCH 3/4] rust: devres: get rid of Devres' inner Arc Danilo Krummrich
2025-06-22  7:05   ` Benno Lossin [this message]
2025-06-22 12:08     ` Danilo Krummrich
2025-06-22 20:16       ` Benno Lossin
2025-06-22 15:45     ` Danilo Krummrich
2025-06-22 20:15       ` Benno Lossin
2025-06-12 14:51 ` [PATCH 4/4] rust: devres: implement register_foreign_release() Danilo Krummrich
2025-06-22  7:26   ` Benno Lossin
2025-06-22 12:46     ` Danilo Krummrich
2025-06-22 20:14       ` Benno Lossin
2025-06-22 20:25         ` Danilo Krummrich

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=DASVDU1WY5RH.1VLCIQ4TIS0FP@kernel.org \
    --to=lossin@kernel.org \
    --cc=a.hindborg@kernel.org \
    --cc=alex.gaynor@gmail.com \
    --cc=aliceryhl@google.com \
    --cc=benno.lossin@proton.me \
    --cc=bjorn3_gh@protonmail.com \
    --cc=boqun.feng@gmail.com \
    --cc=dakr@kernel.org \
    --cc=gary@garyguo.net \
    --cc=gregkh@linuxfoundation.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=ojeda@kernel.org \
    --cc=rafael@kernel.org \
    --cc=rust-for-linux@vger.kernel.org \
    --cc=tmgross@umich.edu \
    /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.