All of lore.kernel.org
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Philipp Stanner" <phasta@kernel.org>
Cc: ojeda@kernel.org, media-ci@linuxtv.org, dri-devel@lists.freedesktop.org
Subject: Re: [PATCH v8 4/5] rust: Add dma_fence abstractions
Date: Fri, 31 Jul 2026 08:18:16 +0000	[thread overview]
Message-ID: <20260731081816.BB4ED1F00A3A@smtp.kernel.org> (raw)
In-Reply-To: <20260731080411.848772-6-phasta@kernel.org>

Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
- [High] Soundness bug in `ForeignOwnable` allows safe lifetime erasure, leading to UAF of `FenceContext`.
- [High] `Fence` objects safely outlive their `FenceContext`, leading to UAF in C callbacks.
- [High] Unconditional system-wide `rcu_barrier()` on `FenceContext` drop causes severe latency spikes.
- [High] Calling `Fence::is_signaled()` from a fence callback causes a hard deadlock.
- [Medium] `FenceContext::drop` does not signal leaked fences, causing permanent deadlocks for consumers.
- [Medium] `nr_of_unsignaled_fences` counter leaks if a fence is signaled via the C API.
--

--- Patch [4]: [PATCH v8 4/5] rust: Add dma_fence abstractions ---
commit baeb68fb999484f7d372bacbf53bbc08faf26f2e
Author: Philipp Stanner <phasta@kernel.org>
rust: Add dma_fence abstractions

This commit adds abstractions for dma_fence in Rust. It provides DriverFence 
and Fence types to cleanly decouple fence issuers and consumers, along with 
a FenceContext to handle sequencing.

> diff --git a/rust/kernel/dma_buf/dma_fence.rs b/rust/kernel/dma_buf/dma_fence.rs
> new file mode 100644
> index 0000000000000..d6322e062faff
> --- /dev/null
> +++ b/rust/kernel/dma_buf/dma_fence.rs
[ ... ]
> +    extern "C" fn get_driver_name(ptr: *mut bindings::dma_fence) -> *const c_char {
> +        // SAFETY: The C backend only invokes this callback with `ptr` pointing
> +        // to a valid, unsignaled `bindings::dma_fence`. All fences created in
> +        // this module always reside within `Fence` which always resides in a
> +        // `DriverFenceData`, thus satisfying the function's safety
> +        // requirements.
> +        let fctx = unsafe { Self::from_raw_fence(ptr) };
> +
> +        fctx.driver_name.as_char_ptr()
> +    }

[Severity: High]
Can Fence objects safely outlive their FenceContext, leading to a 
use-after-free here?

It looks like Fence implements AlwaysRefCounted but lacks a lifetime 
parameter connecting it to its FenceContext. If a user drops the DriverFence 
and then drops the FenceContext while the ARef to the Fence remains alive, 
callbacks like get_driver_name could access a dangling fctx pointer.

Could this happen if a consumer accesses the exported fence, for example 
through debugfs reading the fence driver name or through SYNC_IOC_FILE_INFO, 
triggering the C backend to call this function on a signaled fence?

[ ... ]
> +#[pinned_drop]
> +impl<T: FenceContextOps + Send + Sync> PinnedDrop for FenceContext<T> {
> +    fn drop(self: Pin<&mut Self>) {
> +        if self.nr_of_unsignaled_fences.load(Relaxed) > 0 {
> +            pr_err!("Unsignaled DriverFence(s) left in FenceContext. UAF possible.\n");
> +        }
> +
> +        // TODO:
> +        // It would be even more robust if the fence context signals all
> +        // forgotten fences itself. To do so, it would keep a list of unsignaled
> +        // fences. That list members would have to be pre-allocated (see
> +        // `FenceCallback::fence_alloc()`).

[Severity: Medium]
Will this cause waiting consumers to hang indefinitely?

The commit message notes that as a safety measure, if a FenceContext drops 
with an unsignaled fence present, it will signal it to ensure full 
decoupling. However, this code leaves a TODO and only prints a warning 
instead of actually signaling the leaked fences.

Does this mean that if a fence is forgotten without being signaled, any 
kernel threads waiting on it will enter a permanent deadlock?

> +
> +        rcu_barrier();
> +    }
> +}

[Severity: High]
Could calling rcu_barrier() unconditionally here cause severe latency spikes?

Since rcu_barrier() blocks the current thread until all pending call_rcu() 
callbacks across the entire kernel have completed, using it for regular 
object destruction seems problematic.

If GPU drivers frequently create and destroy fence contexts dynamically 
(such as per-process or per-timeline), could destroying a context become a 
denial of service vector by repeatedly blocking kernel threads?

[ ... ]
> +    pub fn is_signaled(&self) -> bool {
> +        // We should not use `dma_fence_is_signaled_locked()` here, because
> +        // according to the C backend's recommendations, that function is
> +        // problematic and we should avoid calling that function with a lock
> +        // held.
> +
> +        // SAFETY: `fence` stems from `self`, which is valid by definition.
> +        let ret = unsafe { bindings::dma_fence_is_signaled(self.as_raw()) };
> +
> +        // To be as robust as possible for the future we guarantee that an API
> +        // caller can 100% rely on the signalling being completed (i.e., all
> +        // fence callbacks ran), so we have to take the lock.
> +        //
> +        // The reason is that the C dma_fence backend currently does not
> +        // carefully synchronize the `dma_fence_is_signaled()` function with the
> +        // proper spinlock. This can lead to the function returning `true` while
> +        // fence callbacks are still being executed. This can be mitigated by
> +        // guarding the entire function with the spinlock.
> +        //
> +        // The fundamental reason is that the C backend currently does guard
> +        // setting of the fence's signaled-bit with the fence's spinlock, but
> +        // reading is done locklessly.
> +        //
> +        // See commit c8a5d5ea3ba6a.
> +
> +        let _ = self.lock();
> +
> +        ret
> +    }

[Severity: High]
Does acquiring the lock here cause a hard deadlock if called from a callback?

The is_signaled method intentionally acquires the fence spinlock to guarantee 
signaling completion. However, the C backend executes callbacks registered via 
FenceCallbackRegistration with this exact same spinlock already held.

If a user calls fence.is_signaled() from within their callback implementation, 
would it attempt to recursively acquire the non-recursive Linux spinlock?

[ ... ]
> +unsafe impl<T: Send + Sync + FenceContextOps + 'static> ForeignOwnable for DriverFence<'_, T> {
> +    type Borrowed<'a>
> +        = DriverFenceBorrow<'a, T>
> +    where
> +        Self: 'a;
> +    type BorrowedMut<'a>
> +        = DriverFenceBorrow<'a, T>
> +    where
> +        Self: 'a;
> +
> +    const FOREIGN_ALIGN: usize = core::mem::align_of::<bindings::dma_fence>();
> +
> +    fn into_foreign(self) -> *mut c_void {
> +        let fence = self;
> +
> +        let ptr = fence.as_raw();
> +
> +        // DriverFence must not drop.
> +        let _ = ManuallyDrop::new(fence);
> +
> +        ptr.cast()
> +    }

[Severity: High]
Does this implementation allow safe lifetime erasure of DriverFence?

The ForeignOwnable trait is implemented for DriverFence regardless of its 
lifetime. Because into_foreign is a safe method that returns a raw pointer, 
it completely erases the lifetime parameter of DriverFence.

Could safe Rust code call fence.into_foreign(), successfully ending the 
borrow of FenceContext from the compiler's perspective without dropping the 
fence? This might allow the FenceContext to be safely dropped, invalidating 
the fctx pointer and leading to a use-after-free when the kernel later 
accesses the exported fence.

[ ... ]
> +impl<'a, T: Send + Sync + FenceContextOps> Drop for DriverFence<'a, T> {
> +    fn drop(&mut self) {
> +        let guard = self.as_fence().lock();
> +
> +        // Use dma_fence_test_signaled_flag() instead of
> +        // dma_fence_is_signaled_locked() because the C backend wants to get rid
> +        // of the latter.
> +
> +        // SAFETY: `guard` is valid until the `call_rcu()` below.
> +        let signaled: bool = unsafe { bindings::dma_fence_test_signaled_flag(*guard.deref()) };
> +        if !signaled {
> +            pr_err!("DriverFence drops unsignaled. Danger of memory corruption!\n");
> +            // SAFETY: `guard` is valid until the `call_rcu()` below. The fence
> +            // must not have been signaled yet, which we check directly above.
> +            unsafe { bindings::dma_fence_set_error(*guard.deref(), ECANCELED.to_errno()) };
> +            // SAFETY: `guard` is valid until the `call_rcu()` below. The lock
> +            // must be held, which we acquired above.
> +            unsafe { bindings::dma_fence_signal_locked(*guard.deref()) };
> +
> +            // SAFETY: `self.data` is valid because `self` is valid.
> +            let fctx = unsafe { self.data.as_ref().fctx };
> +            let _ = fctx.nr_of_unsignaled_fences.fetch_sub(1, Relaxed);
> +        }
> +        drop(guard);

[Severity: Medium]
Can the nr_of_unsignaled_fences counter leak if the fence is signaled 
externally?

The logic here only decrements the counter if dma_fence_test_signaled_flag 
returns false. If another subsystem signals the fence through the C API, the 
signaled flag is set.

When this DriverFence is subsequently dropped, it appears it would skip the 
decrement, permanently leaking the counter. Would this cause false-positive 
memory corruption warnings later when the FenceContext is dropped?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260731080411.848772-2-phasta@kernel.org?part=4

  reply	other threads:[~2026-07-31  8:18 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-31  8:04 [PATCH v8 0/5] rust / dma_buf: Add abstractions for dma_fence Philipp Stanner
2026-07-31  8:04 ` [PATCH v8 1/5] rust: error: add remaining error codes Philipp Stanner
2026-07-31  8:04 ` [PATCH v8 2/5] rust: types: implement ForeignOwnable for ARef<T> Philipp Stanner
2026-07-31  8:04 ` [PATCH v8 3/5] rust: sync: Add abstraction for rcu_barrier() Philipp Stanner
2026-07-31  8:04 ` [PATCH v8 4/5] rust: Add dma_fence abstractions Philipp Stanner
2026-07-31  8:18   ` sashiko-bot [this message]
2026-07-31  8:04 ` [PATCH v8 5/5] MAINTAINERS: Add entry for Rust dma-buf Philipp Stanner

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=20260731081816.BB4ED1F00A3A@smtp.kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=dri-devel@lists.freedesktop.org \
    --cc=media-ci@linuxtv.org \
    --cc=ojeda@kernel.org \
    --cc=phasta@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.