dri-devel Archive on 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: 9+ 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-08-04 17:54   ` Daniel Almeida
2026-08-05  8:08     ` Philipp Stanner
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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox