Skip to contentSkip to conversation

rust / dma_buf: Add abstractions for dma_fence

PATCH Superseded Philipp Stanner 9 participants 71 messages May 30 – Jun 5

Subsystems (6)

v1 of 11 Superseded Latest is v11 →

lore
Share & tools
lore

Philipp Stanner

author maintainer
#0 lore raw

cover rust / dma_buf: Add abstractions for dma_fence

Changes since the RFC:
- Include support for ForeignOwnable for ARef, so that a Fence can be stuffed into an XArray et al. (Code by Danilo)
- Implement ForeignOwnable (with new borrow type) for DriverFence, so that it can be stuffed into an XArray.
- Include the rcu::RcuBox data type to defer dropping data with RCU (Cody by Alice)
- Port DmaFence to RcuBox to make UAF bugs through later, new dma_fence callbacks (backend_ops) impossible.
- Force users to pass their fence data in an RcuBox (or have it not need drop()) through a Sealed trait.
- Document the rules for the user's DriverFence::data's drop implementation very clearly (deadlock danger).
- rustfmt, Clippy.
- Various style suggestions, safety comments, etc. (Önur)
- Add __rust_helper prefix to helper functions. (Önur)

Changes in RFC v3:
- Omit JobQueue patches for now
- Completely redesign the memory layout: Instead of a Fence refcounting a DriverFence, both now live in the same allocation to allow for future support the dma_fence backend_ops callbacks which need to do container_of. (mostly Boris's feedback)
- Allow for pre-allocating fences to avoid deadlocks when submitting jobs to a GPU. (Boris)
- Simultaneously, allow for pre-preparing fence callback objects, so the driver can allocate them when it sees fit. (code largely stolen and inspired by Daniel).
- Signal fences on drop, ensure synchronization.
- Force users to set an error code when signalling.
- Write more documentation
- A ton of minor other changes.

Alright, so since the last RFCs did not reveal significant design issues, I decided to transition this series to a v1 and hope that we can get it upstream.

This now includes code for more common infrastructure that dma_fence needs, contributed by Danilo and Alice.

---

Old cover letter for RFC:

So, this is the spiritual successor of the first / second RFC [1]. v2 also contained code for drm::JobQueue, but mostly to show how the fence code would be used. JobQueue is under heavy rework right now, so I don't want to bother your eyes with it. The docstring examples should show how Rust fences are supposed to be used, though.

This v3 contains a huge amount of highly valuable feedback from a variety of people, notably Boris, but also from Alice, Gary and Danilo.

There are some TODOs open (a better trait for fence backend_ops and RCU support), but my hope is that this effort is now finally approaching its end.

I would greatly appreciate feedback and especially more information about what might be missing to make this usable, which is obviously where Daniel's and Boris's feedback will be valuable once more.

Please regard this patch just as what it's titled: an RFC, to discuss a bit more and to inform a broader community about what the current state is and where this is heading at.

Many regards,
Philipp

[1] https://lore.kernel.org/rust-for-linux/[email protected]/

Alice Ryhl (1):
rust: rcu: add RcuBox type

Danilo Krummrich (1):
rust: types: implement ForeignOwnable for ARef<T>

Philipp Stanner (2):
rust: Add dma_fence abstractions
MAINTAINERS: Add entry for Rust dma-buf

 MAINTAINERS                      |   2 +
 rust/bindings/bindings_helper.h  |   2 +
 rust/helpers/dma_fence.c         |  48 ++
 rust/helpers/helpers.c           |   1 +
 rust/kernel/dma_buf/dma_fence.rs | 821 +++++++++++++++++++++++++++++++
 rust/kernel/dma_buf/mod.rs       |  13 +
 rust/kernel/lib.rs               |   1 +
 rust/kernel/sync/aref.rs         |  39 ++
 rust/kernel/sync/rcu.rs          |  31 +-
 rust/kernel/sync/rcu/rcu_box.rs  | 145 ++++++
 10 files changed, 1102 insertions(+), 1 deletion(-)
 create mode 100644 rust/helpers/dma_fence.c
 create mode 100644 rust/kernel/dma_buf/dma_fence.rs
 create mode 100644 rust/kernel/dma_buf/mod.rs
 create mode 100644 rust/kernel/sync/rcu/rcu_box.rs
Signature · 1 line
2.54.0
author maintainer
#1 lore raw

PATCH 1/4 rust: types: implement ForeignOwnable for ARef<T>

From: Danilo Krummrich <[email protected]>

Implement ForeignOwnable for ARef<T>, making it possible for C code to own an ARef<T>.

Since ARef represents shared ownership, BorrowedMut is &T rather than &mut T, matching the semantics of the underlying reference-counted type.

---
 rust/kernel/sync/aref.rs | 39 +++++++++++++++++++++++++++++++++++++++
 1 file changed, 39 insertions(+)
rust/kernel/sync/aref.rs +39 -0
index 9989f56d0605..82907383c44b 100644
@@ -17,6 +17,10 @@
 //! [`Arc`]: crate::sync::Arc
 //! [`Arc<T>`]: crate::sync::Arc
 
+use crate::{
+    prelude::*,
+    types::ForeignOwnable, //
+};
 use core::{marker::PhantomData, mem::ManuallyDrop, ops::Deref, ptr::NonNull};
 
 /// Types that are _always_ reference counted.
@@ -183,6 +187,41 @@ fn eq(&self, other: &ARef<U>) -> bool {
 }
 impl<T: AlwaysRefCounted + Eq> Eq for ARef<T> {}
 
+// SAFETY: `into_foreign` returns a pointer from `NonNull::as_ptr`, so it's non-null. The
+// `ARef` invariant guarantees that `ptr` points to a valid `T`, so it's aligned to `T`.
+unsafe impl<T: AlwaysRefCounted + 'static> ForeignOwnable for ARef<T> {
+    const FOREIGN_ALIGN: usize = core::mem::align_of::<T>();
+
+    type Borrowed<'a> = &'a T;
+    type BorrowedMut<'a> = &'a T;
+
+    fn into_foreign(self) -> *mut c_void {
+        ARef::into_raw(self).as_ptr().cast()
+    }
+
+    unsafe fn from_foreign(ptr: *mut c_void) -> Self {
+        // SAFETY: The safety requirements of this function ensure that `ptr` comes from a previous
+        // call to `Self::into_foreign`.
+        let ptr = unsafe { NonNull::new_unchecked(ptr.cast()) };
+
+        // SAFETY: `ptr` came from `into_foreign`, which consumed an `ARef` without decrementing
+        // the refcount, so we can transfer the ownership to the new `ARef`.
+        unsafe { ARef::from_raw(ptr) }
+    }
+
+    unsafe fn borrow<'a>(ptr: *mut c_void) -> &'a T {
+        // SAFETY: The safety requirements of this method ensure that the object remains alive and
+        // immutable for the duration of 'a.
+        unsafe { &*ptr.cast() }
+    }
+
+    unsafe fn borrow_mut<'a>(ptr: *mut c_void) -> &'a T {
+        // SAFETY: The safety requirements for `borrow_mut` are a superset of the safety
+        // requirements for `borrow`.
+        unsafe { <Self as ForeignOwnable>::borrow(ptr) }
+    }
+}
+
 impl<T, U> PartialEq<&'_ U> for ARef<T>
 where
     T: AlwaysRefCounted + PartialEq<U>,
Signature · 1 line
2.54.0
author maintainer
#4 lore raw

PATCH 2/4 rust: rcu: add RcuBox type

In reply to Philipp Stanner

From: Alice Ryhl <[email protected]>

This adds an RcuBox container, which is like KBox except that the value is freed with kfree_rcu.

To allow containers to rely on the rcu properties of RcuBox, an extension of ForeignOwnable is added.

---
 rust/bindings/bindings_helper.h |   1 +
 rust/kernel/sync/rcu.rs         |  31 ++++++-
 rust/kernel/sync/rcu/rcu_box.rs | 145 ++++++++++++++++++++++++++++++++
 3 files changed, 176 insertions(+), 1 deletion(-)
 create mode 100644 rust/kernel/sync/rcu/rcu_box.rs
rust/bindings/bindings_helper.h +1 -0
index 446dbeaf0866..2011645c7cfb 100644
@@ -80,6 +80,7 @@
 #include <linux/property.h>
 #include <linux/pwm.h>
 #include <linux/random.h>
+#include <linux/rcupdate.h>
 #include <linux/refcount.h>
 #include <linux/regulator/consumer.h>
 #include <linux/sched.h>
rust/kernel/sync/rcu.rs +30 -1
index a32bef6e490b..7234fe3e79ee 100644
@@ -4,7 +4,16 @@
 //!
 //! C header: [`include/linux/rcupdate.h`](srctree/include/linux/rcupdate.h)
 
-use crate::{bindings, types::NotThreadSafe};
+use crate::{
+    bindings,
+    types::{
+        ForeignOwnable,
+        NotThreadSafe, //
+    }, //
+};
+
+mod rcu_box;
+pub use self::rcu_box::RcuBox;
 
 /// Evidence that the RCU read side lock is held on the current thread/CPU.
 ///
@@ -50,3 +59,23 @@ fn drop(&mut self) {
 pub fn read_lock() -> Guard {
     Guard::new()
 }
+
+/// Declares that a pointer type is rcu safe.
+pub trait ForeignOwnableRcu: ForeignOwnable {
+    /// Type used to immutably borrow an rcu-safe value that is currently foreign-owned.
+    type RcuBorrowed<'a>;
+
+    /// Borrows a foreign-owned object immutably for an rcu grace period.
+    ///
+    /// This method provides a way to access a foreign-owned rcu-safe value from Rust immutably.
+    ///
+    /// # Safety
+    ///
+    /// * The provided pointer must have been returned by a previous call to [`into_foreign`].
+    /// * If [`from_foreign`] is called, then `'a` must not end after the call to `from_foreign`
+    ///   plus one rcu grace period.
+    ///
+    /// [`into_foreign`]: ForeignOwnable::into_foreign
+    /// [`from_foreign`]: ForeignOwnable::from_foreign
+    unsafe fn rcu_borrow<'a>(ptr: *mut ffi::c_void) -> Self::RcuBorrowed<'a>;
+}
rust/kernel/sync/rcu/rcu_box.rs new file +145 -0

145 lines not on this page · raw message

Signature · 1 line
2.54.0
author maintainer
#15 lore raw

PATCH 3/4 rust: Add dma_fence abstractions

In reply to Philipp Stanner

C's dma_fence's are synchronisation primitives that will be needed by all Rust GPU drivers.

The dma_fence framework sets a number of rules, notably:
- fences must only be signalled once
- all fences must be signalled at some point
- fence error codes must only be set before signalling
- every pointer to a fence must be backed by a reference

All those rules are being addressed by these abstractions.

To cleanly decouple fence issuers and consumers, two types are provided:
- DriverFence: the only fence type that can be signalled and that carries driver-specific data.
- Fence: the fence type to be shared with other drivers and / or userspace. The only type callbacks can be registered on. Cannot be signalled.

Hereby, a Fence lives in the same chunk of memory as a DriverFence. Both share the refcount of the underlying C dma_fence. Since this implementation does not provide a custom dma_fence_backend_ops.release() function, the memory is freed by the dma_fence backend once the refcount drops to 0.

To create a DriverFence, the user must first allocate a DriverFenceAllocation, so that the creation of the DriverFence later on can always succeed. Otherwise, deadlocks could occur if fences need to be created in a GPU job submission path.

Synchronization is ensured by the dma_fence backend.

All DriverFence's created through this abstraction must be signalled by the creator with an error code. In case a DriverFence drops without being signalled beforehand, it is signalled with -ECANCELLED as its error and a warning is printed. This allows the Rust abstraction to very cleanly decouple fence issuer and consumer by relying on the decoupling mechanisms in the C backend, which ensures through RCU and the 'signalled' fence-flag that dma_fence_backend_ops functions cannot access the potentially unloaded driver code anymore.

Signalling fences on drop thus grants many advantages. Not signalling fences on drop would risk deadlock and does not grant real advantages:
By definition only the drivers can ensure that a fence always represents the hardware's state correctly.

This implementation models a DmaFenceCtx (fence context) object on which fences are to be created, thereby ensuring correct sequence numbering according to the timeline.

dma_fence supports a variety of callbacks. The mandatory callbacks (get_timeline_name() and get_driver_name()) are implemented in this patch. For convenience, they store those name parameters in the fence context, saving the driver from implementing these two callbacks.

Support for other callbacks (like for hardware signalling) is prepared for through the fact that both DriverFence and Fence live in the same allocation, allowing for usage of container_of from the callback to access the driver-specific data.

Synchronization for backend_ops callbacks is ensured through RCU which prevents UAF-bugs should a DriverFence drop while a Fence callback is currently operating on the associated driver data.

Add abstractions for dma_fence in Rust.

---
 rust/bindings/bindings_helper.h  |   1 +
 rust/helpers/dma_fence.c         |  48 ++
 rust/helpers/helpers.c           |   1 +
 rust/kernel/dma_buf/dma_fence.rs | 821 +++++++++++++++++++++++++++++++
 rust/kernel/dma_buf/mod.rs       |  13 +
 rust/kernel/lib.rs               |   1 +
 6 files changed, 885 insertions(+)
 create mode 100644 rust/helpers/dma_fence.c
 create mode 100644 rust/kernel/dma_buf/dma_fence.rs
 create mode 100644 rust/kernel/dma_buf/mod.rs
rust/bindings/bindings_helper.h +1 -0
index 2011645c7cfb..69daeb790f77 100644
@@ -52,6 +52,7 @@
 #include <linux/debugfs.h>
 #include <linux/device/faux.h>
 #include <linux/dma-direction.h>
+#include <linux/dma-fence.h>
 #include <linux/dma-mapping.h>
 #include <linux/dma-resv.h>
 #include <linux/errname.h>
rust/helpers/dma_fence.c new file +48 -0

48 lines not on this page · raw message

rust/helpers/helpers.c +1 -0
index 625921e27dfb..d9114d0b3c8f 100644
@@ -57,6 +57,7 @@
 #include "cred.c"
 #include "device.c"
 #include "dma.c"
+#include "dma_fence.c"
 #include "dma-resv.c"
 #include "drm.c"
 #include "err.c"
rust/kernel/dma_buf/dma_fence.rs new file +821 -0

821 lines not on this page · raw message

rust/kernel/dma_buf/mod.rs new file +13 -0
new file mode 100644
index 000000000000..d9da3dc57fce
@@ -0,0 +1,13 @@
+// SPDX-License-Identifier: GPL-2.0 OR MIT
+
+//! DMA-buf subsystem abstractions.
+
+pub mod dma_fence;
+
+pub use self::dma_fence::{
+    DriverFence,
+    Fence,
+    FenceCb,
+    FenceCbRegistration,
+    FenceCtx, //
+};
rust/kernel/lib.rs +1 -0
index b72b2fbe046d..a05ccaa7598c 100644
@@ -63,6 +63,7 @@
 pub mod device_id;
 pub mod devres;
 pub mod dma;
+pub mod dma_buf;
 pub mod driver;
 #[cfg(CONFIG_DRM = "y")]
 pub mod drm;
Signature · 1 line
2.54.0
author maintainer
#68 lore raw

PATCH 4/4 MAINTAINERS: Add entry for Rust dma-buf

In reply to Philipp Stanner

Rust does now have abstractions for dma_fence. These abstractions are quite complicated and require expertise with both the C and the Rust side. Therefore, using the existing entry also for maintenance of the Rust code appears reasonable.

Philipp volunteers to help maintain the dma_fence abstractions. Add a corresponding MAINTAINERS entry.

---
Just as a suggestion, I don't want to force myself in here. Would also be perfectly happy with other approaches; there are certainly a few people who could maintain or co-maintain it.

---
 MAINTAINERS | 2 ++
 1 file changed, 2 insertions(+)
MAINTAINERS +2 -0
index 2e8d160babc2..31fc595d5c6b 100644
@@ -7521,6 +7521,7 @@ F:	fs/dlm/
 DMA BUFFER SHARING FRAMEWORK
 M:	Sumit Semwal <[email protected]>
 M:	Christian König <[email protected]>
+M:	Philipp Stanner <[email protected]>
 L:	[email protected]
 L:	[email protected]
 L:	[email protected] (moderated for non-subscribers)
@@ -7529,6 +7530,7 @@ T:	git https://gitlab.freedesktop.org/drm/misc/kernel.git
 F:	Documentation/driver-api/dma-buf.rst
 F:	Documentation/userspace-api/dma-buf-alloc-exchange.rst
 F:	drivers/dma-buf/
+F:	rust/kernel/dma_buf/
 F:	include/linux/*fence.h
 F:	include/linux/dma-buf.h
 F:	include/linux/dma-buf/
Signature · 1 line
2.54.0
#2 lore raw

Re: [PATCH 1/4] rust: types: implement ForeignOwnable for ARef<T>

In reply to Philipp Stanner
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Low] Missing `#[inline]` annotations on small forwarding trait methods in `ForeignOwnable`…

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] Missing #[inline] annotations on small forwarding trait methods in ForeignOwnable implementation for ARef<T>. --

commit 62543d419c62c8206acbd8b76f07e5198b71442e
Author: Danilo Krummrich <[email protected]>

rust: types: implement ForeignOwnable for ARef<T>

This commit implements ForeignOwnable for ARef<T>, making it possible for C code to own an ARef<T>. It sets BorrowedMut to &T to match the shared ownership semantics of the underlying reference-counted type.

Quoted 4 lines
rust/kernel/sync/aref.rs +0 -0
index 9989f56d06052..82907383c44b5 100644

[ ... ]

rust/kernel/sync/aref.rs fn eq(&self, other: &ARef<U>) -> bool { · line 198

Quoted 4 lines
rust/kernel/sync/aref.rs +4 -0
+    type Borrowed<'a> = &'a T;
+    type BorrowedMut<'a> = &'a T;
+
+    fn into_foreign(self) -> *mut c_void {

[Severity: Low]
This isn't a bug, but should this method be annotated with #[inline]? According to the Rust subsystem guidelines, small forwarding functions in abstractions should be annotated with #[inline].

rust/kernel/sync/aref.rs fn eq(&self, other: &ARef<U>) -> bool { · line 202

Quoted 4 lines
rust/kernel/sync/aref.rs +4 -0
+        ARef::into_raw(self).as_ptr().cast()
+    }
+
+    unsafe fn from_foreign(ptr: *mut c_void) -> Self {

[Severity: Low]
This isn't a bug, but should from_foreign() also have an #[inline] annotation since it is a small wrapper method?

rust/kernel/sync/aref.rs fn eq(&self, other: &ARef<U>) -> bool { · line 212

Quoted 10 lines
rust/kernel/sync/aref.rs +10 -0
+        // SAFETY: The safety requirements of this function ensure that `ptr` comes from a previous
+        // call to `Self::into_foreign`.
+        let ptr = unsafe { NonNull::new_unchecked(ptr.cast()) };
+
+        // SAFETY: `ptr` came from `into_foreign`, which consumed an `ARef` without decrementing
+        // the refcount, so we can transfer the ownership to the new `ARef`.
+        unsafe { ARef::from_raw(ptr) }
+    }
+
+    unsafe fn borrow<'a>(ptr: *mut c_void) -> &'a T {

[Severity: Low]
This isn't a bug, but similar to the above, should borrow() be annotated with #[inline]?

rust/kernel/sync/aref.rs fn eq(&self, other: &ARef<U>) -> bool { · line 218

Quoted 6 lines
rust/kernel/sync/aref.rs +6 -0
+        // SAFETY: The safety requirements of this method ensure that the object remains alive and
+        // immutable for the duration of 'a.
+        unsafe { &*ptr.cast() }
+    }
+
+    unsafe fn borrow_mut<'a>(ptr: *mut c_void) -> &'a T {

[Severity: Low]
This isn't a bug, but does borrow_mut() need an #[inline] annotation here as well?

Quoted 5 lines
rust/kernel/sync/aref.rs +5 -0
+        // SAFETY: The safety requirements for `borrow_mut` are a superset of the safety
+        // requirements for `borrow`.
+        unsafe { <Self as ForeignOwnable>::borrow(ptr) }
+    }
+}
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1
#5 lore raw

Re: [PATCH 2/4] rust: rcu: add RcuBox type

In reply to Philipp Stanner
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider: - [Critical] `RcuBox::into_foreign` consumes `self` by value without preventing its destructor from…

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [Critical] RcuBox::into_foreign consumes self by value without preventing its destructor from running, causing immediate RCU freeing of the active allocation.
- [High] RcuBox defers the execution of the inner value's destructor using RCU but fails to restrict the value to the 'static lifetime, allowing use-after-free on stack references.
- [High] ForeignOwnableRcu implementation for RcuBox<T> does not require T: Sync, allowing data races on !Sync types.
--

commit 81791c9c93af881931b7e9c9ed0d885ba31c73cb
Author: Alice Ryhl <[email protected]>

rust: rcu: add RcuBox type

This adds an RcuBox container, which is like KBox except that the value is freed with kfree_rcu. To allow containers to rely on the rcu properties of RcuBox, an extension of ForeignOwnable is added.

Quoted 5 lines
rust/kernel/sync/rcu/rcu_box.rs new file +0 -0
new file mode 100644
index 0000000000000..2508fdb609ecc

[ ... ]

rust/kernel/sync/rcu/rcu_box.rs line 26

Quoted 9 lines
rust/kernel/sync/rcu/rcu_box.rs new file +9 -0
+/// A box that is freed with rcu.
+///
+/// The value must be `Send`, as rcu may drop it on another thread.
+///
+/// # Invariants
+///
+/// * The pointer is valid and references a pinned `RcuBoxInner<T>` allocated with `kmalloc`.
+/// * This `RcuBox` holds exclusive permissions to rcu free the allocation.
+pub struct RcuBox<T: Send>(NonNull<RcuBoxInner<T>>);

[Severity: High]
Does RcuBox need a 'static bound on T here?

RcuBox defers the execution of the inner value's destructor using RCU via call_rcu, but the definition lacks a 'static bound on T. This means the Rust compiler would permit short-lived local references (e.g., &'a mut i32) to be stored inside types wrapped by RcuBox.

Since the actual destructor of T is executed asynchronously in the RCU callback, it may occur after the local variables it references have been popped from the stack. If T's destructor accesses those references, it will read or write to freed stack memory.

[ ... ]

rust/kernel/sync/rcu/rcu_box.rs line 88

Quoted 12 lines
rust/kernel/sync/rcu/rcu_box.rs new file +12 -0
+// SAFETY:
+// * The `RcuBoxInner<T>` was allocated with `kmalloc`.
+// * `NonNull::as_ptr` returns a non-null pointer.
+unsafe impl<T: Send + 'static> ForeignOwnable for RcuBox<T> {
+    const FOREIGN_ALIGN: usize = <KBox<RcuBoxInner<T>> as ForeignOwnable>::FOREIGN_ALIGN;
+
+    type Borrowed<'a> = &'a T;
+    type BorrowedMut<'a> = &'a T;
+
+    fn into_foreign(self) -> *mut c_void {
+        self.0.as_ptr().cast()
+    }

[Severity: Critical]
Does this code inadvertently run the destructor of RcuBox during into_foreign()?

Since this method takes self by value and doesn't explicitly wrap it in core::mem::ManuallyDrop or call core::mem::forget(self), the object will be dropped at the end of the method's scope.

This would invoke RcuBox::drop immediately, enqueuing a call to bindings::call_rcu or bindings::kvfree_call_rcu before returning the pointer. Any subsequent access by C code reading the published pointer would result in a use-after-free, and calling from_foreign() and dropping it again would cause a double-free.

Quoted 2 lines
rust/kernel/sync/rcu/rcu_box.rs new file +2 -0
+
+    unsafe fn from_foreign(ptr: *mut c_void) -> Self {

[ ... ]

Quoted 1 line

+impl<T: Send + 'static> ForeignOwnableRcu for RcuBox<T> {

[Severity: High]
Should this trait implementation also require T: Sync?

The ForeignOwnableRcu trait is designed to allow concurrent RCU readers to safely borrow the underlying data, with rcu_borrow() returning &'a T.

Because RCU allows multiple threads to execute read-side critical sections concurrently, multiple CPUs can concurrently call rcu_borrow on the same pointer. If T is not Sync (e.g., Cell<i32>), this could result in multiple &T references to a !Sync type across different threads, allowing concurrent mutation through shared references.

Quoted 8 lines
rust/kernel/sync/rcu/rcu_box.rs new file +8 -0
+    type RcuBorrowed<'a> = &'a T;
+
+    unsafe fn rcu_borrow<'a>(ptr: *mut c_void) -> &'a T {
+        // SAFETY: `RcuBox::drop` can only run after `from_foreign` is called, and the value is
+        // valid until `RcuBox::drop` plus one grace period.
+        unsafe { &(*ptr.cast::<RcuBoxInner<T>>()).value }
+    }
+}
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=2
#16 lore raw

Re: [PATCH 3/4] rust: Add dma_fence abstractions

In reply to Philipp Stanner
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider: - [Critical] DriverFence::drop eagerly drops FenceCtx and user data before the C dma_fence refcount reaches…

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [Critical] DriverFence::drop eagerly drops FenceCtx and user data before the C dma_fence refcount reaches 0, leading to a Use-After-Free.
- [High] FenceCtx::drop unconditionally calls rcu_barrier(), causing a 'scheduling while atomic' panic when a DriverFence is dropped from an interrupt handler or under a spinlock.
- [High] DriverFence::drop() passes a positive error code (ECANCELED) to dma_fence_set_error(), triggering a WARN_ON and misconfiguring the error state.
- [High] DriverFence::as_raw and DriverFenceBorrow::as_raw create exclusive mutable references (&mut) to actively shared data, causing Rust aliasing Undefined Behavior. --

commit bf1d1a15337629cfe74eaeb7d34f6878de552428
Author: Philipp Stanner <[email protected]>
Subject: rust: Add dma_fence abstractions

This commit introduces Rust abstractions for the dma_fence framework. It provides DriverFence, Fence, and FenceCtx types to decouple fence issuers from consumers and safely manage synchronization primitives required by GPU drivers.

Quoted 4 lines
rust/kernel/dma_buf/dma_fence.rs new file +0 -0
new file mode 100644

[ ... ]

rust/kernel/dma_buf/dma_fence.rs line 212

Quoted 5 lines
rust/kernel/dma_buf/dma_fence.rs new file +5 -0
+#[pinned_drop]
+impl<F: Send + Sync, C: Send + Sync> PinnedDrop for FenceCtx<F, C> {
+    fn drop(self: Pin<&mut Self>) {
+        // SAFETY: `rcu_barrier()` is always safe to be called.
+        unsafe { bindings::rcu_barrier() };

[Severity: High]
Does calling rcu_barrier() here risk a scheduling-while-atomic panic?

Since DriverFence::signal() consumes self, the DriverFence will be dropped at the end of the method. If this happens in an atomic context (such as an interrupt handler or under a spinlock), and it holds the last reference to the FenceCtx, dropping it triggers FenceCtx::drop().

Because rcu_barrier() blocks and sleeps until all pending RCU callbacks complete, this could attempt to sleep in atomic context.

Quoted 2 lines
rust/kernel/dma_buf/dma_fence.rs new file +2 -0
+    }
+}

[ ... ]

rust/kernel/dma_buf/dma_fence.rs line 614

Quoted 4 lines
rust/kernel/dma_buf/dma_fence.rs new file +4 -0
+impl<F: Send + Sync, C: Send + Sync> DriverFence<F, C> {
+    fn as_raw(&self) -> *mut bindings::dma_fence {
+        // SAFETY: Valid because `self` is valid.
+        let fence_data = unsafe { &mut *self.data.as_ptr() };

[Severity: High]
Does this create an exclusive mutable reference to actively shared memory?

DriverFenceData can be accessed concurrently by other threads holding Fence references (for instance, when checking if the fence is signaled). Creating a mutable reference (&mut) in Rust asserts exclusive access and violates aliasing rules, which allows the compiler to make invalid optimization assumptions.

Could this use an immutable reference &*self.data.as_ptr() instead?

Quoted 3 lines
rust/kernel/dma_buf/dma_fence.rs new file +3 -0
+
+        fence_data.inner.inner.get()
+    }

[ ... ]

rust/kernel/dma_buf/dma_fence.rs line 707

Quoted 4 lines
rust/kernel/dma_buf/dma_fence.rs new file +4 -0
+impl<F: Send + Sync, C: Send + Sync> DriverFenceBorrow<F, C> {
+    fn as_raw(&self) -> *mut bindings::dma_fence {
+        // SAFETY: Valid because `self` is valid.
+        let fence_data = unsafe { &mut *self.data.as_ptr() };

[Severity: High]
Similar to DriverFence::as_raw(), does this also incorrectly create a mutable reference to shared data?

Quoted 3 lines
rust/kernel/dma_buf/dma_fence.rs new file +3 -0
+
+        fence_data.inner.inner.get()
+    }

[ ... ]

Quoted 4 lines
rust/kernel/dma_buf/dma_fence.rs new file +4 -0
+impl<F: Send + Sync, C: Send + Sync> Drop for DriverFence<F, C> {
+    fn drop(&mut self) {
+        let fence = self.as_raw();
+        let mut fence_flags: usize = 0;

rust/kernel/dma_buf/dma_fence.rs line 797

Quoted 10 lines
rust/kernel/dma_buf/dma_fence.rs new file +10 -0
+        let flag_ptr = &raw mut fence_flags;
+
+        // SAFETY: Once a `DriverFence` is initialized, the inner `fence` is
+        // valid and initialized. It is valid until the refcount drops
+        // to 0, which can earliest happen once the `DriverFence` has been dropped.
+        unsafe {
+            bindings::dma_fence_lock_irqsave(fence, flag_ptr);
+            #[allow(unused_unsafe)]
+            if warn_on!(!bindings::dma_fence_is_signaled_locked(fence)) {
+                bindings::dma_fence_set_error(fence, ECANCELED as i32);

[Severity: High]
Is it intentional to pass a positive error code here?

bindings::ECANCELED evaluates to a positive integer, but dma_fence_set_error() expects a negative error code (and triggers a WARN_ON for positive values). A positive value might also be interpreted as success by callers checking for values < 0.

Should this be - (bindings::ECANCELED as i32)?

rust/kernel/dma_buf/dma_fence.rs line 809

Quoted 12 lines
rust/kernel/dma_buf/dma_fence.rs new file +12 -0
+                bindings::dma_fence_signal_locked(fence);
+            }
+            bindings::dma_fence_unlock_irqrestore(fence, flag_ptr);
+        }
+
+        // SAFETY: `self.data` is owned by the DriverFence, but could be accessed
+        // through some dma_fence callbacks right now. Access is being revoked
+        // above by signalling the fence. The DriverFenceAllowedData trait
+        // ensures that the data either does not need drop, or if it does it
+        // lives in a RcuBox which will delay dropping by one grace period, hence
+        // ensuring that all readers have disappeared.
+        unsafe { drop_in_place(self.data.as_ptr()) };

[Severity: Critical]
Could this eagerly drop FenceCtx and user data before the C dma_fence refcount reaches 0?

The C dma_fence object can remain alive as long as consumers (like sync_file or the GPU scheduler) hold references to it. If the backend later invokes callbacks like ops->get_driver_name(), FenceCtx::get_driver_name() would attempt to read fctx.driver_name from the already-dropped FenceCtx:

FenceCtx::get_driver_name() {
    ...
    fctx.driver_name.as_char_ptr()
}

Can this result in a use-after-free?

Quoted 7 lines
rust/kernel/dma_buf/dma_fence.rs new file +7 -0
+
+        // SAFETY: Once a `DriverFence` is initialized, the inner `fence` is
+        // valid and initialized. It is valid until the refcount drops
+        // to 0, which can earliest happen once the `DriverFence` has been dropped.
+        unsafe {
+            bindings::dma_fence_put(fence);
+        }
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=3
maintainer
#6 lore raw

Re: [PATCH 2/4] rust: rcu: add RcuBox type

In reply to Philipp Stanner
Quoted 10 lines · Philipp Stanner

From: Alice Ryhl <[email protected]>

This adds an RcuBox container, which is like KBox except that the value is freed with kfree_rcu.

To allow containers to rely on the rcu properties of RcuBox, an extension of ForeignOwnable is added.

---

I have the following on top of Alice's patch. @Alice, @Danilo, thoughts?

Then we can have:

type RcuKBox<T> = RcuBox<T, Kmalloc>;
type RcuVBox<T> = RcuBox<T, Vmalloc>;

and Philipp can use the RcuKBox in this patchset. We also need to impl InPlaceInit for RcuBox, but that can be added later.

Regards,
Boqun

------------->8
Subject: [PATCH] rust: rcu: Make RcuBox generic over Allocator

To support RCU-protected vmalloc allocation, we need to make RcuBox generic over Allocator. Currently this works since all Allocators are either kmalloc() or vmalloc(), and kvfree_call_rcu() works with both allocations.

While we are at it, add some basic test cases.

---
 rust/kernel/sync/rcu/rcu_box.rs | 96 +++++++++++++++++++++++----------
 1 file changed, 67 insertions(+), 29 deletions(-)
rust/kernel/sync/rcu/rcu_box.rs +67 -29
index 2508fdb609ec..5c344d82c0d9 100644
@@ -4,47 +4,59 @@
 
 //! Provides the `RcuBox` type for Rust allocations that live for a grace period.
 
-use core::{ops::Deref, ptr::NonNull};
+use core::{
+    marker::PhantomData,
+    ops::Deref,
+    ptr::NonNull, //
+};
 
 use kernel::{
-    alloc::{self, AllocError},
+    alloc::{
+        self,
+        AllocError,
+        Allocator, //
+    },
     bindings,
     ffi::c_void,
     prelude::*,
-    sync::rcu::{ForeignOwnableRcu, Guard},
     types::ForeignOwnable,
 };
 
+use super::{
+    ForeignOwnableRcu,
+    Guard, //
+};
+
 /// A box that is freed with rcu.
 ///
 /// The value must be `Send`, as rcu may drop it on another thread.
 ///
 /// # Invariants
 ///
-/// * The pointer is valid and references a pinned `RcuBoxInner<T>` allocated with `kmalloc`.
+/// * The pointer is valid and references a pinned `RcuBoxInner<T>` allocated with `A`.
 /// * This `RcuBox` holds exclusive permissions to rcu free the allocation.
-pub struct RcuBox<T: Send>(NonNull<RcuBoxInner<T>>);
+pub struct RcuBox<T: Send, A: Allocator>(NonNull<RcuBoxInner<T>>, PhantomData<A>);
 
 struct RcuBoxInner<T> {
     value: T,
     rcu_head: bindings::callback_head,
 }
 
-// Note that `T: Sync` is required since when moving an `RcuBox<T>`, the previous owner may still
-// access `&T` for one grace period.
+// Note that `T: Sync` is required since when moving an `RcuBox<T, A>`, the previous owner may
+// still access `&T` for one grace period.
 //
-// SAFETY: Ownership of the `RcuBox<T>` allows for `&T` and dropping the `T`, so `T: Send + Sync`
-// implies `RcuBox<T>: Send`.
-unsafe impl<T: Send + Sync> Send for RcuBox<T> {}
+// SAFETY: Ownership of the `RcuBox<T, A>` allows for `&T` and dropping the `T`, so `T: Send +
+// Sync` implies `RcuBox<T, A>: Send`.
+unsafe impl<T: Send + Sync, A: Allocator> Send for RcuBox<T, A> {}
 
-// SAFETY: `&RcuBox<T>` allows for no operations other than those permitted by `&T`, so `T: Sync`
-// implies `RcuBox<T>: Sync`.
-unsafe impl<T: Send + Sync> Sync for RcuBox<T> {}
+// SAFETY: `&RcuBox<T, A>` allows for no operations other than those permitted by `&T`, so `T:
+// Sync` implies `RcuBox<T, A>: Sync`.
+unsafe impl<T: Send + Sync, A: Allocator> Sync for RcuBox<T, A> {}
 
-impl<T: Send> RcuBox<T> {
+impl<T: Send, A: Allocator> RcuBox<T, A> {
     /// Create a new `RcuBox`.
     pub fn new(x: T, flags: alloc::Flags) -> Result<Self, AllocError> {
-        let b = KBox::new(
+        let b = Box::<_, A>::new(
             RcuBoxInner {
                 value: x,
                 rcu_head: Default::default(),
@@ -53,9 +65,9 @@ pub fn new(x: T, flags: alloc::Flags) -> Result<Self, AllocError> {
         )?;
 
         // INVARIANT:
-        // * The pointer contains a valid `RcuBoxInner` allocated with `kmalloc`.
+        // * The pointer contains a valid `RcuBoxInner` allocated with `A`.
         // * We just allocated it, so we own free permissions.
-        Ok(RcuBox(NonNull::from(KBox::leak(b))))
+        Ok(RcuBox(NonNull::from(Box::leak(b)), PhantomData))
     }
 
     /// Access the value for a grace period.
@@ -66,7 +78,7 @@ pub fn with_rcu<'rcu>(&self, _read_guard: &'rcu Guard) -> &'rcu T {
     }
 }
 
-impl<T: Send> Deref for RcuBox<T> {
+impl<T: Send, A: Allocator> Deref for RcuBox<T, A> {
     type Target = T;
     fn deref(&self) -> &T {
         // SAFETY: While the `RcuBox<T>` exists, the value remains valid.
@@ -75,10 +87,10 @@ fn deref(&self) -> &T {
 }
 
 // SAFETY:
-// * The `RcuBoxInner<T>` was allocated with `kmalloc`.
+// * The `RcuBoxInner<T>` was allocated with `A`.
 // * `NonNull::as_ptr` returns a non-null pointer.
-unsafe impl<T: Send + 'static> ForeignOwnable for RcuBox<T> {
-    const FOREIGN_ALIGN: usize = <KBox<RcuBoxInner<T>> as ForeignOwnable>::FOREIGN_ALIGN;
+unsafe impl<T: Send + 'static, A: Allocator> ForeignOwnable for RcuBox<T, A> {
+    const FOREIGN_ALIGN: usize = <Box<RcuBoxInner<T>, A> as ForeignOwnable>::FOREIGN_ALIGN;
 
     type Borrowed<'a> = &'a T;
     type BorrowedMut<'a> = &'a T;
@@ -88,9 +100,9 @@ fn into_foreign(self) -> *mut c_void {
     }
 
     unsafe fn from_foreign(ptr: *mut c_void) -> Self {
-        // INVARIANT: Pointer returned by `into_foreign` carries same invariants as `RcuBox<T>`.
+        // INVARIANT: Pointer returned by `into_foreign, A` carries same invariants as `RcuBox<T>`.
         // SAFETY: `into_foreign` never returns a null pointer.
-        Self(unsafe { NonNull::new_unchecked(ptr.cast()) })
+        Self(unsafe { NonNull::new_unchecked(ptr.cast()) }, PhantomData)
     }
 
     unsafe fn borrow<'a>(ptr: *mut c_void) -> &'a T {
@@ -104,7 +116,7 @@ unsafe fn borrow_mut<'a>(ptr: *mut c_void) -> &'a T {
     }
 }
 
-impl<T: Send + 'static> ForeignOwnableRcu for RcuBox<T> {
+impl<T: Send + 'static, A: Allocator> ForeignOwnableRcu for RcuBox<T, A> {
     type RcuBorrowed<'a> = &'a T;
 
     unsafe fn rcu_borrow<'a>(ptr: *mut c_void) -> &'a T {
@@ -114,7 +126,7 @@ unsafe fn rcu_borrow<'a>(ptr: *mut c_void) -> &'a T {
     }
 }
 
-impl<T: Send> Drop for RcuBox<T> {
+impl<T: Send, A: Allocator> Drop for RcuBox<T, A> {
     fn drop(&mut self) {
         // SAFETY: The `rcu_head` field is in-bounds of a valid allocation.
         let rcu_head = unsafe { &raw mut (*self.0.as_ptr()).rcu_head };
@@ -122,9 +134,11 @@ fn drop(&mut self) {
             // SAFETY: `rcu_head` is the `rcu_head` field of `RcuBoxInner<T>`. All users will be
             // gone in an rcu grace period. This is the destructor, so we may pass ownership of the
             // allocation.
-            unsafe { bindings::call_rcu(rcu_head, Some(drop_rcu_box::<T>)) };
+            unsafe { bindings::call_rcu(rcu_head, Some(drop_rcu_box::<T, A>)) };
         } else {
             // SAFETY: All users will be gone in an rcu grace period.
+            // TODO: We are luckily since `kvfree_call_rcu()` works on both kmalloc and vmalloc,
+            // maybe a new `Allocator` method is needed.
             unsafe { bindings::kvfree_call_rcu(rcu_head, self.0.as_ptr().cast()) };
         }
     }
@@ -135,11 +149,35 @@ fn drop(&mut self) {
 /// # Safety
 ///
 /// `head` references the `rcu_head` field of an `RcuBoxInner<T>` that has no references to it.
-/// Ownership of the `KBox<RcuBoxInner<T>>` must be passed.
-unsafe extern "C" fn drop_rcu_box<T>(head: *mut bindings::callback_head) {
+/// Ownership of the `Box<RcuBoxInner<T>, A>` must be passed.
+unsafe extern "C" fn drop_rcu_box<T, A: Allocator>(head: *mut bindings::callback_head) {
     // SAFETY: Caller provides a pointer to the `rcu_head` field of a `RcuBoxInner<T>`.
     let box_inner = unsafe { crate::container_of!(head, RcuBoxInner<T>, rcu_head) };
 
     // SAFETY: Caller ensures exclusive access and passed ownership.
-    drop(unsafe { KBox::from_raw(box_inner) });
+    drop(unsafe { Box::<_, A>::from_raw(box_inner) });
+}
+
+#[kunit_tests(rust_rcu_box)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn rcu_box_basic() -> Result {
+        let rb = RcuBox::<_, alloc::allocator::Kmalloc>::new(42i32, alloc::flags::GFP_KERNEL)?;
+
+        assert_eq!(*rb, 42);
+        assert_eq!(*rb.with_rcu(&Guard::new()), 42);
+
+        drop(rb);
+
+        let rb = RcuBox::<_, alloc::allocator::Vmalloc>::new(42i32, alloc::flags::GFP_KERNEL)?;
+
+        assert_eq!(*rb, 42);
+        assert_eq!(*rb.with_rcu(&Guard::new()), 42);
+
+        drop(rb);
+
+        Ok(())
+    }
 }
Signature · 1 line
2.50.1 (Apple Git-155)
maintainer
#46 lore raw

Re: [PATCH 3/4] rust: Add dma_fence abstractions

In reply to Philipp Stanner

(Not a full review, but a few drive-by comments.)

Quoted 1 line · Philipp Stanner

+#[allow(unused_unsafe)]

What is this needed for?

Quoted 1 line

+impl<F: Send + Sync + DriverFenceAllowedData, C: Send + Sync> FenceCtx<F, C> {

<snip>

rust/kernel/dma_buf/dma_fence.rs line 212

Quoted 4 lines
rust/kernel/dma_buf/dma_fence.rs new file +4 -0
+impl<F: Send + Sync, C: Send + Sync> PinnedDrop for FenceCtx<F, C> {
+    fn drop(self: Pin<&mut Self>) {
+        // SAFETY: `rcu_barrier()` is always safe to be called.
+        unsafe { bindings::rcu_barrier() };

We should probably add a safe function for this.

rust/kernel/dma_buf/dma_fence.rs line 309

Quoted 13 lines
rust/kernel/dma_buf/dma_fence.rs new file +13 -0
+impl<T: FenceCb> FenceCbRegistration<T> {
+    /// Register a callback on a fence.
+    ///
+    /// On success the callback is pinned in place and will fire when the fence
+    /// signals. On `AlreadySignaled` the callback is returned to the caller so
+    /// that owned resources can be reclaimed.
+    pub fn new<'a>(fence: &'a Fence, callback: T) -> impl PinInit<Self, CallbackError<T>> + 'a
+    where
+        T: 'a,
+    {
+        // Uses `pin_init_from_closure` instead of `try_pin_init!` so that on
+        // `-ENOENT` (already signaled) the callback can be read back from the
+        // partially-initialized slot and returned through the error.

Seems a bit odd that this needs pin_init_from_closure(). You can still use try_pin_init!() with &this in Self an a _: initializer at the end in the worst case. But the fence and callback fields should be fine to initialize "normally"?

Quoted 29 lines
rust/kernel/dma_buf/dma_fence.rs new file +29 -0
+        //
+        // SAFETY: `pin_init_from_closure` requires:
+        // - On `Ok(())`: the slot is fully initialized and valid for `Drop`.
+        // - On `Err(_)`: the slot is clean, i.e.: no partially-initialized fields
+        //   remain, and the slot can be deallocated without dropping.
+        //
+        // We uphold this as follows:
+        // - On success: all three fields are initialized. Ok(()) is returned.
+        // - On ENOENT (already signaled): `callback` and `fence` are read back
+        //   from the slot via `ptr::read`, leaving the slot clean. `cb` was
+        //   initialized by `dma_fence_add_callback` (it calls
+        //   `INIT_LIST_HEAD(&cb->node)` even on error), but `cb` is
+        //   `Opaque<dma_fence_cb>` which has no `Drop`, so not dropping it is
+        //   fine. The callback is returned through `AlreadySignaled(T)`.
+        // - On other errors: same cleanup as ENOENT, error returned as
+        //   `Other(e)`.
+        unsafe {
+            pin_init_from_closure(move |slot: *mut Self| {
+                let slot_callback = &raw mut (*slot).callback;
+                let slot_fence = &raw mut (*slot).fence;
+                let slot_cb = &raw mut (*slot).cb;
+
+                // Write callback and fence first — must be visible before
+                // dma_fence_add_callback makes the registration live.
+                core::ptr::write(slot_callback, callback);
+                core::ptr::write(slot_fence, ARef::from(fence));
+
+                let ret = to_result(bindings::dma_fence_add_callback(
+                    fence.inner.get(),

rust/kernel/dma_buf/dma_fence.rs line 348

Quoted 10 lines
rust/kernel/dma_buf/dma_fence.rs new file +10 -0
+                    Opaque::cast_into(slot_cb),
+                    Some(Self::dma_fence_callback),
+                ));
+
+                match ret {
+                    Ok(()) => Ok(()),
+                    Err(e) => {
+                        // Read back what we wrote to leave the slot clean.
+                        let cb_back = core::ptr::read(slot_callback);
+                        let _fence_back = core::ptr::read(slot_fence);

What's the purpose of _fence_back?

Quoted 20 lines
rust/kernel/dma_buf/dma_fence.rs new file +20 -0
+
+                        if e.to_errno() == ENOENT.to_errno() {
+                            Err(CallbackError::AlreadySignaled(cb_back))
+                        } else {
+                            Err(CallbackError::Other(e))
+                        }
+                    }
+                }
+            })
+        }
+    }
+    /// Signal the fence. This will invoke all registered callbacks.
+    pub fn signal(self, res: Result) {
+        let fence = self.as_raw();
+        let mut fence_flags: usize = 0;
+        let flag_ptr = &raw mut fence_flags;
+
+        // SAFETY: Once a `DriverFence` is initialized, the inner `fence` is
+        // valid and initialized. It is valid until the refcount drops
+        // to 0, which can earliest happen once the `DriverFence` has been dropped.

rust/kernel/dma_buf/dma_fence.rs line 386

Quoted 10 lines
rust/kernel/dma_buf/dma_fence.rs new file +10 -0
+        unsafe {
+            bindings::dma_fence_lock_irqsave(fence, flag_ptr);
+            if !bindings::dma_fence_is_signaled_locked(fence) {
+                if let Err(err) = res {
+                    bindings::dma_fence_set_error(fence, err.to_errno());
+                }
+                bindings::dma_fence_signal_locked(fence);
+            }
+            bindings::dma_fence_unlock_irqrestore(fence, flag_ptr);
+        }

Please use a single unsafe block per unsafe function call, here and in a few other places.

Quoted 10 lines
rust/kernel/dma_buf/dma_fence.rs new file +10 -0
+    }
+}
+
+// SAFETY: Fences are literally designed to be shared between threads.
+unsafe impl<F: Send + Sync, C: Send + Sync> Send for DriverFence<F, C> {}
+
+impl<F: Send + Sync, C: Send + Sync> Deref for DriverFence<F, C> {
+    type Target = F;
+
+    fn deref(&self) -> &Self::Target {

rust/kernel/dma_buf/dma_fence.rs line 686

Quoted 10 lines
rust/kernel/dma_buf/dma_fence.rs new file +10 -0
+        // SAFETY: Thanks to refcounting, `data` is always valid as long as `self` is.
+        let data = unsafe { &*self.data.as_ptr() };
+
+        &data.data
+    }
+}
+
+/// A borrowed [`DriverFence`]. All you can do with it is access your user data
+/// and obtain a [`Fence`].
+pub struct DriverFenceBorrow<F: Send + Sync, C: Send + Sync> {

This misses the lifetime bound, which is the purpose of this struct.

rust/kernel/dma_buf/dma_fence.rs line 567

Quoted 4 lines
rust/kernel/dma_buf/dma_fence.rs new file +4 -0
+    /// The actual content of the fence. Lives in a raw pointer so that its
+    /// memory can be managed independently. Valid until both the [`DriverFence`]
+    /// and all associated [`Fence`]s have disappeared.
+    data: NonNull<DriverFenceData<F, C>>,

Why not use ManuallyDrop<DriverFence>? This way you would only need a Deref impl to &'a DriverFence.

This way you basically reimplement the DriverFence type just without the destructor.

maintainer
#69 lore raw

Re: [PATCH 4/4] MAINTAINERS: Add entry for Rust dma-buf

In reply to Philipp Stanner

MAINTAINERS T: git https://gitlab.freedesktop.org/drm/misc/kernel.git · line 7533

Quoted 5 lines · Philipp Stanner
MAINTAINERS +1 -0
@@ -7529,6 +7530,7 @@ T:	git https://gitlab.freedesktop.org/drm/misc/kernel.git
 F:	Documentation/driver-api/dma-buf.rst
 F:	Documentation/userspace-api/dma-buf-alloc-exchange.rst
 F:	drivers/dma-buf/
+F:	rust/kernel/dma_buf/

Please also add rust/helpers/dma_fence.c.

Given that dma-buf goes through drm-misc, we should probably also add those file to the drm-rust entry.

maintainer
#7 lore raw

Re: [PATCH 2/4] rust: rcu: add RcuBox type

In reply to Boqun Feng
Quoted 2 lines · Boqun Feng

type RcuKBox<T> = RcuBox<T, Kmalloc>;
type RcuVBox<T> = RcuBox<T, Vmalloc>;

type RcuKVBox<T> = RcuBox<T, KVmalloc>;

Quoted 4 lines

To support RCU-protected vmalloc allocation, we need to make RcuBox generic over Allocator. Currently this works since all Allocators are either kmalloc() or vmalloc(), and kvfree_call_rcu() works with both allocations.

I think we can add Allocator::free_call_rcu().

#8 lore raw

Re: [PATCH 2/4] rust: rcu: add RcuBox type

In reply to Boqun Feng
Quoted 12 lines · Boqun Feng
Quoted 10 lines · Philipp Stanner

From: Alice Ryhl <[email protected]>

This adds an RcuBox container, which is like KBox except that the value is freed with kfree_rcu.

To allow containers to rely on the rcu properties of RcuBox, an extension of ForeignOwnable is added.

---

Quoted 6 lines

I have the following on top of Alice's patch. @Alice, @Danilo, thoughts?

Then we can have:

type RcuKBox<T> = RcuBox<T, Kmalloc>;
type RcuVBox<T> = RcuBox<T, Vmalloc>;

No objections by me.

I just think we have to decide how the treat the namespaces, though. Probably Alice wrote it like that so that it's very apparent that this is not a normal box. It still breaks the naming convention in my opinion.

rcu::Box vs rcu::RcuBox

With all other subsystems, naming like that seems not allowed.

dma::Fence vs dma::DmaFence

I probably would allow the user to decide whether he wants to just use it as rcu::Box in all his code.

But no hard feelings.

Quoted 3 lines

and Philipp can use the RcuKBox in this patchset. We also need to impl InPlaceInit for RcuBox, but that can be added later.

So shall we merge my series with Alice's patch, and later we add your patch and other features, or would you prefer to have the additional boxes from your patch from the get-go?

P.

Quoted 215 lines

Regards,
Boqun

------------->8
Subject: [PATCH] rust: rcu: Make RcuBox generic over Allocator

To support RCU-protected vmalloc allocation, we need to make RcuBox generic over Allocator. Currently this works since all Allocators are either kmalloc() or vmalloc(), and kvfree_call_rcu() works with both allocations.

While we are at it, add some basic test cases.

---
 rust/kernel/sync/rcu/rcu_box.rs | 96 +++++++++++++++++++++++----------

 1 file changed, 67 insertions(+), 29 deletions(-)
rust/kernel/sync/rcu/rcu_box.rs +0 -0
index 2508fdb609ec..5c344d82c0d9 100644
@@ -4,47 +4,59 @@


 //! Provides the RcuBox type for Rust allocations that live for a grace period.

rust/kernel/sync/rcu/rcu_box.rs +5 -1
-use core::{ops::Deref, ptr::NonNull};
+use core::{
+    marker::PhantomData,
+    ops::Deref,
+    ptr::NonNull, //
+};


 use kernel::{
-    alloc::{self, AllocError},
+    alloc::{
+        self,
+        AllocError,
+        Allocator, //
+    },
     bindings,
     ffi::c_void,
     prelude::*,
-    sync::rcu::{ForeignOwnableRcu, Guard},
     types::ForeignOwnable,
 };

rust/kernel/sync/rcu/rcu_box.rs +5 -0
+use super::{
+    ForeignOwnableRcu,
+    Guard, //
+};
+

 /// A box that is freed with rcu.
 ///
 /// The value must be Send, as rcu may drop it on another thread. ///
 /// # Invariants
 ///

rust/kernel/sync/rcu/rcu_box.rs +1 -1
-/// * The pointer is valid and references a pinned `RcuBoxInner<T>` allocated with `kmalloc`.
+/// * The pointer is valid and references a pinned `RcuBoxInner<T>` allocated with `A`.

 /// * This RcuBox holds exclusive permissions to rcu free the allocation.

rust/kernel/sync/rcu/rcu_box.rs +1 -1
-pub struct RcuBox<T: Send>(NonNull<RcuBoxInner<T>>);
+pub struct RcuBox<T: Send, A: Allocator>(NonNull<RcuBoxInner<T>>, PhantomData<A>);


 struct RcuBoxInner<T> {
     value: T,
     rcu_head: bindings::callback_head,
 }

rust/kernel/sync/rcu/rcu_box.rs +2 -2
-// Note that `T: Sync` is required since when moving an `RcuBox<T>`, the previous owner may still
-// access `&T` for one grace period.
+// Note that `T: Sync` is required since when moving an `RcuBox<T, A>`, the previous owner may
+// still access `&T` for one grace period.

 //

rust/kernel/sync/rcu/rcu_box.rs +3 -3
-// SAFETY: Ownership of the `RcuBox<T>` allows for `&T` and dropping the `T`, so `T: Send + Sync`
-// implies `RcuBox<T>: Send`.
-unsafe impl<T: Send + Sync> Send for RcuBox<T> {}
+// SAFETY: Ownership of the `RcuBox<T, A>` allows for `&T` and dropping the `T`, so `T: Send +
+// Sync` implies `RcuBox<T, A>: Send`.
+unsafe impl<T: Send + Sync, A: Allocator> Send for RcuBox<T, A> {}

rust/kernel/sync/rcu/rcu_box.rs +3 -3
-// SAFETY: `&RcuBox<T>` allows for no operations other than those permitted by `&T`, so `T: Sync`
-// implies `RcuBox<T>: Sync`.
-unsafe impl<T: Send + Sync> Sync for RcuBox<T> {}
+// SAFETY: `&RcuBox<T, A>` allows for no operations other than those permitted by `&T`, so `T:
+// Sync` implies `RcuBox<T, A>: Sync`.
+unsafe impl<T: Send + Sync, A: Allocator> Sync for RcuBox<T, A> {}

rust/kernel/sync/rcu/rcu_box.rs +1 -1
-impl<T: Send> RcuBox<T> {
+impl<T: Send, A: Allocator> RcuBox<T, A> {

     /// Create a new RcuBox.
     pub fn new(x: T, flags: alloc::Flags) -> Result<Self, AllocError> { -        let b = KBox::new(
+        let b = Box::<_, A>::new(
             RcuBoxInner {
                 value: x,
                 rcu_head: Default::default(),

rust/kernel/sync/rcu/rcu_box.rs +0 -0
@@ -53,9 +65,9 @@ pub fn new(x: T, flags: alloc::Flags) -> Result<Self, AllocError> {
          )?;


         // INVARIANT:
-        // * The pointer contains a valid RcuBoxInner allocated with kmalloc. +        // * The pointer contains a valid RcuBoxInner allocated with A. // * We just allocated it, so we own free permissions.
-        Ok(RcuBox(NonNull::from(KBox::leak(b))))
+        Ok(RcuBox(NonNull::from(Box::leak(b)), PhantomData))
     }

     /// Access the value for a grace period.

rust/kernel/sync/rcu/rcu_box.rs +0 -0
@@ -66,7 +78,7 @@ pub fn with_rcu<'rcu>(&self, _read_guard: &'rcu Guard) -> &'rcu T {
      }
  }

rust/kernel/sync/rcu/rcu_box.rs +1 -1
-impl<T: Send> Deref for RcuBox<T> {
+impl<T: Send, A: Allocator> Deref for RcuBox<T, A> {

     type Target = T;
     fn deref(&self) -> &T {
         // SAFETY: While the RcuBox<T> exists, the value remains valid.

rust/kernel/sync/rcu/rcu_box.rs +0 -0
@@ -75,10 +87,10 @@ fn deref(&self) -> &T {
  }


 // SAFETY:

rust/kernel/sync/rcu/rcu_box.rs +1 -1
-// * The `RcuBoxInner<T>` was allocated with `kmalloc`.
+// * The `RcuBoxInner<T>` was allocated with `A`.

 // * NonNull::as_ptr returns a non-null pointer.

rust/kernel/sync/rcu/rcu_box.rs +2 -2
-unsafe impl<T: Send + 'static> ForeignOwnable for RcuBox<T> {
-    const FOREIGN_ALIGN: usize = <KBox<RcuBoxInner<T>> as ForeignOwnable>::FOREIGN_ALIGN;
+unsafe impl<T: Send + 'static, A: Allocator> ForeignOwnable for RcuBox<T, A> {
+    const FOREIGN_ALIGN: usize = <Box<RcuBoxInner<T>, A> as ForeignOwnable>::FOREIGN_ALIGN;


     type Borrowed<'a> = &'a T;
     type BorrowedMut<'a> = &'a T;

rust/kernel/sync/rcu/rcu_box.rs +0 -0
@@ -88,9 +100,9 @@ fn into_foreign(self) -> *mut c_void {
      }


     unsafe fn from_foreign(ptr: *mut c_void) -> Self {
-        // INVARIANT: Pointer returned by into_foreign carries same invariants as RcuBox<T>. +        // INVARIANT: Pointer returned by into_foreign, A carries same invariants as RcuBox<T>. // SAFETY: into_foreign never returns a null pointer.
-        Self(unsafe { NonNull::new_unchecked(ptr.cast()) })
+        Self(unsafe { NonNull::new_unchecked(ptr.cast()) }, PhantomData)
     }

     unsafe fn borrow<'a>(ptr: *mut c_void) -> &'a T {

rust/kernel/sync/rcu/rcu_box.rs +0 -0
@@ -104,7 +116,7 @@ unsafe fn borrow_mut<'a>(ptr: *mut c_void) -> &'a T {
      }
  }

rust/kernel/sync/rcu/rcu_box.rs +1 -1
-impl<T: Send + 'static> ForeignOwnableRcu for RcuBox<T> {
+impl<T: Send + 'static, A: Allocator> ForeignOwnableRcu for RcuBox<T, A> {

     type RcuBorrowed<'a> = &'a T;

     unsafe fn rcu_borrow<'a>(ptr: *mut c_void) -> &'a T {

rust/kernel/sync/rcu/rcu_box.rs +0 -0
@@ -114,7 +126,7 @@ unsafe fn rcu_borrow<'a>(ptr: *mut c_void) -> &'a T {
      }
  }

rust/kernel/sync/rcu/rcu_box.rs +1 -1
-impl<T: Send> Drop for RcuBox<T> {
+impl<T: Send, A: Allocator> Drop for RcuBox<T, A> {

     fn drop(&mut self) {
         // SAFETY: The rcu_head field is in-bounds of a valid allocation. let rcu_head = unsafe { &raw mut (*self.0.as_ptr()).rcu_head };

rust/kernel/sync/rcu/rcu_box.rs +5 -3
@@ -122,9 +134,11 @@ fn drop(&mut self) {
              // SAFETY: `rcu_head` is the `rcu_head` field of `RcuBoxInner<T>`. All users will be
              // gone in an rcu grace period. This is the destructor, so we may pass ownership of the
              // allocation.
-            unsafe { bindings::call_rcu(rcu_head, Some(drop_rcu_box::<T>)) };
+            unsafe { bindings::call_rcu(rcu_head, Some(drop_rcu_box::<T, A>)) };
          } else {
              // SAFETY: All users will be gone in an rcu grace period.
+            // TODO: We are luckily since `kvfree_call_rcu()` works on both kmalloc and vmalloc,
+            // maybe a new `Allocator` method is needed.
              unsafe { bindings::kvfree_call_rcu(rcu_head, self.0.as_ptr().cast()) };
          }
      }
@@ -135,11 +149,35 @@ fn drop(&mut self) {
  /// # Safety
  ///
  /// `head` references the `rcu_head` field of an `RcuBoxInner<T>` that has no references to it.
-/// Ownership of the `KBox<RcuBoxInner<T>>` must be passed.
-unsafe extern "C" fn drop_rcu_box<T>(head: *mut bindings::callback_head) {
+/// Ownership of the `Box<RcuBoxInner<T>, A>` must be passed.
+unsafe extern "C" fn drop_rcu_box<T, A: Allocator>(head: *mut bindings::callback_head) {
      // SAFETY: Caller provides a pointer to the `rcu_head` field of a `RcuBoxInner<T>`.
      let box_inner = unsafe { crate::container_of!(head, RcuBoxInner<T>, rcu_head) };


     // SAFETY: Caller ensures exclusive access and passed ownership. -    drop(unsafe { KBox::from_raw(box_inner) });
+    drop(unsafe { Box::<_, A>::from_raw(box_inner) });

rust/kernel/sync/rcu/rcu_box.rs +24 -0
+}
+
+#[kunit_tests(rust_rcu_box)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn rcu_box_basic() -> Result {
+        let rb = RcuBox::<_, alloc::allocator::Kmalloc>::new(42i32, alloc::flags::GFP_KERNEL)?;
+
+        assert_eq!(*rb, 42);
+        assert_eq!(*rb.with_rcu(&Guard::new()), 42);
+
+        drop(rb);
+
+        let rb = RcuBox::<_, alloc::allocator::Vmalloc>::new(42i32, alloc::flags::GFP_KERNEL)?;
+
+        assert_eq!(*rb, 42);
+        assert_eq!(*rb.with_rcu(&Guard::new()), 42);
+
+        drop(rb);
+
+        Ok(())
+    }

 }

#47 lore raw

Re: [PATCH 3/4] rust: Add dma_fence abstractions

In reply to Danilo Krummrich
Quoted 6 lines · Danilo Krummrich

(Not a full review, but a few drive-by comments.)

Quoted 1 line · Philipp Stanner

+#[allow(unused_unsafe)]

What is this needed for?

You know that :-P

Quoted 11 lines
Quoted 2 lines

+impl<F: Send + Sync + DriverFenceAllowedData, C: Send + Sync> FenceCtx<F, C> {

<snip>

Quoted 4 lines

+impl<F: Send + Sync, C: Send + Sync> PinnedDrop for FenceCtx<F, C> { +    fn drop(self: Pin<&mut Self>) {
+        // SAFETY: rcu_barrier() is always safe to be called. +        unsafe { bindings::rcu_barrier() };

We should probably add a safe function for this.

ACK.

Quoted 15 lines
Quoted 14 lines

+impl<T: FenceCb> FenceCbRegistration<T> {
+    /// Register a callback on a fence.
+    ///
+    /// On success the callback is pinned in place and will fire when the fence +    /// signals. On AlreadySignaled the callback is returned to the caller so +    /// that owned resources can be reclaimed.
+    pub fn new<'a>(fence: &'a Fence, callback: T) -> impl PinInit<Self, CallbackError<T>> + 'a +    where
+        T: 'a,
+    {
+        // Uses pin_init_from_closure instead of try_pin_init! so that on
+        // -ENOENT (already signaled) the callback can be read back from the +        // partially-initialized slot and returned through the error.

Quoted 3 lines

Seems a bit odd that this needs pin_init_from_closure(). You can still use try_pin_init!() with &this in Self an a _: initializer at the end in the worst case. But the fence and callback fields should be fine to initialize "normally"?

I'll investigate that.

Quoted 34 lines
Quoted 34 lines

+        //
+        // SAFETY: pin_init_from_closure requires:
+        // - On Ok(()): the slot is fully initialized and valid for Drop. +        // - On Err(_): the slot is clean, i.e.: no partially-initialized fields +        //   remain, and the slot can be deallocated without dropping. +        //
+        // We uphold this as follows:
+        // - On success: all three fields are initialized. Ok(()) is returned. +        // - On ENOENT (already signaled): callback and fence are read back +        //   from the slot via ptr::read, leaving the slot clean. cb was +        //   initialized by dma_fence_add_callback (it calls
+        //   INIT_LIST_HEAD(&cb->node) even on error), but cb is +        //   Opaque<dma_fence_cb> which has no Drop, so not dropping it is +        //   fine. The callback is returned through AlreadySignaled(T). +        // - On other errors: same cleanup as ENOENT, error returned as +        //   Other(e).
+        unsafe {
+            pin_init_from_closure(move |slot: *mut Self| {
+                let slot_callback = &raw mut (*slot).callback;
+                let slot_fence = &raw mut (*slot).fence;
+                let slot_cb = &raw mut (*slot).cb;

rust/kernel/dma_buf/dma_fence.rs new file +12 -0
+
+                // Write callback and fence first — must be visible before
+                // dma_fence_add_callback makes the registration live.
+                core::ptr::write(slot_callback, callback);
+                core::ptr::write(slot_fence, ARef::from(fence));
+
+                let ret = to_result(bindings::dma_fence_add_callback(
+                    fence.inner.get(),
+                    Opaque::cast_into(slot_cb),
+                    Some(Self::dma_fence_callback),
+                ));
+
Quoted 8 lines
Quoted 6 lines

+                match ret {
+                    Ok(()) => Ok(()),
+                    Err(e) => {
+                        // Read back what we wrote to leave the slot clean. +                        let cb_back = core::ptr::read(slot_callback); +                        let _fence_back = core::ptr::read(slot_fence);

What's the purpose of _fence_back?

Relic. Will rework.

Quoted 26 lines
Quoted 26 lines
rust/kernel/dma_buf/dma_fence.rs new file +25 -0
+
+                        if e.to_errno() == ENOENT.to_errno() {
+                            Err(CallbackError::AlreadySignaled(cb_back))
+                        } else {
+                            Err(CallbackError::Other(e))
+                        }
+                    }
+                }
+            })
+        }
+    }
+    /// Signal the fence. This will invoke all registered callbacks.
+    pub fn signal(self, res: Result) {
+        let fence = self.as_raw();
+        let mut fence_flags: usize = 0;
+        let flag_ptr = &raw mut fence_flags;
+
+        // SAFETY: Once a `DriverFence` is initialized, the inner `fence` is
+        // valid and initialized. It is valid until the refcount drops
+        // to 0, which can earliest happen once the `DriverFence` has been dropped.
+        unsafe {
+            bindings::dma_fence_lock_irqsave(fence, flag_ptr);
+            if !bindings::dma_fence_is_signaled_locked(fence) {
+                if let Err(err) = res {
+                    bindings::dma_fence_set_error(fence, err.to_errno());
Quoted 8 lines
Quoted 5 lines

+                }
+                bindings::dma_fence_signal_locked(fence); +            }
+            bindings::dma_fence_unlock_irqrestore(fence, flag_ptr); +        }

Please use a single unsafe block per unsafe function call, here and in a few other places.

Is that an official rule? If so, the linters should inform about it.

At first glance, I don't see any advantage to it and the disadvantage of greatly reducing readability.

Quoted 29 lines
Quoted 11 lines

+    }

rust/kernel/dma_buf/dma_fence.rs new file +9 -0
+}
+
+// SAFETY: Fences are literally designed to be shared between threads.
+unsafe impl<F: Send + Sync, C: Send + Sync> Send for DriverFence<F, C> {}
+
+impl<F: Send + Sync, C: Send + Sync> Deref for DriverFence<F, C> {
+    type Target = F;
+
+    fn deref(&self) -> &Self::Target {

rust/kernel/dma_buf/dma_fence.rs line 686

Quoted 10 lines
rust/kernel/dma_buf/dma_fence.rs new file +10 -0
+        // SAFETY: Thanks to refcounting, `data` is always valid as long as `self` is.
+        let data = unsafe { &*self.data.as_ptr() };
+
+        &data.data
+    }
+}
+
+/// A borrowed [`DriverFence`]. All you can do with it is access your user data
+/// and obtain a [`Fence`].
+pub struct DriverFenceBorrow<F: Send + Sync, C: Send + Sync> {

This misses the lifetime bound, which is the purpose of this struct.

Quoted 4 lines

+    /// The actual content of the fence. Lives in a raw pointer so that its +    /// memory can be managed independently. Valid until both the [DriverFence] +    /// and all associated [Fence]s have disappeared.
+    data: NonNull<DriverFenceData<F, C>>,

Quoted 5 lines

Why not use ManuallyDrop<DriverFence>? This way you would only need a Deref impl to &'a DriverFence.

This way you basically reimplement the DriverFence type just without the destructor.

Good idea, will do.

P.

maintainer Reviewed-by on v1 1/4
#3 lore raw

Re: [PATCH 1/4] rust: types: implement ForeignOwnable for ARef<T>

In reply to Philipp Stanner
Quoted 9 lines · Philipp Stanner

From: Danilo Krummrich <[email protected]>

Implement ForeignOwnable for ARef<T>, making it possible for C code to own an ARef<T>.

Since ARef represents shared ownership, BorrowedMut is &T rather than &mut T, matching the semantics of the underlying reference-counted type.

maintainer
#48 lore raw

Re: [PATCH 3/4] rust: Add dma_fence abstractions

In reply to Philipp Stanner
Quoted 9 lines · Philipp Stanner
Quoted 6 lines · Danilo Krummrich

(Not a full review, but a few drive-by comments.)

Quoted 1 line · Philipp Stanner

+#[allow(unused_unsafe)]

What is this needed for?

You know that :-P

I don't, it's a serious question.

Quoted 14 lines
Quoted 14 lines
Quoted 13 lines

+        // SAFETY: Once a DriverFence is initialized, the inner fence is +        // valid and initialized. It is valid until the refcount drops +        // to 0, which can earliest happen once the DriverFence has been dropped. +        unsafe {
+            bindings::dma_fence_lock_irqsave(fence, flag_ptr);
+            if !bindings::dma_fence_is_signaled_locked(fence) {
+                if let Err(err) = res {
+                    bindings::dma_fence_set_error(fence, err.to_errno()); +                }
+                bindings::dma_fence_signal_locked(fence);
+            }
+            bindings::dma_fence_unlock_irqrestore(fence, flag_ptr);
+        }

Quoted 7 lines
Quoted 2 lines

Please use a single unsafe block per unsafe function call, here and in a few other places.

Is that an official rule? If so, the linters should inform about it.

At first glance, I don't see any advantage to it and the disadvantage of greatly reducing readability.

The advantage is that it separates the safety justifications per unsafe call, which increases the chances of catching a bug, makes it easier for the reader to match requirements against justifications and potentially allows tooling to perform checks of the justification against the requirement.

For the specific case above, there's no documented requirements in the sense of Rust safety requirements of course, as they are all FFI calls, but dma_fence_signal_locked() for instance has the obvious requirement that it must only be called with the fence lock held and dma_fence_set_error() must only be called when the fence is actually signaled.

Besides that, since this pattern seems to occur at least twice, you could also consider adding a lock guard.

maintainer
#17 lore raw

Re: [PATCH 3/4] rust: Add dma_fence abstractions

In reply to [email protected]
Quoted 8 lines · On Sat, May 30, 2026 at 03:06:21PM +0000, [email protected] wrote:

rust/kernel/dma_buf/dma_fence.rs line 614

Quoted 4 lines
rust/kernel/dma_buf/dma_fence.rs new file +4 -0
+impl<F: Send + Sync, C: Send + Sync> DriverFence<F, C> {
+    fn as_raw(&self) -> *mut bindings::dma_fence {
+        // SAFETY: Valid because `self` is valid.
+        let fence_data = unsafe { &mut *self.data.as_ptr() };

[Severity: High]
Does this create an exclusive mutable reference to actively shared memory?

Quoted 7 lines

DriverFenceData can be accessed concurrently by other threads holding Fence references (for instance, when checking if the fence is signaled). Creating a mutable reference (&mut) in Rust asserts exclusive access and violates aliasing rules, which allows the compiler to make invalid optimization assumptions.

Could this use an immutable reference &*self.data.as_ptr() instead?

Yes, please use an immutable reference here.

Quoted 6 lines
Quoted 3 lines
rust/kernel/dma_buf/dma_fence.rs new file +3 -0
+
+        fence_data.inner.inner.get()
+    }

[ ... ]

Quoted 8 lines

rust/kernel/dma_buf/dma_fence.rs line 707

Quoted 4 lines
rust/kernel/dma_buf/dma_fence.rs new file +4 -0
+impl<F: Send + Sync, C: Send + Sync> DriverFenceBorrow<F, C> {
+    fn as_raw(&self) -> *mut bindings::dma_fence {
+        // SAFETY: Valid because `self` is valid.
+        let fence_data = unsafe { &mut *self.data.as_ptr() };

[Severity: High]
Similar to DriverFence::as_raw(), does this also incorrectly create a mutable reference to shared data?

Here as well.

Quoted 24 lines
Quoted 3 lines
rust/kernel/dma_buf/dma_fence.rs new file +3 -0
+
+        fence_data.inner.inner.get()
+    }

[ ... ]

Quoted 4 lines
rust/kernel/dma_buf/dma_fence.rs new file +4 -0
+impl<F: Send + Sync, C: Send + Sync> Drop for DriverFence<F, C> {
+    fn drop(&mut self) {
+        let fence = self.as_raw();
+        let mut fence_flags: usize = 0;

rust/kernel/dma_buf/dma_fence.rs line 797

Quoted 10 lines
rust/kernel/dma_buf/dma_fence.rs new file +10 -0
+        let flag_ptr = &raw mut fence_flags;
+
+        // SAFETY: Once a `DriverFence` is initialized, the inner `fence` is
+        // valid and initialized. It is valid until the refcount drops
+        // to 0, which can earliest happen once the `DriverFence` has been dropped.
+        unsafe {
+            bindings::dma_fence_lock_irqsave(fence, flag_ptr);
+            #[allow(unused_unsafe)]
+            if warn_on!(!bindings::dma_fence_is_signaled_locked(fence)) {
+                bindings::dma_fence_set_error(fence, ECANCELED as i32);

[Severity: High]
Is it intentional to pass a positive error code here?

Quoted 6 lines

bindings::ECANCELED evaluates to a positive integer, but dma_fence_set_error() expects a negative error code (and triggers a WARN_ON for positive values). A positive value might also be interpreted as success by callers checking for values < 0.

Should this be - (bindings::ECANCELED as i32)?

Please use ECANCELED.to_errno(). Do not import these from bindings.

Quoted 22 lines

rust/kernel/dma_buf/dma_fence.rs line 809

Quoted 12 lines
rust/kernel/dma_buf/dma_fence.rs new file +12 -0
+                bindings::dma_fence_signal_locked(fence);
+            }
+            bindings::dma_fence_unlock_irqrestore(fence, flag_ptr);
+        }
+
+        // SAFETY: `self.data` is owned by the DriverFence, but could be accessed
+        // through some dma_fence callbacks right now. Access is being revoked
+        // above by signalling the fence. The DriverFenceAllowedData trait
+        // ensures that the data either does not need drop, or if it does it
+        // lives in a RcuBox which will delay dropping by one grace period, hence
+        // ensuring that all readers have disappeared.
+        unsafe { drop_in_place(self.data.as_ptr()) };

[Severity: Critical]
Could this eagerly drop FenceCtx and user data before the C dma_fence refcount reaches 0?

The C dma_fence object can remain alive as long as consumers (like sync_file or the GPU scheduler) hold references to it. If the backend later invokes callbacks like ops->get_driver_name(), FenceCtx::get_driver_name() would attempt to read fctx.driver_name from the already-dropped FenceCtx:

Quoted 6 lines
FenceCtx::get_driver_name() {
    ...
    fctx.driver_name.as_char_ptr()
}

Can this result in a use-after-free?

Hmm, I don't know about what sahisko said, but I don't think this is the right way to do rcu freeing. I think the type's destructor should be reserved for cases where the value becomes immediately unusable.

For example, let's say I'm using RcuBox<_> here. Yes, the data you get from dereferencing the RcuBox will stay alive for a grace period, but IMO once you run the destructor of the box itself, the *pointer* becomes immediately unusable.

Alice

maintainer
#49 lore raw

Re: [PATCH 3/4] rust: Add dma_fence abstractions

In reply to Philipp Stanner
Quoted 189 lines · Philipp Stanner

C's dma_fence's are synchronisation primitives that will be needed by all Rust GPU drivers.

The dma_fence framework sets a number of rules, notably:
- fences must only be signalled once
- all fences must be signalled at some point
- fence error codes must only be set before signalling
- every pointer to a fence must be backed by a reference

All those rules are being addressed by these abstractions.

To cleanly decouple fence issuers and consumers, two types are provided:
- DriverFence: the only fence type that can be signalled and that carries driver-specific data.
- Fence: the fence type to be shared with other drivers and / or userspace. The only type callbacks can be registered on. Cannot be signalled.

Hereby, a Fence lives in the same chunk of memory as a DriverFence. Both share the refcount of the underlying C dma_fence. Since this implementation does not provide a custom dma_fence_backend_ops.release() function, the memory is freed by the dma_fence backend once the refcount drops to 0.

To create a DriverFence, the user must first allocate a DriverFenceAllocation, so that the creation of the DriverFence later on can always succeed. Otherwise, deadlocks could occur if fences need to be created in a GPU job submission path.

Synchronization is ensured by the dma_fence backend.

All DriverFence's created through this abstraction must be signalled by the creator with an error code. In case a DriverFence drops without being signalled beforehand, it is signalled with -ECANCELLED as its error and a warning is printed. This allows the Rust abstraction to very cleanly decouple fence issuer and consumer by relying on the decoupling mechanisms in the C backend, which ensures through RCU and the 'signalled' fence-flag that dma_fence_backend_ops functions cannot access the potentially unloaded driver code anymore.

Signalling fences on drop thus grants many advantages. Not signalling fences on drop would risk deadlock and does not grant real advantages:
By definition only the drivers can ensure that a fence always represents the hardware's state correctly.

This implementation models a DmaFenceCtx (fence context) object on which fences are to be created, thereby ensuring correct sequence numbering according to the timeline.

dma_fence supports a variety of callbacks. The mandatory callbacks (get_timeline_name() and get_driver_name()) are implemented in this patch. For convenience, they store those name parameters in the fence context, saving the driver from implementing these two callbacks.

Support for other callbacks (like for hardware signalling) is prepared for through the fact that both DriverFence and Fence live in the same allocation, allowing for usage of container_of from the callback to access the driver-specific data.

Synchronization for backend_ops callbacks is ensured through RCU which prevents UAF-bugs should a DriverFence drop while a Fence callback is currently operating on the associated driver data.

Add abstractions for dma_fence in Rust.

---
 rust/bindings/bindings_helper.h  |   1 +
 rust/helpers/dma_fence.c         |  48 ++
 rust/helpers/helpers.c           |   1 +
 rust/kernel/dma_buf/dma_fence.rs | 821 +++++++++++++++++++++++++++++++
 rust/kernel/dma_buf/mod.rs       |  13 +
 rust/kernel/lib.rs               |   1 +
 6 files changed, 885 insertions(+)
 create mode 100644 rust/helpers/dma_fence.c
 create mode 100644 rust/kernel/dma_buf/dma_fence.rs
 create mode 100644 rust/kernel/dma_buf/mod.rs
rust/bindings/bindings_helper.h +1 -0
index 2011645c7cfb..69daeb790f77 100644
@@ -52,6 +52,7 @@
 #include <linux/debugfs.h>
 #include <linux/device/faux.h>
 #include <linux/dma-direction.h>
+#include <linux/dma-fence.h>
 #include <linux/dma-mapping.h>
 #include <linux/dma-resv.h>
 #include <linux/errname.h>
rust/helpers/dma_fence.c new file +48 -0
new file mode 100644
index 000000000000..6244a5a61038
@@ -0,0 +1,48 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/dma-fence.h>
+
+__rust_helper void rust_helper_dma_fence_get(struct dma_fence *f)
+{
+	dma_fence_get(f);
+}
+
+__rust_helper void rust_helper_dma_fence_put(struct dma_fence *f)
+{
+	dma_fence_put(f);
+}
+
+__rust_helper bool rust_helper_dma_fence_begin_signalling(void)
+{
+	return dma_fence_begin_signalling();
+}
+
+__rust_helper void rust_helper_dma_fence_end_signalling(bool cookie)
+{
+	dma_fence_end_signalling(cookie);
+}
+
+__rust_helper bool rust_helper_dma_fence_is_signaled(struct dma_fence *f)
+{
+	return dma_fence_is_signaled(f);
+}
+
+__rust_helper bool rust_helper_dma_fence_is_signaled_locked(struct dma_fence *f)
+{
+	return dma_fence_is_signaled_locked(f);
+}
+
+__rust_helper void rust_helper_dma_fence_lock_irqsave(struct dma_fence *f, unsigned long *flags)
+{
+	dma_fence_lock_irqsave(f, *flags);
+}
+
+__rust_helper void rust_helper_dma_fence_unlock_irqrestore(struct dma_fence *f, unsigned long *flags)
+{
+	dma_fence_unlock_irqrestore(f, *flags);
+}
+
+__rust_helper void rust_helper_dma_fence_set_error(struct dma_fence *f, int error)
+{
+	dma_fence_set_error(f, error);
+}
rust/helpers/helpers.c +1 -0
index 625921e27dfb..d9114d0b3c8f 100644
@@ -57,6 +57,7 @@
 #include "cred.c"
 #include "device.c"
 #include "dma.c"
+#include "dma_fence.c"
 #include "dma-resv.c"
 #include "drm.c"
 #include "err.c"
rust/kernel/dma_buf/dma_fence.rs new file +27 -0
new file mode 100644
index 000000000000..7dc1f5c16b02
@@ -0,0 +1,821 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Copyright (C) 2025, 2026 Red Hat Inc.:
+//   - Philipp Stanner <[email protected]>
+
+//! DriverFence support.
+//!
+//! Reference: <https://docs.kernel.org/driver-api/dma-buf.html#c.dma_fence>
+//!
+//! C header: [`include/linux/dma-fence.h`](srctree/include/linux/dma-fence.h)
+
+use crate::{
+    alloc::AllocError,
+    bindings,
+    container_of,
+    error::to_result,
+    prelude::*,
+    sync::rcu::RcuBox,
+    types::ForeignOwnable,
+    types::Opaque,
+    warn_on, //
+};
+
+use pin_init::pin_init_from_closure;
+
+use core::{
+    marker::PhantomData, //

rust/kernel/dma_buf/dma_fence.rs line 37

Quoted 10 lines
rust/kernel/dma_buf/dma_fence.rs new file +10 -0
+    ops::Deref,
+    ptr,
+    ptr::{
+        drop_in_place,
+        NonNull, //
+    },
+    sync::atomic::{
+        AtomicU64,
+        Ordering, //
+    },

Use atomics from the kernel crate instead.

Quoted 10 lines
rust/kernel/dma_buf/dma_fence.rs new file +10 -0
+};
+
+use bindings::ECANCELED;
+
+use kernel::str::CString;
+use kernel::sync::{
+    aref::{
+        ARef,
+        AlwaysRefCounted, //
+    },

rust/kernel/dma_buf/dma_fence.rs line 57

Quoted 10 lines
rust/kernel/dma_buf/dma_fence.rs new file +10 -0
+    Arc,
+    ArcBorrow, //
+};
+
+/// VTable for dma_fence backend_ops callbacks.
+//
+// Mandatory dma_fence backend_ops are implemented implicitly through
+// [`FenceCtx`]. Additional ones shall get implemented on this trait, which then
+// shall be demanded for the fence context data.
+pub trait FenceCtxOps {}

This empty trait is unused.

rust/kernel/dma_buf/dma_fence.rs line 62

Quoted 4 lines
rust/kernel/dma_buf/dma_fence.rs new file +4 -0
+/// A dma-fence context. A fence context takes care of associating related fences with each other,
+/// providing each with raising sequence numbers and a common identifier.
+#[pin_data(PinnedDrop)]
+pub struct FenceCtx<F: Send + Sync, C: Send + Sync> {

No need to list any trait bounds here. You can list them on impl blocks only.

Quoted 263 lines
rust/kernel/dma_buf/dma_fence.rs new file +263 -0
+    /// The fence context number.
+    nr: u64,
+    /// The sequence number for the next fence created.
+    seqno: AtomicU64,
+    // The name parameters live in RcuBox because they can be accessed by the
+    // dma_fence backend_ops. Those accesses are guarded by the rcu_read_lock(),
+    // so dropping them must be delayed by a grace period.
+    /// The name of the driver this FenceCtx's fences belong to.
+    driver_name: CString,
+    /// The name of the timeline this FenceCtx's fences belong to.
+    timeline_name: CString,
+    #[pin]
+    data: C,
+    fence_type: PhantomData<F>,
+}
+
+#[allow(unused_unsafe)]
+impl<F: Send + Sync + DriverFenceAllowedData, C: Send + Sync> FenceCtx<F, C> {
+    // This can later be extended as a vtable in case other parties need support
+    // for the more "exotic" callbacks.
+    const OPS: bindings::dma_fence_ops = bindings::dma_fence_ops {
+        get_driver_name: Some(Self::get_driver_name),
+        get_timeline_name: Some(Self::get_timeline_name),
+        enable_signaling: None,
+        signaled: None,
+        wait: None,
+        release: None,
+        set_deadline: None,
+    };
+
+    /// Create a new `FenceCtx`.
+    pub fn new(
+        driver_name: CString,
+        timeline_name: CString,
+        data: impl PinInit<C>,
+    ) -> Result<Arc<Self>> {
+        let ctx = pin_init!(Self {
+            // SAFETY: `dma_fence_context_alloc()` merely works on a global atomic. Parameter `1`
+            // is the number of contexts we want to allocate.
+            nr: unsafe { bindings::dma_fence_context_alloc(1) },
+            seqno: AtomicU64::new(0),
+            driver_name,
+            timeline_name,
+            data <- data,
+            fence_type: PhantomData,
+        });
+
+        Arc::pin_init(ctx, GFP_KERNEL)
+    }
+
+    fn get_next_fence_seqno(&self) -> u64 {
+        self.seqno.fetch_add(1, Ordering::Relaxed)
+    }
+
+    /// Allocate the memory for a [`DriverFence`] and already store `data` inside.
+    ///
+    /// This is needed because many times, creation of a [`DriverFence`] must not
+    /// fail, and allocating might deadlock in some situations.
+    ///
+    /// The `data` you pass here must not perform any operations that are illegal
+    /// in atomic context in its [`Drop`] implementation.
+    pub fn new_fence_allocation(
+        self: ArcBorrow<'_, Self>,
+        data: F,
+    ) -> Result<DriverFenceAllocation<F, C>> {
+        let fctx = Arc::<Self>::from(self);
+
+        DriverFenceAllocation::new(fctx, data)
+    }
+
+    /// Create a new fence, consuming `data`.
+    ///
+    /// The fence will increment the refcount of the fence context associated with this
+    /// [`FenceCtx`].
+    pub fn new_fence(&self, memory: DriverFenceAllocation<F, C>) -> DriverFence<F, C> {
+        let seqno: u64 = self.get_next_fence_seqno();
+
+        // We feed the C dma_fence backend a NULL for the spinlock so that it
+        // uses per-fence locks automatically.
+        let null_ptr: *mut bindings::spinlock = ptr::null_mut();
+        let fence_ptr = memory.as_raw();
+        // SAFETY: `fence_ptr` has been created directly above. It will live
+        // at least as long as `Self`. The same applies to `&Self::OPS`.
+        unsafe { bindings::dma_fence_init(fence_ptr, &Self::OPS, null_ptr, self.nr, seqno) };
+
+        // A `DriverFenceAllocation`'s purpose is to carry allocated memory, so that
+        // `DriverFence`s can always be created without allocating. In this
+        // method, ownership over that memory is transferred to the new
+        // `DriverFence` and managed through refcounting. The C dma_fence
+        // backend will ultimately free the memory once the refcount reaches 0.
+        let ptr = KBox::into_raw(memory.data);
+        // SAFETY: `ptr` was just created validly directly above.
+        let ptr = unsafe { NonNull::new_unchecked(ptr) };
+
+        DriverFence { data: ptr }
+    }
+
+    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()
+    }
+
+    extern "C" fn get_timeline_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.timeline_name.as_char_ptr()
+    }
+
+    /// Create a [`FenceCtx`] from an associated [`bindings::dma_fence`].
+    ///
+    /// # Safety
+    ///
+    /// `ptr` must be a valid pointer to a dma_fence which resides within a [`Fence`],
+    /// which in turn resides in a [`DriverFenceData`].
+    unsafe fn from_raw_fence<'a>(ptr: *mut bindings::dma_fence) -> &'a Self {
+        let opaque_fence = Opaque::cast_from(ptr);
+
+        // SAFETY: Safe due to the function's overall safety requirements.
+        let fence_ptr = unsafe { container_of!(opaque_fence, Fence, inner) };
+
+        // DriverFenceData is repr(C) and a Fence is its first member.
+        let fence_data_ptr = fence_ptr as *mut DriverFenceData<F, C>;
+
+        // SAFETY: Safe because of the safety comment directly above.
+        let fence_data = unsafe { &*fence_data_ptr };
+
+        &fence_data.fctx
+    }
+}
+
+// FenceCtx's drop() ensures that the driver cannot unload while there are still
+// dma_fence callbacks running. This also prevents UAF problems with fctx.driver_name
+// and fctx.timeline_name.
+//
+// DriverFence data gets dropped through call_rcu() in DriverFence::drop.
+// This `rcu_barrier()` also serves to wait for their completion.
+#[pinned_drop]
+impl<F: Send + Sync, C: Send + Sync> PinnedDrop for FenceCtx<F, C> {
+    fn drop(self: Pin<&mut Self>) {
+        // SAFETY: `rcu_barrier()` is always safe to be called.
+        unsafe { bindings::rcu_barrier() };
+    }
+}
+
+/// Error type for fence callback registration.
+///
+/// Generic over `T` so that `AlreadySignaled` can return the callback to the
+/// caller, allowing it to reclaim any resources owned by the callback (e.g.,
+/// a fence handle that needs to be signaled).
+#[derive(Debug)]
+pub enum CallbackError<T = ()> {
+    /// The fence was already signaled. The callback is returned so the caller
+    /// can extract owned resources without losing them.
+    AlreadySignaled(T),
+    /// Some other error occurred during registration.
+    Other(Error),
+}
+
+impl<T> From<CallbackError<T>> for Error {
+    fn from(err: CallbackError<T>) -> Self {
+        match err {
+            CallbackError::AlreadySignaled(_) => ENOENT,
+            CallbackError::Other(e) => e,
+        }
+    }
+}
+
+impl<T> From<AllocError> for CallbackError<T> {
+    fn from(e: AllocError) -> Self {
+        CallbackError::Other(Error::from(e))
+    }
+}
+
+/// Trait for callbacks that can be registered on fences.
+///
+/// When the fence signals, the callback will be invoked.
+///
+/// # Example
+///
+/// ```rust
+/// use kernel::dma_buf::FenceCb;
+///
+/// struct MyCallback {
+///     // Your callback state here
+/// }
+///
+/// impl FenceCb for MyCallback {
+///     fn called(&mut self) {
+///         pr_info!("Fence signaled!");
+///         // Handle fence completion
+///     }
+/// }
+/// ```
+pub trait FenceCb: Send + 'static {
+    /// Called when the fence is signaled.
+    ///
+    /// This is called from the fence signaling path, which may be in interrupt
+    /// context or with locks held, which is why `self` is only borrowed, so that
+    /// it cannot drop. Implementations must not sleep or perform
+    /// long-running operations.
+    ///
+    /// An implementation likely wants to inform itself (e.g., through a work item)
+    /// within this callback that the associated [`FenceCbRegistration`] can now be
+    /// dropped.
+    fn called(&mut self);
+}
+
+/// A callback registration on a fence.
+///
+/// When this object is dropped, the callback is automatically removed if it
+/// hasn't been called yet.
+///
+/// # Invariants
+///
+/// If `callback` is `Some`, then `cb` is registered with the fence and the
+/// callback hasn't been invoked yet. If `None`, the callback has been invoked
+/// or the fence was already signaled when we tried to register.
+#[pin_data(PinnedDrop)]
+pub struct FenceCbRegistration<T: FenceCb + 'static> {
+    #[pin]
+    cb: Opaque<bindings::dma_fence_cb>,
+    callback: T,
+    fence: ARef<Fence>,
+}
+
+impl<T: FenceCb> FenceCbRegistration<T> {
+    /// Register a callback on a fence.
+    ///
+    /// On success the callback is pinned in place and will fire when the fence
+    /// signals. On `AlreadySignaled` the callback is returned to the caller so
+    /// that owned resources can be reclaimed.
+    pub fn new<'a>(fence: &'a Fence, callback: T) -> impl PinInit<Self, CallbackError<T>> + 'a
+    where
+        T: 'a,
+    {
+        // Uses `pin_init_from_closure` instead of `try_pin_init!` so that on
+        // `-ENOENT` (already signaled) the callback can be read back from the
+        // partially-initialized slot and returned through the error.
+        //
+        // SAFETY: `pin_init_from_closure` requires:
+        // - On `Ok(())`: the slot is fully initialized and valid for `Drop`.
+        // - On `Err(_)`: the slot is clean, i.e.: no partially-initialized fields
+        //   remain, and the slot can be deallocated without dropping.
+        //
+        // We uphold this as follows:
+        // - On success: all three fields are initialized. Ok(()) is returned.
+        // - On ENOENT (already signaled): `callback` and `fence` are read back
+        //   from the slot via `ptr::read`, leaving the slot clean. `cb` was
+        //   initialized by `dma_fence_add_callback` (it calls
+        //   `INIT_LIST_HEAD(&cb->node)` even on error), but `cb` is
+        //   `Opaque<dma_fence_cb>` which has no `Drop`, so not dropping it is
+        //   fine. The callback is returned through `AlreadySignaled(T)`.
+        // - On other errors: same cleanup as ENOENT, error returned as
+        //   `Other(e)`.

rust/kernel/dma_buf/dma_fence.rs line 335

Quoted 10 lines
rust/kernel/dma_buf/dma_fence.rs new file +10 -0
+        unsafe {
+            pin_init_from_closure(move |slot: *mut Self| {
+                let slot_callback = &raw mut (*slot).callback;
+                let slot_fence = &raw mut (*slot).fence;
+                let slot_cb = &raw mut (*slot).cb;
+
+                // Write callback and fence first — must be visible before
+                // dma_fence_add_callback makes the registration live.
+                core::ptr::write(slot_callback, callback);
+                core::ptr::write(slot_fence, ARef::from(fence));

Here you are incrementing the fence refcount. It's better to change the function argument to ARef<Fence> so that the user can avoid this increment if they happen to own a refcount they're willing to give up.

rust/kernel/dma_buf/dma_fence.rs line 348

Quoted 12 lines
rust/kernel/dma_buf/dma_fence.rs new file +12 -0
+                let ret = to_result(bindings::dma_fence_add_callback(
+                    fence.inner.get(),
+                    Opaque::cast_into(slot_cb),
+                    Some(Self::dma_fence_callback),
+                ));
+
+                match ret {
+                    Ok(()) => Ok(()),
+                    Err(e) => {
+                        // Read back what we wrote to leave the slot clean.
+                        let cb_back = core::ptr::read(slot_callback);
+                        let _fence_back = core::ptr::read(slot_fence);

This can be drop_in_place().

Quoted 25 lines
rust/kernel/dma_buf/dma_fence.rs new file +25 -0
+                        if e.to_errno() == ENOENT.to_errno() {
+                            Err(CallbackError::AlreadySignaled(cb_back))
+                        } else {
+                            Err(CallbackError::Other(e))
+                        }
+                    }
+                }
+            })
+        }
+    }
+
+    /// Raw dma fence callback that is called by the C code.
+    ///
+    /// # Safety
+    ///
+    /// This is only called by the dma_fence subsystem with valid pointers.
+    unsafe extern "C" fn dma_fence_callback(
+        _fence: *mut bindings::dma_fence,
+        cb: *mut bindings::dma_fence_cb,
+    ) {
+        let ptr = Opaque::cast_from(cb).cast_mut();
+
+        // SAFETY: All `cb` we can receive here have been created in such a way
+        // that they are embedded into a `FenceCbRegistration`. The backend
+        // ensures synchronisation so whoever holds the registration object

rust/kernel/dma_buf/dma_fence.rs line 384

Quoted 10 lines
rust/kernel/dma_buf/dma_fence.rs new file +10 -0
+        // cannot drop it while this code is running. See `FenceCbRegistration::drop`.
+        unsafe {
+            let reg: *mut Self = container_of!(ptr, Self, cb);
+
+            (*reg).callback.called();
+        }
+    }
+
+    /// Returns a reference to the fence this callback is registered on.
+    pub fn fence(self: Pin<&Self>) -> &Fence {

Can be simplified to fn fence(&self) -> &Fence.

Quoted 19 lines
rust/kernel/dma_buf/dma_fence.rs new file +19 -0
+        &self.get_ref().fence
+    }
+}
+
+#[pinned_drop]
+impl<T: FenceCb> PinnedDrop for FenceCbRegistration<T> {
+    fn drop(self: Pin<&mut Self>) {
+        // Always call dma_fence_remove_callback, even if `callback` has already
+        // been taken by `dma_fence_callback`.  This is necessary for
+        // synchronization: `dma_fence_remove_callback` acquires `fence->lock`,
+        // which ensures that any in-flight `dma_fence_signal` (which calls our
+        // callback while holding the same lock) has completed before we free
+        // the struct.
+        //
+        // Without this, Drop can race with a concurrent signal:
+        //   CPU0 (signal, lock held): take() -> signaled(fence_ref) (in progress)
+        //   CPU1 (drop): sees is_some()==false -> skips lock -> frees struct
+        //   CPU0: accesses fence_ref -> use-after-free
+        //

rust/kernel/dma_buf/dma_fence.rs line 413

Quoted 10 lines
rust/kernel/dma_buf/dma_fence.rs new file +10 -0
+        // When the callback has already fired, the signal path detached the
+        // list node via INIT_LIST_HEAD, so dma_fence_remove_callback just sees
+        // an empty node and returns false — the lock acquisition is the only
+        // thing that matters.
+        //
+        // SAFETY: The fence pointer is valid and the cb was initialized by
+        // dma_fence_add_callback during construction.
+        unsafe {
+            bindings::dma_fence_remove_callback(self.fence.as_raw(), self.cb.get());
+        }

Formatting nit: Usually the ; goes outside the unsafe block.

rust/kernel/dma_buf/dma_fence.rs line 421

Quoted 8 lines
rust/kernel/dma_buf/dma_fence.rs new file +8 -0
+    }
+}
+
+// SAFETY: FenceCbRegistration can be sent between threads
+unsafe impl<T: FenceCb> Send for FenceCbRegistration<T> {}
+
+// SAFETY: &FenceCbRegistration can be shared between threads if &T can.
+unsafe impl<T: FenceCb> Sync for FenceCbRegistration<T> where T: Sync {}

There's no &FenceCbRegistration<T> -> &T accessor, so I don't think this bound is required.

	unsafe impl<T: FenceCb> Sync for FenceCbRegistration<T> {}

There also can't be such an accessor in the future because the closure takes a &mut T.

Quoted 49 lines
rust/kernel/dma_buf/dma_fence.rs new file +49 -0
+/// The receiving counterpart of a [`DriverFence`], designed to register callbacks
+/// on, check the signalled state etc. A [`Fence`] cannot be signalled.
+/// A [`Fence`] is always refcounted.
+pub struct Fence {
+    /// The actual dma_fence passed to C.
+    inner: Opaque<bindings::dma_fence>,
+}
+
+// SAFETY: Fences are literally designed to be shared between threads.
+unsafe impl Send for Fence {}
+// SAFETY: Fences are literally designed to be shared between threads.
+unsafe impl Sync for Fence {}
+
+impl Fence {
+    /// Check whether the fence was signalled at the moment of the function call.
+    pub fn is_signaled(&self) -> bool {
+        // SAFETY: self is by definition still valid. The backend ensures proper
+        // locking.
+        unsafe { bindings::dma_fence_is_signaled(self.as_raw()) }
+    }
+
+    fn as_raw(&self) -> *mut bindings::dma_fence {
+        self.inner.get()
+    }
+
+    /// Create a [`Fence`] from a raw C [`bindings::dma_fence`].
+    ///
+    /// # Safety
+    ///
+    /// `ptr` must point to an initialized fence that is embedded into a [`Fence`].
+    pub unsafe fn from_raw<'a>(ptr: *mut bindings::dma_fence) -> &'a Self {
+        // SAFETY: Safe as per the function's overall safety requirements.
+        unsafe { &*ptr.cast() }
+    }
+}
+
+// SAFETY: These implement the C backends refcounting methods which are proven to work correctly.
+unsafe impl AlwaysRefCounted for Fence {
+    fn inc_ref(&self) {
+        // SAFETY: `self.as_raw()` is a pointer to a valid `struct dma_fence`.
+        unsafe { bindings::dma_fence_get(self.as_raw()) }
+    }
+
+    /// # Safety
+    ///
+    /// `ptr`must be a valid pointer to a [`DriverFence`].
+    unsafe fn dec_ref(ptr: NonNull<Self>) {
+        // SAFETY: `ptr` is never a NULL pointer; and when `dec_ref()` is called
+        // the fence is by definition still valid.

rust/kernel/dma_buf/dma_fence.rs line 481

Quoted 10 lines
rust/kernel/dma_buf/dma_fence.rs new file +10 -0
+        let fence = unsafe { (*ptr.as_ptr()).inner.get() };
+
+        // SAFETY: Valid because `fence` was created validly above.
+        unsafe { bindings::dma_fence_put(fence) }
+    }
+}
+
+#[repr(C)] // Necessary to guarantee that `inner` always comes first so that we can cast.
+#[pin_data]
+struct DriverFenceData<F: Send + Sync, C: Send + Sync> {

Ditto here about trait bounds. (And everywhere else.)

Quoted 17 lines
rust/kernel/dma_buf/dma_fence.rs new file +17 -0
+    #[pin]
+    /// The inner fence.
+    inner: Fence,
+    /// Pointer to access the FenceCtx. Useful for obtaining name parameters.
+    // The FenceCtx lives as long as at least all its fences, hence this is safe.
+    fctx: Arc<FenceCtx<F, C>>,
+    /// The API user's data. As required by [`DriverFenceAllowedData`], this either
+    /// does not need drop, or must live in a [`rcu::RcuBox`]. It is essential
+    /// that the data only performs operations legal in atomic context in its
+    /// [`Drop`] implementation.
+    #[pin]
+    data: F,
+}
+
+/// A trait to enforce that all data in a [`DriverFence`] either does not need
+/// drop, or lives in a [`RcuBox`].
+pub trait DriverFenceAllowedData: private::Sealed {}

rust/kernel/dma_buf/dma_fence.rs line 508

Quoted 10 lines
rust/kernel/dma_buf/dma_fence.rs new file +10 -0
+
+mod private {
+    pub trait Sealed {}
+}
+
+impl<F: Copy> DriverFenceAllowedData for F {}
+impl<F: Send> DriverFenceAllowedData for RcuBox<F> {}
+
+impl<F: Copy> private::Sealed for F {}
+impl<F: Send> private::Sealed for RcuBox<F> {}

Why sealed? Just make the trait unsafe and require the things you require from the user.

Quoted 7 lines
rust/kernel/dma_buf/dma_fence.rs new file +7 -0
+/// A synchronization primitive mainly for GPU drivers.
+///
+/// Fences are always reference counted. The typical use case is that one side registers
+/// callbacks on the fence which will perform a certain action (such as queueing work) once the
+/// other side signals the fence.
+///
+/// # Examples

rust/kernel/dma_buf/dma_fence.rs line 526

Quoted 10 lines
rust/kernel/dma_buf/dma_fence.rs new file +10 -0
+///
+/// ```
+/// use kernel::dma_buf::{DriverFence, FenceCtx, FenceCb, FenceCbRegistration};
+/// use kernel::str::CString;
+/// use kernel::sync::{
+///     aref::ARef,
+///     rcu::RcuBox, //
+/// };
+/// use core::ops::Deref;
+/// use core::fmt::Display;

Use fmt traits from kernel instead. (Actually, I don't think you use Display at all here?)

Quoted 129 lines
rust/kernel/dma_buf/dma_fence.rs new file +129 -0
+/// struct CallbackData { }
+///
+/// impl FenceCb for CallbackData {
+///     fn called(&mut self) {
+///         pr_info!("DmaFence callback executed.\n");
+///     }
+/// }
+///
+/// let driver_name = CString::try_from_fmt(fmt!("dummy_driver"))?;
+/// let timeline_name = CString::try_from_fmt(fmt!("dummy_timeline"))?;
+///
+/// let fctx = FenceCtx::new(driver_name, timeline_name, ())?;
+///
+/// let fence_data = CString::try_from_fmt(fmt!("dummy_data"))?;
+/// // DriverFence::data must either not need drop, or live in an RcuBox.
+/// let fence_data = RcuBox::new(fence_data, GFP_KERNEL)?;
+///
+/// let fence_alloc = fctx.as_arc_borrow().new_fence_allocation(fence_data)?;
+/// let mut fence = fctx.new_fence(fence_alloc);
+///
+/// let cb_data = CallbackData { };
+/// let waiting_fence = ARef::from(fence.as_fence());
+/// let cb_reg = FenceCbRegistration::new(&waiting_fence, cb_data);
+/// let cb_reg = KBox::pin_init(cb_reg, GFP_KERNEL)?;
+///
+/// // DriverFence implements Deref.
+/// // FIXME: unit test claims that CString does not implement Display. Why?
+/// // pr_info!("Fence's inner data is: {}", fence.deref().deref());
+///
+/// // TODO begin_signalling
+/// fence.signal(Ok(()));
+/// assert_eq!(waiting_fence.is_signaled(), true);
+///
+/// Ok::<(), Error>(())
+/// ```
+pub struct DriverFence<F: Send + Sync, C: Send + Sync> {
+    /// The actual content of the fence. Lives in a raw pointer so that its
+    /// memory can be managed independently. Valid until both the [`DriverFence`]
+    /// and all associated [`Fence`]s have disappeared.
+    data: NonNull<DriverFenceData<F, C>>,
+}
+
+/// A pre-prepared DMA fence, carrying the user's data and the memory it and the
+/// fence reside in. Only useful for creating a [`DriverFence`]. Splitting
+/// allocation and full initialization is necessary because fences cannot be
+/// allocated dynamically in some circumstances (deadlock).
+pub struct DriverFenceAllocation<F: Send + Sync, C: Send + Sync> {
+    /// The memory for the actual content of the fence.
+    /// Handed over to a [`DriverFence`], or deallocated once the
+    /// [`DriverFenceAllocation`] drops.
+    data: KBox<DriverFenceData<F, C>>,
+}
+
+impl<F: Send + Sync + DriverFenceAllowedData, C: Send + Sync> DriverFenceAllocation<F, C> {
+    /// Create a new allocation slot that can later be used to create a fully
+    /// initialized [`DriverFence`] without the need to allocate.
+    pub fn new(fctx: Arc<FenceCtx<F, C>>, data: F) -> Result<Self> {
+        let fence_data = DriverFenceData {
+            // `inner` remains uninitialized until a [`DriverFence`] takes over.
+            inner: Fence {
+                inner: Opaque::uninit(),
+            },
+            fctx,
+            data,
+        };
+
+        // In order to support the C dma_fence callbacks, it is necessary for
+        // a `Fence` and a `DriverFence` to live in the same allocation,
+        // because the C backend passes a dma_fence, from which the driver most
+        // likely wants to be able to access its `data` in `DriverFence`.
+        //
+        // Hence, we need the manage the memory manually. It will be freed by the
+        // C backend automatically once the refcount within `Fence` drops to 0.
+        let data = KBox::new(fence_data, GFP_KERNEL | __GFP_ZERO)?;
+
+        Ok(Self { data })
+    }
+
+    fn as_raw(&self) -> *mut bindings::dma_fence {
+        self.data.inner.inner.get()
+    }
+}
+
+impl<F: Send + Sync, C: Send + Sync> DriverFence<F, C> {
+    fn as_raw(&self) -> *mut bindings::dma_fence {
+        // SAFETY: Valid because `self` is valid.
+        let fence_data = unsafe { &mut *self.data.as_ptr() };
+
+        fence_data.inner.inner.get()
+    }
+
+    /// Create a [`DriverFence`] from a raw pointer to a [`bindings::dma_fence`].
+    ///
+    /// # Safety
+    ///
+    /// `ptr` must be a valid pointer to a `dma_fence` that was obtained through
+    /// a [`DriverFence`] with matching generic data for both fence and associated
+    /// [`FenceCtx`].
+    unsafe fn from_raw(ptr: *mut bindings::dma_fence) -> Self {
+        let opaque_fence = Opaque::cast_from(ptr);
+
+        // SAFETY: Safe due to the function's overall safety requirements.
+        let fence_ptr = unsafe { container_of!(opaque_fence, Fence, inner) };
+
+        // DriverFenceData is repr(C) and a Fence is its first member.
+        let fence_data_ptr = fence_ptr as *mut DriverFenceData<F, C>;
+
+        // SAFETY: `fence_data_ptr` was created validly above.
+        let data = unsafe { NonNull::new_unchecked(fence_data_ptr) };
+
+        Self { data }
+    }
+
+    /// Return the underlying [`Fence`].
+    pub fn as_fence(&self) -> &Fence {
+        // SAFETY: `self` is by definition still valid, and it cannot drop until
+        // this new reference is gone.
+        unsafe { Fence::from_raw(self.as_raw()) }
+    }
+
+    /// Signal the fence. This will invoke all registered callbacks.
+    pub fn signal(self, res: Result) {
+        let fence = self.as_raw();
+        let mut fence_flags: usize = 0;
+        let flag_ptr = &raw mut fence_flags;
+
+        // SAFETY: Once a `DriverFence` is initialized, the inner `fence` is
+        // valid and initialized. It is valid until the refcount drops
+        // to 0, which can earliest happen once the `DriverFence` has been dropped.

rust/kernel/dma_buf/dma_fence.rs line 666

Quoted 10 lines
rust/kernel/dma_buf/dma_fence.rs new file +10 -0
+        unsafe {
+            bindings::dma_fence_lock_irqsave(fence, flag_ptr);
+            if !bindings::dma_fence_is_signaled_locked(fence) {
+                if let Err(err) = res {
+                    bindings::dma_fence_set_error(fence, err.to_errno());
+                }
+                bindings::dma_fence_signal_locked(fence);
+            }
+            bindings::dma_fence_unlock_irqrestore(fence, flag_ptr);
+        }

This single unsafe blocks spans five different unsafe operations.

Quoted 84 lines
rust/kernel/dma_buf/dma_fence.rs new file +84 -0
+    }
+}
+
+// SAFETY: Fences are literally designed to be shared between threads.
+unsafe impl<F: Send + Sync, C: Send + Sync> Send for DriverFence<F, C> {}
+
+impl<F: Send + Sync, C: Send + Sync> Deref for DriverFence<F, C> {
+    type Target = F;
+
+    fn deref(&self) -> &Self::Target {
+        // SAFETY: Thanks to refcounting, `data` is always valid as long as `self` is.
+        let data = unsafe { &*self.data.as_ptr() };
+
+        &data.data
+    }
+}
+
+/// A borrowed [`DriverFence`]. All you can do with it is access your user data
+/// and obtain a [`Fence`].
+pub struct DriverFenceBorrow<F: Send + Sync, C: Send + Sync> {
+    /// The actual content of the fence. Lives in a raw pointer so that its
+    /// memory can be managed independently. Valid until both the [`DriverFence`]
+    /// and all associated [`Fence`]s have disappeared.
+    data: NonNull<DriverFenceData<F, C>>,
+}
+
+impl<F: Send + Sync, C: Send + Sync> Deref for DriverFenceBorrow<F, C> {
+    type Target = F;
+
+    fn deref(&self) -> &Self::Target {
+        // SAFETY: Thanks to refcounting, `data` is always valid as long as `self` is.
+        let data = unsafe { &*self.data.as_ptr() };
+
+        &data.data
+    }
+}
+
+impl<F: Send + Sync, C: Send + Sync> DriverFenceBorrow<F, C> {
+    fn as_raw(&self) -> *mut bindings::dma_fence {
+        // SAFETY: Valid because `self` is valid.
+        let fence_data = unsafe { &mut *self.data.as_ptr() };
+
+        fence_data.inner.inner.get()
+    }
+
+    /// Return the underlying [`Fence`].
+    pub fn as_fence(&self) -> &Fence {
+        // SAFETY: `self` is by definition still valid, and it cannot drop until
+        // this new reference is gone.
+        unsafe { Fence::from_raw(self.as_raw()) }
+    }
+
+    /// Get a [`DriverFenceBorrow`] from a raw pointer.
+    ///
+    /// # Safety
+    ///
+    /// `ptr` must point to a raw dma_fence within a [`Fence`] within a [`DriverFenceData`].
+    unsafe fn from_raw(ptr: *mut bindings::dma_fence) -> Self {
+        let opaque_fence = Opaque::cast_from(ptr);
+
+        // SAFETY: Safe due to the function's overall safety requirements.
+        let fence_ptr = unsafe { container_of!(opaque_fence, Fence, inner) };
+
+        // DriverFenceData is repr(C) and a Fence is its first member.
+        let fence_data_ptr = fence_ptr as *mut DriverFenceData<F, C>;
+
+        // SAFETY: `fence_data_ptr` was created validly above.
+        let data = unsafe { NonNull::new_unchecked(fence_data_ptr) };
+
+        Self { data }
+    }
+}
+
+// SAFETY: The Rust dma_fence abstractions are already designed around the inner
+// C `dma_fence`, which can serve safely as the identification point when being
+// owned by C. Moreover, safety is ensured by not dropping `DriverFence` and by
+// only allowing operations without side effects on the Borrowed type.
+unsafe impl<F: Send + Sync + 'static, C: Send + Sync + 'static> ForeignOwnable
+    for DriverFence<F, C>
+{
+    // `DriverFence` is merely a wrapper around a raw pointer. Thus, we can just
+    // use it directly.
+    type Borrowed<'a> = DriverFenceBorrow<F, C>;
+    type BorrowedMut<'a> = DriverFenceBorrow<F, C>;

rust/kernel/dma_buf/dma_fence.rs line 760

Quoted 10 lines
rust/kernel/dma_buf/dma_fence.rs new file +10 -0
+
+    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.
+        core::mem::forget(fence);

Nit: Modern Rust uses ManuallyDrop instead of forget().

Quoted 94 lines
rust/kernel/dma_buf/dma_fence.rs new file +60 -0
+        ptr.cast()
+    }
+
+    unsafe fn from_foreign(ptr: *mut c_void) -> Self {
+        // SAFETY: Safe because the trait implementation only invokes this with
+        // a valid `ptr`, associated to a `DriverFence` with matching generic data.
+        unsafe { Self::from_raw(ptr.cast()) }
+    }
+
+    unsafe fn borrow<'a>(ptr: *mut c_void) -> Self::Borrowed<'a> {
+        // SAFETY: The trait implementation ensures that `ptr` always resides
+        // within a [`Fence`] within a [`DriverFenceData`].
+        unsafe { DriverFenceBorrow::from_raw(ptr.cast()) }
+    }
+
+    unsafe fn borrow_mut<'a>(ptr: *mut c_void) -> Self::BorrowedMut<'a> {
+        // SAFETY: The trait implementation ensures that `ptr` always resides
+        // within a [`Fence`] within a [`DriverFenceData`].
+        unsafe { DriverFenceBorrow::from_raw(ptr.cast()) }
+    }
+}
+
+impl<F: Send + Sync, C: Send + Sync> Drop for DriverFence<F, C> {
+    fn drop(&mut self) {
+        let fence = self.as_raw();
+        let mut fence_flags: usize = 0;
+        let flag_ptr = &raw mut fence_flags;
+
+        // SAFETY: Once a `DriverFence` is initialized, the inner `fence` is
+        // valid and initialized. It is valid until the refcount drops
+        // to 0, which can earliest happen once the `DriverFence` has been dropped.
+        unsafe {
+            bindings::dma_fence_lock_irqsave(fence, flag_ptr);
+            #[allow(unused_unsafe)]
+            if warn_on!(!bindings::dma_fence_is_signaled_locked(fence)) {
+                bindings::dma_fence_set_error(fence, ECANCELED as i32);
+                bindings::dma_fence_signal_locked(fence);
+            }
+            bindings::dma_fence_unlock_irqrestore(fence, flag_ptr);
+        }
+
+        // SAFETY: `self.data` is owned by the DriverFence, but could be accessed
+        // through some dma_fence callbacks right now. Access is being revoked
+        // above by signalling the fence. The DriverFenceAllowedData trait
+        // ensures that the data either does not need drop, or if it does it
+        // lives in a RcuBox which will delay dropping by one grace period, hence
+        // ensuring that all readers have disappeared.
+        unsafe { drop_in_place(self.data.as_ptr()) };
+
+        // SAFETY: Once a `DriverFence` is initialized, the inner `fence` is
+        // valid and initialized. It is valid until the refcount drops
+        // to 0, which can earliest happen once the `DriverFence` has been dropped.
+        unsafe {
+            bindings::dma_fence_put(fence);
+        }
+
+        // The actual memory the data associated with a `DriverFence` lives in
+        // gets freed by the C dma_fence backend once the fence's refcount reaches 0.
+    }
+}
rust/kernel/dma_buf/mod.rs new file +13 -0
new file mode 100644
index 000000000000..d9da3dc57fce
@@ -0,0 +1,13 @@
+// SPDX-License-Identifier: GPL-2.0 OR MIT
+
+//! DMA-buf subsystem abstractions.
+
+pub mod dma_fence;
+
+pub use self::dma_fence::{
+    DriverFence,
+    Fence,
+    FenceCb,
+    FenceCbRegistration,
+    FenceCtx, //
+};
rust/kernel/lib.rs +1 -0
index b72b2fbe046d..a05ccaa7598c 100644
@@ -63,6 +63,7 @@
 pub mod device_id;
 pub mod devres;
 pub mod dma;
+pub mod dma_buf;
 pub mod driver;
 #[cfg(CONFIG_DRM = "y")]
 pub mod drm;
Signature · 1 line
2.54.0
#50 lore raw

Re: [PATCH 3/4] rust: Add dma_fence abstractions

Quoted 14 lines · On Mon, 1 Jun 2026 10:36:06 +0000 Alice Ryhl <[email protected]> wrote:
Quoted 14 lines
rust/kernel/dma_buf/dma_fence.rs new file +14 -0
+};
+
+use bindings::ECANCELED;
+
+use kernel::str::CString;
+use kernel::sync::{
+    aref::{
+        ARef,
+        AlwaysRefCounted, //
+    },
+    Arc,
+    ArcBorrow, //
+};
+
Quoted 8 lines

rust/kernel/dma_buf/dma_fence.rs line 57

Quoted 6 lines
rust/kernel/dma_buf/dma_fence.rs new file +6 -0
+/// VTable for dma_fence backend_ops callbacks.
+//
+// Mandatory dma_fence backend_ops are implemented implicitly through
+// [`FenceCtx`]. Additional ones shall get implemented on this trait, which then
+// shall be demanded for the fence context data.
+pub trait FenceCtxOps {}  

This empty trait is unused.

I had initially suggested to add the F type (AKA FenceData) passed around in multiple places type as an associated type

pub trait FenceCtxOps {
type FenceData: Send + Sync;
}

so we don't have to pass both F and C. The reasoning here is that:

1. We expect we'll have to define more methods to the FenceCtxOps trait at some point, so adding it now kinda makes sense.

2. In the current design, we've assumed that a Fence can't live/be created outside of a given context, so there's no world where the FenceData wouldn't be known by the FenceCtx implementation, and forcing users to pass F and C around seems needlessly verbose.

#51 lore raw

Re: [PATCH 3/4] rust: Add dma_fence abstractions

Quoted 35 lines · Boris Brezillon
Quoted 14 lines · On Mon, 1 Jun 2026 10:36:06 +0000 Alice Ryhl <[email protected]> wrote:
Quoted 14 lines
rust/kernel/dma_buf/dma_fence.rs new file +14 -0
+};
+
+use bindings::ECANCELED;
+
+use kernel::str::CString;
+use kernel::sync::{
+    aref::{
+        ARef,
+        AlwaysRefCounted, //
+    },
+    Arc,
+    ArcBorrow, //
+};
+
Quoted 8 lines

rust/kernel/dma_buf/dma_fence.rs line 57

Quoted 6 lines
rust/kernel/dma_buf/dma_fence.rs new file +6 -0
+/// VTable for dma_fence backend_ops callbacks.
+//
+// Mandatory dma_fence backend_ops are implemented implicitly through
+// [`FenceCtx`]. Additional ones shall get implemented on this trait, which then
+// shall be demanded for the fence context data.
+pub trait FenceCtxOps {}  

This empty trait is unused.

I had initially suggested to add the F type (AKA FenceData) passed around in multiple places type as an associated type

pub trait FenceCtxOps {
   type FenceData: Send + Sync;
}

so we don't have to pass both F and C. The reasoning here is that:

Quoted 7 lines

1. We expect we'll have to define more methods to the FenceCtxOps trait at some point, so adding it now kinda makes sense.

2. In the current design, we've assumed that a Fence can't live/be created outside of a given context, so there's no world where the FenceData wouldn't be known by the FenceCtx implementation, and forcing users to pass F and C around seems needlessly verbose.

I had investigated that, but found that this causes us to write things like

DriverFence<T> (where T is the FencCtx generic)

and then in the actual implementation use

T::FenceData

which reads very weird IMO. Because now for reasons a fence's own data are not referred to in its own implementation, but you derive it from the context.

I do prefer it in a way where the DriverFence generic does appear in said fence's actual code, on equal rank with the FenceCtx.

I suppose that is actually one use case for which PhantomData does exist.

P.

#53 lore raw

Re: [PATCH 3/4] rust: Add dma_fence abstractions

In reply to Alice Ryhl
Quoted 2 lines · Alice Ryhl
Quoted 1 line · Philipp Stanner

[…]

Quoted 8 lines
Quoted 8 lines
rust/kernel/dma_buf/dma_fence.rs new file +8 -0
+use pin_init::pin_init_from_closure;
+
+use core::{
+    marker::PhantomData, //
+    ops::Deref,
+    ptr,
+    ptr::{
+        drop_in_place,
Quoted 8 lines
Quoted 6 lines

+        NonNull, //
+    },
+    sync::atomic::{
+        AtomicU64,
+        Ordering, //
+    },

Use atomics from the kernel crate instead.

OK.

Quoted 15 lines
Quoted 15 lines
rust/kernel/dma_buf/dma_fence.rs new file +14 -0
+};
+
+use bindings::ECANCELED;
+
+use kernel::str::CString;
+use kernel::sync::{
+    aref::{
+        ARef,
+        AlwaysRefCounted, //
+    },
+    Arc,
+    ArcBorrow, //
+};
+
Quoted 8 lines

rust/kernel/dma_buf/dma_fence.rs line 57

Quoted 6 lines
rust/kernel/dma_buf/dma_fence.rs new file +6 -0
+/// VTable for dma_fence backend_ops callbacks.
+//
+// Mandatory dma_fence backend_ops are implemented implicitly through
+// [`FenceCtx`]. Additional ones shall get implemented on this trait, which then
+// shall be demanded for the fence context data.
+pub trait FenceCtxOps {}

This empty trait is unused.

(discussed in the other thread with Boris)

Quoted 8 lines

rust/kernel/dma_buf/dma_fence.rs line 62

Quoted 5 lines
rust/kernel/dma_buf/dma_fence.rs new file +4 -0
+/// A dma-fence context. A fence context takes care of associating related fences with each other,
+/// providing each with raising sequence numbers and a common identifier.
+#[pin_data(PinnedDrop)]
+pub struct FenceCtx<F: Send + Sync, C: Send + Sync> {

No need to list any trait bounds here. You can list them on impl blocks only.

ACK.

Quoted 2 lines
Quoted 2 lines

[…]

Quoted 31 lines
Quoted 22 lines

+    {
+        // Uses pin_init_from_closure instead of try_pin_init! so that on +        // -ENOENT (already signaled) the callback can be read back from the +        // partially-initialized slot and returned through the error. +        //
+        // SAFETY: pin_init_from_closure requires:
+        // - On Ok(()): the slot is fully initialized and valid for Drop. +        // - On Err(_): the slot is clean, i.e.: no partially-initialized fields +        //   remain, and the slot can be deallocated without dropping. +        //
+        // We uphold this as follows:
+        // - On success: all three fields are initialized. Ok(()) is returned. +        // - On ENOENT (already signaled): callback and fence are read back +        //   from the slot via ptr::read, leaving the slot clean. cb was +        //   initialized by dma_fence_add_callback (it calls
+        //   INIT_LIST_HEAD(&cb->node) even on error), but cb is +        //   Opaque<dma_fence_cb> which has no Drop, so not dropping it is +        //   fine. The callback is returned through AlreadySignaled(T). +        // - On other errors: same cleanup as ENOENT, error returned as +        //   Other(e).
+        unsafe {
+            pin_init_from_closure(move |slot: *mut Self| {

Quoted 8 lines

+                let slot_callback = &raw mut (*slot).callback; +                let slot_fence = &raw mut (*slot).fence;
+                let slot_cb = &raw mut (*slot).cb;
+
+                // Write callback and fence first — must be visible before +                // dma_fence_add_callback makes the registration live. +                core::ptr::write(slot_callback, callback);
+                core::ptr::write(slot_fence, ARef::from(fence));

Quoted 3 lines

Here you are incrementing the fence refcount. It's better to change the function argument to ARef<Fence> so that the user can avoid this increment if they happen to own a refcount they're willing to give up.

Agreed, will do

Quoted 77 lines
Quoted 13 lines

+                let ret = to_result(bindings::dma_fence_add_callback( +                    fence.inner.get(),
+                    Opaque::cast_into(slot_cb),
+                    Some(Self::dma_fence_callback),
+                ));
+
+                match ret {
+                    Ok(()) => Ok(()),
+                    Err(e) => {
+                        // Read back what we wrote to leave the slot clean. +                        let cb_back = core::ptr::read(slot_callback); +                        let _fence_back = core::ptr::read(slot_fence);

This can be drop_in_place().

Quoted 25 lines

+                        if e.to_errno() == ENOENT.to_errno() { +                            Err(CallbackError::AlreadySignaled(cb_back)) +                        } else {
+                            Err(CallbackError::Other(e))
+                        }
+                    }
+                }
+            })
+        }
+    }

rust/kernel/dma_buf/dma_fence.rs new file +15 -0
+
+    /// Raw dma fence callback that is called by the C code.
+    ///
+    /// # Safety
+    ///
+    /// This is only called by the dma_fence subsystem with valid pointers.
+    unsafe extern "C" fn dma_fence_callback(
+        _fence: *mut bindings::dma_fence,
+        cb: *mut bindings::dma_fence_cb,
+    ) {
+        let ptr = Opaque::cast_from(cb).cast_mut();
+
+        // SAFETY: All `cb` we can receive here have been created in such a way
+        // that they are embedded into a `FenceCbRegistration`. The backend
+        // ensures synchronisation so whoever holds the registration object

rust/kernel/dma_buf/dma_fence.rs line 384

Quoted 10 lines
rust/kernel/dma_buf/dma_fence.rs new file +10 -0
+        // cannot drop it while this code is running. See `FenceCbRegistration::drop`.
+        unsafe {
+            let reg: *mut Self = container_of!(ptr, Self, cb);
+
+            (*reg).callback.called();
+        }
+    }
+
+    /// Returns a reference to the fence this callback is registered on.
+    pub fn fence(self: Pin<&Self>) -> &Fence {

Can be simplified to fn fence(&self) -> &Fence.

Quoted 23 lines

+        &self.get_ref().fence
+    }

rust/kernel/dma_buf/dma_fence.rs new file +21 -0
+}
+
+#[pinned_drop]
+impl<T: FenceCb> PinnedDrop for FenceCbRegistration<T> {
+    fn drop(self: Pin<&mut Self>) {
+        // Always call dma_fence_remove_callback, even if `callback` has already
+        // been taken by `dma_fence_callback`.  This is necessary for
+        // synchronization: `dma_fence_remove_callback` acquires `fence->lock`,
+        // which ensures that any in-flight `dma_fence_signal` (which calls our
+        // callback while holding the same lock) has completed before we free
+        // the struct.
+        //
+        // Without this, Drop can race with a concurrent signal:
+        //   CPU0 (signal, lock held): take() -> signaled(fence_ref) (in progress)
+        //   CPU1 (drop): sees is_some()==false -> skips lock -> frees struct
+        //   CPU0: accesses fence_ref -> use-after-free
+        //
+        // When the callback has already fired, the signal path detached the
+        // list node via INIT_LIST_HEAD, so dma_fence_remove_callback just sees
+        // an empty node and returns false — the lock acquisition is the only
+        // thing that matters.
Quoted 8 lines
Quoted 6 lines

+        //
+        // SAFETY: The fence pointer is valid and the cb was initialized by +        // dma_fence_add_callback during construction.
+        unsafe {
+            bindings::dma_fence_remove_callback(self.fence.as_raw(), self.cb.get()); +        }

Formatting nit: Usually the ; goes outside the unsafe block.

I could have sworn that it was rustfmt who did that? Maybe because the ; was inside to begin with.

Quoted 13 lines

rust/kernel/dma_buf/dma_fence.rs line 421

Quoted 9 lines

+    }

rust/kernel/dma_buf/dma_fence.rs new file +7 -0
+}
+
+// SAFETY: FenceCbRegistration can be sent between threads
+unsafe impl<T: FenceCb> Send for FenceCbRegistration<T> {}
+
+// SAFETY: &FenceCbRegistration can be shared between threads if &T can.
+unsafe impl<T: FenceCb> Sync for FenceCbRegistration<T> where T: Sync {}

There's no &FenceCbRegistration<T> -> &T accessor, so I don't think this bound is required.

Quoted 4 lines
	unsafe impl<T: FenceCb> Sync for FenceCbRegistration<T> {}

There also can't be such an accessor in the future because the closure takes a &mut T.

Hm, very correct. The entire design only allows serial access.

Quoted 85 lines
Quoted 50 lines
rust/kernel/dma_buf/dma_fence.rs new file +49 -0
+/// The receiving counterpart of a [`DriverFence`], designed to register callbacks
+/// on, check the signalled state etc. A [`Fence`] cannot be signalled.
+/// A [`Fence`] is always refcounted.
+pub struct Fence {
+    /// The actual dma_fence passed to C.
+    inner: Opaque<bindings::dma_fence>,
+}
+
+// SAFETY: Fences are literally designed to be shared between threads.
+unsafe impl Send for Fence {}
+// SAFETY: Fences are literally designed to be shared between threads.
+unsafe impl Sync for Fence {}
+
+impl Fence {
+    /// Check whether the fence was signalled at the moment of the function call.
+    pub fn is_signaled(&self) -> bool {
+        // SAFETY: self is by definition still valid. The backend ensures proper
+        // locking.
+        unsafe { bindings::dma_fence_is_signaled(self.as_raw()) }
+    }
+
+    fn as_raw(&self) -> *mut bindings::dma_fence {
+        self.inner.get()
+    }
+
+    /// Create a [`Fence`] from a raw C [`bindings::dma_fence`].
+    ///
+    /// # Safety
+    ///
+    /// `ptr` must point to an initialized fence that is embedded into a [`Fence`].
+    pub unsafe fn from_raw<'a>(ptr: *mut bindings::dma_fence) -> &'a Self {
+        // SAFETY: Safe as per the function's overall safety requirements.
+        unsafe { &*ptr.cast() }
+    }
+}
+
+// SAFETY: These implement the C backends refcounting methods which are proven to work correctly.
+unsafe impl AlwaysRefCounted for Fence {
+    fn inc_ref(&self) {
+        // SAFETY: `self.as_raw()` is a pointer to a valid `struct dma_fence`.
+        unsafe { bindings::dma_fence_get(self.as_raw()) }
+    }
+
+    /// # Safety
+    ///
+    /// `ptr`must be a valid pointer to a [`DriverFence`].
+    unsafe fn dec_ref(ptr: NonNull<Self>) {
+        // SAFETY: `ptr` is never a NULL pointer; and when `dec_ref()` is called
+        // the fence is by definition still valid.

rust/kernel/dma_buf/dma_fence.rs line 481

Quoted 10 lines
rust/kernel/dma_buf/dma_fence.rs new file +10 -0
+        let fence = unsafe { (*ptr.as_ptr()).inner.get() };
+
+        // SAFETY: Valid because `fence` was created validly above.
+        unsafe { bindings::dma_fence_put(fence) }
+    }
+}
+
+#[repr(C)] // Necessary to guarantee that `inner` always comes first so that we can cast.
+#[pin_data]
+struct DriverFenceData<F: Send + Sync, C: Send + Sync> {

Ditto here about trait bounds. (And everywhere else.)

Quoted 22 lines

+    #[pin]
+    /// The inner fence.
+    inner: Fence,
+    /// Pointer to access the FenceCtx. Useful for obtaining name parameters. +    // The FenceCtx lives as long as at least all its fences, hence this is safe. +    fctx: Arc<FenceCtx<F, C>>,
+    /// The API user's data. As required by [DriverFenceAllowedData], this either +    /// does not need drop, or must live in a [rcu::RcuBox]. It is essential +    /// that the data only performs operations legal in atomic context in its +    /// [Drop] implementation.
+    #[pin]
+    data: F,

rust/kernel/dma_buf/dma_fence.rs new file +10 -0
+}
+
+/// A trait to enforce that all data in a [`DriverFence`] either does not need
+/// drop, or lives in a [`RcuBox`].
+pub trait DriverFenceAllowedData: private::Sealed {}
+
+mod private {
+    pub trait Sealed {}
+}
+
Quoted 8 lines

rust/kernel/dma_buf/dma_fence.rs line 508

Quoted 5 lines
rust/kernel/dma_buf/dma_fence.rs new file +5 -0
+impl<F: Copy> DriverFenceAllowedData for F {}
+impl<F: Send> DriverFenceAllowedData for RcuBox<F> {}
+
+impl<F: Copy> private::Sealed for F {}
+impl<F: Send> private::Sealed for RcuBox<F> {}

Why sealed? Just make the trait unsafe and require the things you require from the user.

This is far better. We definitely only allow the user to pass A or B, and only then it compiles.

The unsafe implementation could be messed up.

I thought that's what Sealed is for. Or isn't it?

Quoted 13 lines
Quoted 13 lines
rust/kernel/dma_buf/dma_fence.rs new file +12 -0
+/// A synchronization primitive mainly for GPU drivers.
+///
+/// Fences are always reference counted. The typical use case is that one side registers
+/// callbacks on the fence which will perform a certain action (such as queueing work) once the
+/// other side signals the fence.
+///
+/// # Examples
+///
+/// ```
+/// use kernel::dma_buf::{DriverFence, FenceCtx, FenceCb, FenceCbRegistration};
+/// use kernel::str::CString;
+/// use kernel::sync::{
Quoted 8 lines

rust/kernel/dma_buf/dma_fence.rs line 526

Quoted 5 lines
rust/kernel/dma_buf/dma_fence.rs new file +5 -0
+///     aref::ARef,
+///     rcu::RcuBox, //
+/// };
+/// use core::ops::Deref;
+/// use core::fmt::Display;

Use fmt traits from kernel instead. (Actually, I don't think you use Display at all here?)

I tried, see a few lines below:

Quoted 21 lines
Quoted 21 lines
rust/kernel/dma_buf/dma_fence.rs new file +20 -0
+/// struct CallbackData { }
+///
+/// impl FenceCb for CallbackData {
+///     fn called(&mut self) {
+///         pr_info!("DmaFence callback executed.\n");
+///     }
+/// }
+///
+/// let driver_name = CString::try_from_fmt(fmt!("dummy_driver"))?;
+/// let timeline_name = CString::try_from_fmt(fmt!("dummy_timeline"))?;
+///
+/// let fctx = FenceCtx::new(driver_name, timeline_name, ())?;
+///
+/// let fence_data = CString::try_from_fmt(fmt!("dummy_data"))?;
+/// // DriverFence::data must either not need drop, or live in an RcuBox.
+/// let fence_data = RcuBox::new(fence_data, GFP_KERNEL)?;
+///
+/// let fence_alloc = fctx.as_arc_borrow().new_fence_allocation(fence_data)?;
+/// let mut fence = fctx.new_fence(fence_alloc);
+///
Quoted 8 lines

rust/kernel/dma_buf/dma_fence.rs line 555

Quoted 8 lines
rust/kernel/dma_buf/dma_fence.rs new file +8 -0
+/// let cb_data = CallbackData { };
+/// let waiting_fence = ARef::from(fence.as_fence());
+/// let cb_reg = FenceCbRegistration::new(&waiting_fence, cb_data);
+/// let cb_reg = KBox::pin_init(cb_reg, GFP_KERNEL)?;
+///
+/// // DriverFence implements Deref.
+/// // FIXME: unit test claims that CString does not implement Display. Why?
+/// // pr_info!("Fence's inner data is: {}", fence.deref().deref());

Lazily, I was hoping that someone here will tell me how that is supposed to be done correctly 8-)

Quoted 105 lines
Quoted 105 lines
rust/kernel/dma_buf/dma_fence.rs new file +105 -0
+///
+/// // TODO begin_signalling
+/// fence.signal(Ok(()));
+/// assert_eq!(waiting_fence.is_signaled(), true);
+///
+/// Ok::<(), Error>(())
+/// ```
+pub struct DriverFence<F: Send + Sync, C: Send + Sync> {
+    /// The actual content of the fence. Lives in a raw pointer so that its
+    /// memory can be managed independently. Valid until both the [`DriverFence`]
+    /// and all associated [`Fence`]s have disappeared.
+    data: NonNull<DriverFenceData<F, C>>,
+}
+
+/// A pre-prepared DMA fence, carrying the user's data and the memory it and the
+/// fence reside in. Only useful for creating a [`DriverFence`]. Splitting
+/// allocation and full initialization is necessary because fences cannot be
+/// allocated dynamically in some circumstances (deadlock).
+pub struct DriverFenceAllocation<F: Send + Sync, C: Send + Sync> {
+    /// The memory for the actual content of the fence.
+    /// Handed over to a [`DriverFence`], or deallocated once the
+    /// [`DriverFenceAllocation`] drops.
+    data: KBox<DriverFenceData<F, C>>,
+}
+
+impl<F: Send + Sync + DriverFenceAllowedData, C: Send + Sync> DriverFenceAllocation<F, C> {
+    /// Create a new allocation slot that can later be used to create a fully
+    /// initialized [`DriverFence`] without the need to allocate.
+    pub fn new(fctx: Arc<FenceCtx<F, C>>, data: F) -> Result<Self> {
+        let fence_data = DriverFenceData {
+            // `inner` remains uninitialized until a [`DriverFence`] takes over.
+            inner: Fence {
+                inner: Opaque::uninit(),
+            },
+            fctx,
+            data,
+        };
+
+        // In order to support the C dma_fence callbacks, it is necessary for
+        // a `Fence` and a `DriverFence` to live in the same allocation,
+        // because the C backend passes a dma_fence, from which the driver most
+        // likely wants to be able to access its `data` in `DriverFence`.
+        //
+        // Hence, we need the manage the memory manually. It will be freed by the
+        // C backend automatically once the refcount within `Fence` drops to 0.
+        let data = KBox::new(fence_data, GFP_KERNEL | __GFP_ZERO)?;
+
+        Ok(Self { data })
+    }
+
+    fn as_raw(&self) -> *mut bindings::dma_fence {
+        self.data.inner.inner.get()
+    }
+}
+
+impl<F: Send + Sync, C: Send + Sync> DriverFence<F, C> {
+    fn as_raw(&self) -> *mut bindings::dma_fence {
+        // SAFETY: Valid because `self` is valid.
+        let fence_data = unsafe { &mut *self.data.as_ptr() };
+
+        fence_data.inner.inner.get()
+    }
+
+    /// Create a [`DriverFence`] from a raw pointer to a [`bindings::dma_fence`].
+    ///
+    /// # Safety
+    ///
+    /// `ptr` must be a valid pointer to a `dma_fence` that was obtained through
+    /// a [`DriverFence`] with matching generic data for both fence and associated
+    /// [`FenceCtx`].
+    unsafe fn from_raw(ptr: *mut bindings::dma_fence) -> Self {
+        let opaque_fence = Opaque::cast_from(ptr);
+
+        // SAFETY: Safe due to the function's overall safety requirements.
+        let fence_ptr = unsafe { container_of!(opaque_fence, Fence, inner) };
+
+        // DriverFenceData is repr(C) and a Fence is its first member.
+        let fence_data_ptr = fence_ptr as *mut DriverFenceData<F, C>;
+
+        // SAFETY: `fence_data_ptr` was created validly above.
+        let data = unsafe { NonNull::new_unchecked(fence_data_ptr) };
+
+        Self { data }
+    }
+
+    /// Return the underlying [`Fence`].
+    pub fn as_fence(&self) -> &Fence {
+        // SAFETY: `self` is by definition still valid, and it cannot drop until
+        // this new reference is gone.
+        unsafe { Fence::from_raw(self.as_raw()) }
+    }
+
+    /// Signal the fence. This will invoke all registered callbacks.
+    pub fn signal(self, res: Result) {
+        let fence = self.as_raw();
+        let mut fence_flags: usize = 0;
+        let flag_ptr = &raw mut fence_flags;
+
+        // SAFETY: Once a `DriverFence` is initialized, the inner `fence` is
+        // valid and initialized. It is valid until the refcount drops
+        // to 0, which can earliest happen once the `DriverFence` has been dropped.
+        unsafe {
+            bindings::dma_fence_lock_irqsave(fence, flag_ptr);
+            if !bindings::dma_fence_is_signaled_locked(fence) {
+                if let Err(err) = res {
Quoted 8 lines
Quoted 6 lines

+                    bindings::dma_fence_set_error(fence, err.to_errno()); +                }
+                bindings::dma_fence_signal_locked(fence);
+            }
+            bindings::dma_fence_unlock_irqrestore(fence, flag_ptr); +        }

This single unsafe blocks spans five different unsafe operations.

Same discussion with Danilo. I'd prefer it this way, but I guess separate blocks also have some advantages.

Quoted 89 lines
Quoted 89 lines

+    }

rust/kernel/dma_buf/dma_fence.rs new file +87 -0
+}
+
+// SAFETY: Fences are literally designed to be shared between threads.
+unsafe impl<F: Send + Sync, C: Send + Sync> Send for DriverFence<F, C> {}
+
+impl<F: Send + Sync, C: Send + Sync> Deref for DriverFence<F, C> {
+    type Target = F;
+
+    fn deref(&self) -> &Self::Target {
+        // SAFETY: Thanks to refcounting, `data` is always valid as long as `self` is.
+        let data = unsafe { &*self.data.as_ptr() };
+
+        &data.data
+    }
+}
+
+/// A borrowed [`DriverFence`]. All you can do with it is access your user data
+/// and obtain a [`Fence`].
+pub struct DriverFenceBorrow<F: Send + Sync, C: Send + Sync> {
+    /// The actual content of the fence. Lives in a raw pointer so that its
+    /// memory can be managed independently. Valid until both the [`DriverFence`]
+    /// and all associated [`Fence`]s have disappeared.
+    data: NonNull<DriverFenceData<F, C>>,
+}
+
+impl<F: Send + Sync, C: Send + Sync> Deref for DriverFenceBorrow<F, C> {
+    type Target = F;
+
+    fn deref(&self) -> &Self::Target {
+        // SAFETY: Thanks to refcounting, `data` is always valid as long as `self` is.
+        let data = unsafe { &*self.data.as_ptr() };
+
+        &data.data
+    }
+}
+
+impl<F: Send + Sync, C: Send + Sync> DriverFenceBorrow<F, C> {
+    fn as_raw(&self) -> *mut bindings::dma_fence {
+        // SAFETY: Valid because `self` is valid.
+        let fence_data = unsafe { &mut *self.data.as_ptr() };
+
+        fence_data.inner.inner.get()
+    }
+
+    /// Return the underlying [`Fence`].
+    pub fn as_fence(&self) -> &Fence {
+        // SAFETY: `self` is by definition still valid, and it cannot drop until
+        // this new reference is gone.
+        unsafe { Fence::from_raw(self.as_raw()) }
+    }
+
+    /// Get a [`DriverFenceBorrow`] from a raw pointer.
+    ///
+    /// # Safety
+    ///
+    /// `ptr` must point to a raw dma_fence within a [`Fence`] within a [`DriverFenceData`].
+    unsafe fn from_raw(ptr: *mut bindings::dma_fence) -> Self {
+        let opaque_fence = Opaque::cast_from(ptr);
+
+        // SAFETY: Safe due to the function's overall safety requirements.
+        let fence_ptr = unsafe { container_of!(opaque_fence, Fence, inner) };
+
+        // DriverFenceData is repr(C) and a Fence is its first member.
+        let fence_data_ptr = fence_ptr as *mut DriverFenceData<F, C>;
+
+        // SAFETY: `fence_data_ptr` was created validly above.
+        let data = unsafe { NonNull::new_unchecked(fence_data_ptr) };
+
+        Self { data }
+    }
+}
+
+// SAFETY: The Rust dma_fence abstractions are already designed around the inner
+// C `dma_fence`, which can serve safely as the identification point when being
+// owned by C. Moreover, safety is ensured by not dropping `DriverFence` and by
+// only allowing operations without side effects on the Borrowed type.
+unsafe impl<F: Send + Sync + 'static, C: Send + Sync + 'static> ForeignOwnable
+    for DriverFence<F, C>
+{
+    // `DriverFence` is merely a wrapper around a raw pointer. Thus, we can just
+    // use it directly.
+    type Borrowed<'a> = DriverFenceBorrow<F, C>;
+    type BorrowedMut<'a> = DriverFenceBorrow<F, C>;
+
+    const FOREIGN_ALIGN: usize = core::mem::align_of::<bindings::dma_fence>();
+
+    fn into_foreign(self) -> *mut c_void {
Quoted 8 lines

rust/kernel/dma_buf/dma_fence.rs line 760

Quoted 6 lines

+        let fence = self;

rust/kernel/dma_buf/dma_fence.rs new file +5 -0
+
+        let ptr = fence.as_raw();
+
+        // DriverFence must not drop.
+        core::mem::forget(fence);

Nit: Modern Rust uses ManuallyDrop instead of forget().

You mean still take self here, then stuff it into ManuallyDrop and let it go out of scope, aye?

Thx for the review,
P.

#18 lore raw

Re: [PATCH 3/4] rust: Add dma_fence abstractions

In reply to Alice Ryhl
Quoted 30 lines · Alice Ryhl
Quoted 8 lines · On Sat, May 30, 2026 at 03:06:21PM +0000, [email protected] wrote:
Quoted 4 lines

+impl<F: Send + Sync, C: Send + Sync> DriverFence<F, C> { +    fn as_raw(&self) -> *mut bindings::dma_fence { +        // SAFETY: Valid because self is valid.
+        let fence_data = unsafe { &mut *self.data.as_ptr() };

[Severity: High]
Does this create an exclusive mutable reference to actively shared memory?

Quoted 7 lines

DriverFenceData can be accessed concurrently by other threads holding Fence references (for instance, when checking if the fence is signaled). Creating a mutable reference (&mut) in Rust asserts exclusive access and violates aliasing rules, which allows the compiler to make invalid optimization assumptions.

Could this use an immutable reference &*self.data.as_ptr() instead?

Yes, please use an immutable reference here.

Quoted 11 lines
Quoted 3 lines

+
+        fence_data.inner.inner.get()
+    }

[ ... ]

Quoted 4 lines

+impl<F: Send + Sync, C: Send + Sync> DriverFenceBorrow<F, C> { +    fn as_raw(&self) -> *mut bindings::dma_fence {
+        // SAFETY: Valid because self is valid.
+        let fence_data = unsafe { &mut *self.data.as_ptr() };

Quoted 5 lines
Quoted 3 lines

[Severity: High]
Similar to DriverFence::as_raw(), does this also incorrectly create a mutable reference to shared data?

Here as well.

data is not shared. By design there is only ever one DriverFence, and the driver's data (data.data) is Sync.

But I guess an immutable one should do the trick, too.

Quoted 61 lines
Quoted 25 lines
Quoted 4 lines

+
+        fence_data.inner.inner.get()
+    }

[ ... ]

Quoted 4 lines
rust/kernel/dma_buf/dma_fence.rs new file +4 -0
+impl<F: Send + Sync, C: Send + Sync> Drop for DriverFence<F, C> {
+    fn drop(&mut self) {
+        let fence = self.as_raw();
+        let mut fence_flags: usize = 0;

rust/kernel/dma_buf/dma_fence.rs line 797

Quoted 10 lines
rust/kernel/dma_buf/dma_fence.rs new file +10 -0
+        let flag_ptr = &raw mut fence_flags;
+
+        // SAFETY: Once a `DriverFence` is initialized, the inner `fence` is
+        // valid and initialized. It is valid until the refcount drops
+        // to 0, which can earliest happen once the `DriverFence` has been dropped.
+        unsafe {
+            bindings::dma_fence_lock_irqsave(fence, flag_ptr);
+            #[allow(unused_unsafe)]
+            if warn_on!(!bindings::dma_fence_is_signaled_locked(fence)) {
+                bindings::dma_fence_set_error(fence, ECANCELED as i32);

[Severity: High]
Is it intentional to pass a positive error code here?

Quoted 6 lines

bindings::ECANCELED evaluates to a positive integer, but dma_fence_set_error() expects a negative error code (and triggers a WARN_ON for positive values). A positive value might also be interpreted as success by callers checking for values < 0.

Should this be - (bindings::ECANCELED as i32)?

Please use ECANCELED.to_errno(). Do not import these from bindings.

Quoted 27 lines
Quoted 4 lines

+                bindings::dma_fence_signal_locked(fence); +            }
+            bindings::dma_fence_unlock_irqrestore(fence, flag_ptr); +        }

Quoted 8 lines

+
+        // SAFETY: self.data is owned by the DriverFence, but could be accessed +        // through some dma_fence callbacks right now. Access is being revoked +        // above by signalling the fence. The DriverFenceAllowedData trait +        // ensures that the data either does not need drop, or if it does it +        // lives in a RcuBox which will delay dropping by one grace period, hence +        // ensuring that all readers have disappeared.
+        unsafe { drop_in_place(self.data.as_ptr()) };

[Severity: Critical]
Could this eagerly drop FenceCtx and user data before the C dma_fence refcount reaches 0?

The C dma_fence object can remain alive as long as consumers (like sync_file or the GPU scheduler) hold references to it. If the backend later invokes callbacks like ops->get_driver_name(), FenceCtx::get_driver_name() would attempt to read fctx.driver_name from the already-dropped FenceCtx:

FenceCtx::get_driver_name() {
    ...
    fctx.driver_name.as_char_ptr()
}

Quoted 5 lines
Quoted 1 line

Can this result in a use-after-free?

Hmm, I don't know about what sahisko said, but I don't think this is the right way to do rcu freeing. I think the type's destructor should be reserved for cases where the value becomes immediately unusable.

We could guard the strings with RcuBox, but we could not then guard the FenceCtx code against code UAF if we don't have the rcu_barrier().

Or could we?

If a rust module unloads, module::remove() should contain an rcu_barrier() (right??). Would that be enough to guard against the FenceCtx code being unloaded?

Quoted 5 lines

For example, let's say I'm using RcuBox<_> here. Yes, the data you get from dereferencing the RcuBox will stay alive for a grace period, but IMO once you run the destructor of the box itself, the *pointer* becomes immediately unusable.

I don't know why you're stressing the pointer?

The trick above is simply that drop / dealloc *and* code unloading is delayed by a grace period.

P.

Quoted 2 lines

Alice

#52 lore raw

Re: [PATCH 3/4] rust: Add dma_fence abstractions

In reply to Philipp Stanner
Quoted 49 lines · On Mon, 01 Jun 2026 13:17:27 +0200 Philipp Stanner <[email protected]> wrote:
Quoted 35 lines · Boris Brezillon
Quoted 14 lines · On Mon, 1 Jun 2026 10:36:06 +0000 Alice Ryhl <[email protected]> wrote:
Quoted 14 lines
rust/kernel/dma_buf/dma_fence.rs new file +14 -0
+};
+
+use bindings::ECANCELED;
+
+use kernel::str::CString;
+use kernel::sync::{
+    aref::{
+        ARef,
+        AlwaysRefCounted, //
+    },
+    Arc,
+    ArcBorrow, //
+};
+
Quoted 8 lines

rust/kernel/dma_buf/dma_fence.rs line 57

Quoted 6 lines
rust/kernel/dma_buf/dma_fence.rs new file +6 -0
+/// VTable for dma_fence backend_ops callbacks.
+//
+// Mandatory dma_fence backend_ops are implemented implicitly through
+// [`FenceCtx`]. Additional ones shall get implemented on this trait, which then
+// shall be demanded for the fence context data.
+pub trait FenceCtxOps {}    

This empty trait is unused.

I had initially suggested to add the F type (AKA FenceData) passed around in multiple places type as an associated type

pub trait FenceCtxOps {
   type FenceData: Send + Sync;
}

so we don't have to pass both F and C. The reasoning here is that:

Quoted 7 lines

1. We expect we'll have to define more methods to the FenceCtxOps trait at some point, so adding it now kinda makes sense.

2. In the current design, we've assumed that a Fence can't live/be created outside of a given context, so there's no world where the FenceData wouldn't be known by the FenceCtx implementation, and forcing users to pass F and C around seems needlessly verbose.

I had investigated that, but found that this causes us to write things like

DriverFence<T> (where T is the FencCtx generic)

Quoted 7 lines

and then in the actual implementation use

T::FenceData

which reads very weird IMO. Because now for reasons a fence's own data are not referred to in its own implementation, but you derive it from the context.

Well, I actually think that's a good thing, because DriverFence and FenceCtx are tightly related: FenceCtx<F, C> instantiates and manages DriverFence<F, C> fences, and DriverFenceData<F, C> has an Arc to a FenceCtx<F, C>.

Quoted 3 lines

I do prefer it in a way where the DriverFence generic does appear in said fence's actual code, on equal rank with the FenceCtx.

Question is, can you really have random <F, C> combination or is C dictating which F you'll get attached to the DriverFence? If a given context can't handle more than one type of fence, I don't really see the point of passing both around when one could be directly derived from the other, and since the trait we consider defining for the future is on the FenceCtx (FenceCtxOps), it makes sense to have FenceData defined as an associated type of FenceCtx.

Quoted 3 lines

I suppose that is actually one use case for which PhantomData does exist.

Yeah, I don't, it just feels weird to pass both around, and it doesn't seem to match what we've been doing in other places (drm::Driver, drm::Object, ...).

#59 lore raw

Re: [PATCH 3/4] rust: Add dma_fence abstractions

In reply to Alice Ryhl
Quoted 8 lines · On Mon, 1 Jun 2026 10:36:06 +0000 Alice Ryhl <[email protected]> wrote:
Quoted 1 line

+pub trait FenceCtxOps {}

This empty trait is unused.

rust/kernel/dma_buf/dma_fence.rs line 62

Quoted 4 lines
rust/kernel/dma_buf/dma_fence.rs new file +4 -0
+/// A dma-fence context. A fence context takes care of associating related fences with each other,
+/// providing each with raising sequence numbers and a common identifier.
+#[pin_data(PinnedDrop)]
+pub struct FenceCtx<F: Send + Sync, C: Send + Sync> {  

BTW, if you define a FenceCtxOps trait, this should be:

pub struct FenceCtx<F: Send + Sync, C: FenceCtxOps + Send + Sync> {

maintainer
#54 lore raw

Re: [PATCH 3/4] rust: Add dma_fence abstractions

In reply to Philipp Stanner
Quoted 10 lines · Philipp Stanner
Quoted 6 lines · Alice Ryhl
Quoted 3 lines · Philipp Stanner

+        unsafe {
+            bindings::dma_fence_remove_callback(self.fence.as_raw(), self.cb.get()); +        }

Formatting nit: Usually the ; goes outside the unsafe block.

I could have sworn that it was rustfmt who did that? Maybe because the ; was inside to begin with.

Indeed, rustfmt will not change whether the ; is inside or outside the unsafe block.

Quoted 17 lines
Quoted 8 lines
Quoted 8 lines
rust/kernel/dma_buf/dma_fence.rs new file +8 -0
+/// A trait to enforce that all data in a [`DriverFence`] either does not need
+/// drop, or lives in a [`RcuBox`].
+pub trait DriverFenceAllowedData: private::Sealed {}
+
+mod private {
+    pub trait Sealed {}
+}
+
Quoted 8 lines

rust/kernel/dma_buf/dma_fence.rs line 508

Quoted 5 lines
rust/kernel/dma_buf/dma_fence.rs new file +5 -0
+impl<F: Copy> DriverFenceAllowedData for F {}
+impl<F: Send> DriverFenceAllowedData for RcuBox<F> {}
+
+impl<F: Copy> private::Sealed for F {}
+impl<F: Send> private::Sealed for RcuBox<F> {}

Why sealed? Just make the trait unsafe and require the things you require from the user.

Quoted 2 lines

This is far better. We definitely only allow the user to pass A or B, and only then it compiles.

What if I have another type that I want to use here? For example, maybe I have a struct containing a copy field and an RcuBox. Or maybe I have an ARef<_> of some C type that uses rcu for cleanup. Then I must edit this file to add support for it?

Quoted 3 lines

The unsafe implementation could be messed up.

I thought that's what Sealed is for. Or isn't it?

Sealed is for making 100% sure that downstream crates/drivers cannot provide their own implementations. But I don't see why you need that. All you require is that the value remains valid for one grace period after cleanup begins. As long as the type satisfies that, you are happy. An unsafe trait can require that sort of requirement from the user.

I think what you want is expressed well by RcuFreeSafe from this thread:
https://rust-for-linux.zulipchat.com/#narrow/channel/291566-Library/topic/Consolidate.20.60PollCondVarBox.60.20into.20.60Rcu.2ABox.60/near/598726724

Quoted 47 lines
Quoted 12 lines
Quoted 12 lines
rust/kernel/dma_buf/dma_fence.rs new file +12 -0
+/// A synchronization primitive mainly for GPU drivers.
+///
+/// Fences are always reference counted. The typical use case is that one side registers
+/// callbacks on the fence which will perform a certain action (such as queueing work) once the
+/// other side signals the fence.
+///
+/// # Examples
+///
+/// ```
+/// use kernel::dma_buf::{DriverFence, FenceCtx, FenceCb, FenceCbRegistration};
+/// use kernel::str::CString;
+/// use kernel::sync::{
Quoted 8 lines

rust/kernel/dma_buf/dma_fence.rs line 526

Quoted 5 lines
rust/kernel/dma_buf/dma_fence.rs new file +5 -0
+///     aref::ARef,
+///     rcu::RcuBox, //
+/// };
+/// use core::ops::Deref;
+/// use core::fmt::Display;

Use fmt traits from kernel instead. (Actually, I don't think you use Display at all here?)

I tried, see a few lines below:

Quoted 24 lines
Quoted 24 lines
rust/kernel/dma_buf/dma_fence.rs new file +23 -0
+/// struct CallbackData { }
+///
+/// impl FenceCb for CallbackData {
+///     fn called(&mut self) {
+///         pr_info!("DmaFence callback executed.\n");
+///     }
+/// }
+///
+/// let driver_name = CString::try_from_fmt(fmt!("dummy_driver"))?;
+/// let timeline_name = CString::try_from_fmt(fmt!("dummy_timeline"))?;
+///
+/// let fctx = FenceCtx::new(driver_name, timeline_name, ())?;
+///
+/// let fence_data = CString::try_from_fmt(fmt!("dummy_data"))?;
+/// // DriverFence::data must either not need drop, or live in an RcuBox.
+/// let fence_data = RcuBox::new(fence_data, GFP_KERNEL)?;
+///
+/// let fence_alloc = fctx.as_arc_borrow().new_fence_allocation(fence_data)?;
+/// let mut fence = fctx.new_fence(fence_alloc);
+///
+/// let cb_data = CallbackData { };
+/// let waiting_fence = ARef::from(fence.as_fence());
+/// let cb_reg = FenceCbRegistration::new(&waiting_fence, cb_data);
Quoted 8 lines
Quoted 5 lines

rust/kernel/dma_buf/dma_fence.rs line 555

Quoted 5 lines
rust/kernel/dma_buf/dma_fence.rs new file +5 -0
+/// let cb_reg = KBox::pin_init(cb_reg, GFP_KERNEL)?;
+///
+/// // DriverFence implements Deref.
+/// // FIXME: unit test claims that CString does not implement Display. Why?
+/// // pr_info!("Fence's inner data is: {}", fence.deref().deref());

Lazily, I was hoping that someone here will tell me how that is supposed to be done correctly 8-)

This specific code could be written cleaner as &**fence, but it looks like CString should implement fmt::Display like CStr does.

Quoted 24 lines
Quoted 15 lines
Quoted 15 lines
rust/kernel/dma_buf/dma_fence.rs new file +15 -0
+// SAFETY: The Rust dma_fence abstractions are already designed around the inner
+// C `dma_fence`, which can serve safely as the identification point when being
+// owned by C. Moreover, safety is ensured by not dropping `DriverFence` and by
+// only allowing operations without side effects on the Borrowed type.
+unsafe impl<F: Send + Sync + 'static, C: Send + Sync + 'static> ForeignOwnable
+    for DriverFence<F, C>
+{
+    // `DriverFence` is merely a wrapper around a raw pointer. Thus, we can just
+    // use it directly.
+    type Borrowed<'a> = DriverFenceBorrow<F, C>;
+    type BorrowedMut<'a> = DriverFenceBorrow<F, C>;
+
+    const FOREIGN_ALIGN: usize = core::mem::align_of::<bindings::dma_fence>();
+
+    fn into_foreign(self) -> *mut c_void {
Quoted 8 lines

rust/kernel/dma_buf/dma_fence.rs line 760

Quoted 6 lines

+        let fence = self;

rust/kernel/dma_buf/dma_fence.rs new file +5 -0
+
+        let ptr = fence.as_raw();
+
+        // DriverFence must not drop.
+        core::mem::forget(fence);

Nit: Modern Rust uses ManuallyDrop instead of forget().

Quoted 2 lines

You mean still take self here, then stuff it into ManuallyDrop and let it go out of scope, aye?

I mean this:

let fence = ManuallyDrop::new(self);
let ptr = fence.as_raw();

This avoids moving fence after calling as_raw().

Alice

#55 lore raw

Re: [PATCH 3/4] rust: Add dma_fence abstractions

Quoted 50 lines · Alice Ryhl
Quoted 10 lines · Philipp Stanner
Quoted 6 lines · Alice Ryhl
Quoted 3 lines · Philipp Stanner

+        unsafe {
+            bindings::dma_fence_remove_callback(self.fence.as_raw(), self.cb.get()); +        }

Formatting nit: Usually the ; goes outside the unsafe block.

I could have sworn that it was rustfmt who did that? Maybe because the ; was inside to begin with.

Indeed, rustfmt will not change whether the ; is inside or outside the unsafe block.

Quoted 17 lines
Quoted 8 lines
Quoted 8 lines
rust/kernel/dma_buf/dma_fence.rs new file +8 -0
+/// A trait to enforce that all data in a [`DriverFence`] either does not need
+/// drop, or lives in a [`RcuBox`].
+pub trait DriverFenceAllowedData: private::Sealed {}
+
+mod private {
+    pub trait Sealed {}
+}
+
Quoted 8 lines

rust/kernel/dma_buf/dma_fence.rs line 508

Quoted 5 lines
rust/kernel/dma_buf/dma_fence.rs new file +5 -0
+impl<F: Copy> DriverFenceAllowedData for F {}
+impl<F: Send> DriverFenceAllowedData for RcuBox<F> {}
+
+impl<F: Copy> private::Sealed for F {}
+impl<F: Send> private::Sealed for RcuBox<F> {}

Why sealed? Just make the trait unsafe and require the things you require from the user.

Quoted 2 lines

This is far better. We definitely only allow the user to pass A or B, and only then it compiles.

What if I have another type that I want to use here? For example, maybe I have a struct containing a copy field and an RcuBox. Or maybe I have an ARef<_> of some C type that uses rcu for cleanup. Then I must edit this file to add support for it?

Quoted 3 lines

The unsafe implementation could be messed up.

I thought that's what Sealed is for. Or isn't it?

Sealed is for making 100% sure that downstream crates/drivers cannot provide their own implementations. But I don't see why you need that. All you require is that the value remains valid for one grace period after cleanup begins. As long as the type satisfies that, you are happy. An unsafe trait can require that sort of requirement from the user.

Quoted 4 lines

I think what you want is expressed well by RcuFreeSafe from this thread:
https://rust-for-linux.zulipchat.com/#narrow/channel/291566-Library/topic/Consolidate.20.60PollCondVarBox.60.20into.20.60Rcu.2ABox.60/near/598726724

I guess this is a question of design principles. If you demand an RcuBox, you have a guarantee that it's safe.

If you demand an unsafe trait, you open the possibility for people messing up.

Due to the unsafe-contract you'd have moved the responsibility for the soundness to the driver.

I would not want to block your suggestion, but I am not sure whether that's really the better design idea.

Quoted 1 line
Quoted 1 line
Quoted 1 line

P.

maintainer
#19 lore raw

Re: [PATCH 3/4] rust: Add dma_fence abstractions

In reply to Philipp Stanner
Quoted 36 lines · Philipp Stanner
Quoted 34 lines · Alice Ryhl
Quoted 8 lines · On Sat, May 30, 2026 at 03:06:21PM +0000, [email protected] wrote:

rust/kernel/dma_buf/dma_fence.rs line 614

Quoted 4 lines
rust/kernel/dma_buf/dma_fence.rs new file +4 -0
+impl<F: Send + Sync, C: Send + Sync> DriverFence<F, C> {
+    fn as_raw(&self) -> *mut bindings::dma_fence {
+        // SAFETY: Valid because `self` is valid.
+        let fence_data = unsafe { &mut *self.data.as_ptr() };

[Severity: High]
Does this create an exclusive mutable reference to actively shared memory?

Quoted 7 lines

DriverFenceData can be accessed concurrently by other threads holding Fence references (for instance, when checking if the fence is signaled). Creating a mutable reference (&mut) in Rust asserts exclusive access and violates aliasing rules, which allows the compiler to make invalid optimization assumptions.

Could this use an immutable reference &*self.data.as_ptr() instead?

Yes, please use an immutable reference here.

Quoted 14 lines
Quoted 3 lines
rust/kernel/dma_buf/dma_fence.rs new file +3 -0
+
+        fence_data.inner.inner.get()
+    }

[ ... ]

rust/kernel/dma_buf/dma_fence.rs line 707

Quoted 4 lines
rust/kernel/dma_buf/dma_fence.rs new file +4 -0
+impl<F: Send + Sync, C: Send + Sync> DriverFenceBorrow<F, C> {
+    fn as_raw(&self) -> *mut bindings::dma_fence {
+        // SAFETY: Valid because `self` is valid.
+        let fence_data = unsafe { &mut *self.data.as_ptr() };

[Severity: High]
Similar to DriverFence::as_raw(), does this also incorrectly create a mutable reference to shared data?

Quoted 6 lines
Quoted 1 line

Here as well.

data is not shared. By design there is only ever one DriverFence, and the driver's data (data.data) is Sync.

But I guess an immutable one should do the trick, too.

There's only one DriverFence, but I can perform shared access to that one DriverFence from two threads in parallel. You made the type Sync, and this is what you are allowing when you do so.

Quoted 48 lines
Quoted 27 lines
Quoted 27 lines

rust/kernel/dma_buf/dma_fence.rs line 809

Quoted 12 lines
rust/kernel/dma_buf/dma_fence.rs new file +12 -0
+                bindings::dma_fence_signal_locked(fence);
+            }
+            bindings::dma_fence_unlock_irqrestore(fence, flag_ptr);
+        }
+
+        // SAFETY: `self.data` is owned by the DriverFence, but could be accessed
+        // through some dma_fence callbacks right now. Access is being revoked
+        // above by signalling the fence. The DriverFenceAllowedData trait
+        // ensures that the data either does not need drop, or if it does it
+        // lives in a RcuBox which will delay dropping by one grace period, hence
+        // ensuring that all readers have disappeared.
+        unsafe { drop_in_place(self.data.as_ptr()) };

[Severity: Critical]
Could this eagerly drop FenceCtx and user data before the C dma_fence refcount reaches 0?

The C dma_fence object can remain alive as long as consumers (like sync_file or the GPU scheduler) hold references to it. If the backend later invokes callbacks like ops->get_driver_name(), FenceCtx::get_driver_name() would attempt to read fctx.driver_name from the already-dropped FenceCtx:

FenceCtx::get_driver_name() {
    ...
    fctx.driver_name.as_char_ptr()
}
Quoted 5 lines
Quoted 1 line

Can this result in a use-after-free?

Hmm, I don't know about what sahisko said, but I don't think this is the right way to do rcu freeing. I think the type's destructor should be reserved for cases where the value becomes immediately unusable.

We could guard the strings with RcuBox, but we could not then guard the FenceCtx code against code UAF if we don't have the rcu_barrier().

Or could we?

If a rust module unloads, module::remove() should contain an rcu_barrier() (right??). Would that be enough to guard against the FenceCtx code being unloaded?

Quoted 5 lines

For example, let's say I'm using RcuBox<_> here. Yes, the data you get from dereferencing the RcuBox will stay alive for a grace period, but IMO once you run the destructor of the box itself, the *pointer* becomes immediately unusable.

Quoted 4 lines

I don't know why you're stressing the pointer?

The trick above is simply that drop / dealloc *and* code unloading is delayed by a grace period.

Sorry let me try to rephrase. I'm not worried about the stuff behind the pointer. After all, you're using RcuBox to protect that stuff. What I'm worried about is the pointer itself. You invoked drop_in_place() on the pointer to the fence context, so even though the fence context may be valid for another grace period, the *pointer* to the fence context is not. The pointer could have been zeroed by the destructor.

Actually maybe I am worried about the strings too. Let's say that one thread does:

rcu_read_lock()
get_driver_name()
// .. read the driver name
rcu_read_unlock()

and another thread signals the fence. If there are no queued call_rcu() callbacks right now, then the rcu_barrier() could be a no-op and the rcu reader will UAF on the driver name. It looks like you need synchronize_rcu() rather than rcu_barrier() here?

Alice

#20 lore raw

Re: [PATCH 3/4] rust: Add dma_fence abstractions

+Cc Boris

Quoted 39 lines · Alice Ryhl
Quoted 38 lines · Philipp Stanner
Quoted 35 lines · Alice Ryhl
Quoted 8 lines · On Sat, May 30, 2026 at 03:06:21PM +0000, [email protected] wrote:
Quoted 4 lines

+impl<F: Send + Sync, C: Send + Sync> DriverFence<F, C> { +    fn as_raw(&self) -> *mut bindings::dma_fence { +        // SAFETY: Valid because self is valid.
+        let fence_data = unsafe { &mut *self.data.as_ptr() };

[Severity: High]
Does this create an exclusive mutable reference to actively shared memory?

Quoted 7 lines

DriverFenceData can be accessed concurrently by other threads holding Fence references (for instance, when checking if the fence is signaled). Creating a mutable reference (&mut) in Rust asserts exclusive access and violates aliasing rules, which allows the compiler to make invalid optimization assumptions.

Could this use an immutable reference &*self.data.as_ptr() instead?

Yes, please use an immutable reference here.

Quoted 6 lines
Quoted 3 lines

+
+        fence_data.inner.inner.get()
+    }

[ ... ]

Quoted 8 lines
Quoted 4 lines

+impl<F: Send + Sync, C: Send + Sync> DriverFenceBorrow<F, C> { +    fn as_raw(&self) -> *mut bindings::dma_fence {
+        // SAFETY: Valid because self is valid.
+        let fence_data = unsafe { &mut *self.data.as_ptr() };

[Severity: High]
Similar to DriverFence::as_raw(), does this also incorrectly create a mutable reference to shared data?

Here as well.

Quoted 8 lines
Quoted 4 lines

data is not shared. By design there is only ever one DriverFence, and the driver's data (data.data) is Sync.

But I guess an immutable one should do the trick, too.

There's only one DriverFence, but I can perform shared access to that one DriverFence from two threads in parallel. You made the type Sync, and this is what you are allowing when you do so.

Nope, DriverFence is just Send, not Sync.

data.data is Sync, but data in the code above is not the actual user data, but the memory backing it up.

Quoted 51 lines
Quoted 51 lines
Quoted 28 lines
Quoted 28 lines
Quoted 13 lines

+                bindings::dma_fence_signal_locked(fence);
+            }
+            bindings::dma_fence_unlock_irqrestore(fence, flag_ptr); +        }
+
+        // SAFETY: self.data is owned by the DriverFence, but could be accessed +        // through some dma_fence callbacks right now. Access is being revoked +        // above by signalling the fence. The DriverFenceAllowedData trait +        // ensures that the data either does not need drop, or if it does it +        // lives in a RcuBox which will delay dropping by one grace period, hence +        // ensuring that all readers have disappeared.
+        unsafe { drop_in_place(self.data.as_ptr()) };

[Severity: Critical]
Could this eagerly drop FenceCtx and user data before the C dma_fence refcount reaches 0?

The C dma_fence object can remain alive as long as consumers (like sync_file or the GPU scheduler) hold references to it. If the backend later invokes callbacks like ops->get_driver_name(), FenceCtx::get_driver_name() would attempt to read fctx.driver_name from the already-dropped FenceCtx:

FenceCtx::get_driver_name() {
    ...
    fctx.driver_name.as_char_ptr()
}

Quoted 5 lines
Quoted 1 line

Can this result in a use-after-free?

Hmm, I don't know about what sahisko said, but I don't think this is the right way to do rcu freeing. I think the type's destructor should be reserved for cases where the value becomes immediately unusable.

We could guard the strings with RcuBox, but we could not then guard the FenceCtx code against code UAF if we don't have the rcu_barrier().

Or could we?

If a rust module unloads, module::remove() should contain an rcu_barrier() (right??). Would that be enough to guard against the FenceCtx code being unloaded?

Quoted 5 lines

For example, let's say I'm using RcuBox<_> here. Yes, the data you get from dereferencing the RcuBox will stay alive for a grace period, but IMO once you run the destructor of the box itself, the *pointer* becomes immediately unusable.

I don't know why you're stressing the pointer?

Quoted 8 lines
Quoted 2 lines

The trick above is simply that drop / dealloc *and* code unloading is delayed by a grace period.

Sorry let me try to rephrase. I'm not worried about the stuff behind the pointer. After all, you're using RcuBox to protect that stuff. What I'm worried about is the pointer itself. You invoked drop_in_place() on the pointer to the fence context,

on the pointer to DriverFenceData, which contains a refcount to the FenceCtx, which might then want to drop.

Quoted 4 lines

so even though
the fence context may be valid for another grace period, the *pointer* to the fence context is not. The pointer could have been zeroed by the destructor.

That particular pointer to the DriverFenceData could have been zeroed. But potential other accessors have already crafted themselves a new pointer to the, by the power of RCU, still valid data. That new pointer is container-of-ed from struct dma_fence *f.

Quoted 9 lines

Actually maybe I am worried about the strings too. Let's say that one thread does:

rcu_read_lock()
get_driver_name()
// .. read the driver name
rcu_read_unlock()

Quoted 4 lines

and another thread signals the fence. If there are no queued call_rcu() callbacks right now, then the rcu_barrier() could be a no-op and the rcu reader will UAF on the driver name. It looks like you need synchronize_rcu() rather than rcu_barrier() here?

It would seem to me that you are right.

Boris, do you have an opinion on that?

P.

Quoted 2 lines

Alice

maintainer
#56 lore raw

Re: [PATCH 3/4] rust: Add dma_fence abstractions

In reply to Philipp Stanner
Quoted 48 lines · Philipp Stanner
Quoted 38 lines · Alice Ryhl
Quoted 19 lines · Philipp Stanner
Quoted 17 lines · Alice Ryhl

rust/kernel/dma_buf/dma_fence.rs line 508

Quoted 13 lines · Philipp Stanner
rust/kernel/dma_buf/dma_fence.rs new file +13 -0
+/// A trait to enforce that all data in a [`DriverFence`] either does not need
+/// drop, or lives in a [`RcuBox`].
+pub trait DriverFenceAllowedData: private::Sealed {}
+
+mod private {
+    pub trait Sealed {}
+}
+
+impl<F: Copy> DriverFenceAllowedData for F {}
+impl<F: Send> DriverFenceAllowedData for RcuBox<F> {}
+
+impl<F: Copy> private::Sealed for F {}
+impl<F: Send> private::Sealed for RcuBox<F> {}

Why sealed? Just make the trait unsafe and require the things you require from the user.

Quoted 2 lines

This is far better. We definitely only allow the user to pass A or B, and only then it compiles.

What if I have another type that I want to use here? For example, maybe I have a struct containing a copy field and an RcuBox. Or maybe I have an ARef<_> of some C type that uses rcu for cleanup. Then I must edit this file to add support for it?

Quoted 3 lines

The unsafe implementation could be messed up.

I thought that's what Sealed is for. Or isn't it?

Sealed is for making 100% sure that downstream crates/drivers cannot provide their own implementations. But I don't see why you need that. All you require is that the value remains valid for one grace period after cleanup begins. As long as the type satisfies that, you are happy. An unsafe trait can require that sort of requirement from the user.

Quoted 4 lines

I think what you want is expressed well by RcuFreeSafe from this thread:
https://rust-for-linux.zulipchat.com/#narrow/channel/291566-Library/topic/Consolidate.20.60PollCondVarBox.60.20into.20.60Rcu.2ABox.60/near/598726724

I guess this is a question of design principles. If you demand an RcuBox, you have a guarantee that it's safe.

Quoted 8 lines

If you demand an unsafe trait, you open the possibility for people messing up.

Due to the unsafe-contract you'd have moved the responsibility for the soundness to the driver.

I would not want to block your suggestion, but I am not sure whether that's really the better design idea.

Yes, it's a design principle. You are saying that if someone needs to do X but might get it wrong, we should take away the ability to do X? I fundamentally disagree with that principle. Unsafe traits is the tool Rust created for the exact problem you have; marking places where you should be careful is the entire point of 'unsafe'.

Alice

#57 lore raw

Re: [PATCH 3/4] rust: Add dma_fence abstractions

Quoted 55 lines · Alice Ryhl
Quoted 54 lines · Philipp Stanner
Quoted 38 lines · Alice Ryhl
Quoted 19 lines · Philipp Stanner
Quoted 17 lines · Alice Ryhl

rust/kernel/dma_buf/dma_fence.rs line 508

Quoted 13 lines · Philipp Stanner
rust/kernel/dma_buf/dma_fence.rs new file +13 -0
+/// A trait to enforce that all data in a [`DriverFence`] either does not need
+/// drop, or lives in a [`RcuBox`].
+pub trait DriverFenceAllowedData: private::Sealed {}
+
+mod private {
+    pub trait Sealed {}
+}
+
+impl<F: Copy> DriverFenceAllowedData for F {}
+impl<F: Send> DriverFenceAllowedData for RcuBox<F> {}
+
+impl<F: Copy> private::Sealed for F {}
+impl<F: Send> private::Sealed for RcuBox<F> {}

Why sealed? Just make the trait unsafe and require the things you require from the user.

Quoted 2 lines

This is far better. We definitely only allow the user to pass A or B, and only then it compiles.

What if I have another type that I want to use here? For example, maybe I have a struct containing a copy field and an RcuBox. Or maybe I have an ARef<_> of some C type that uses rcu for cleanup. Then I must edit this file to add support for it?

Quoted 3 lines

The unsafe implementation could be messed up.

I thought that's what Sealed is for. Or isn't it?

Sealed is for making 100% sure that downstream crates/drivers cannot provide their own implementations. But I don't see why you need that. All you require is that the value remains valid for one grace period after cleanup begins. As long as the type satisfies that, you are happy. An unsafe trait can require that sort of requirement from the user.

Quoted 4 lines

I think what you want is expressed well by RcuFreeSafe from this thread:
https://rust-for-linux.zulipchat.com/#narrow/channel/291566-Library/topic/Consolidate.20.60PollCondVarBox.60.20into.20.60Rcu.2ABox.60/near/598726724

I guess this is a question of design principles. If you demand an RcuBox, you have a guarantee that it's safe.

If you demand an unsafe trait, you open the possibility for people messing up.

Due to the unsafe-contract you'd have moved the responsibility for the soundness to the driver.

Quoted 8 lines
Quoted 2 lines

I would not want to block your suggestion, but I am not sure whether that's really the better design idea.

Yes, it's a design principle. You are saying that if someone needs to do X but might get it wrong, we should take away the ability to do X? I fundamentally disagree with that principle. Unsafe traits is the tool Rust created for the exact problem you have; marking places where you should be careful is the entire point of 'unsafe'.

I mean, fine by me if the others don't disagree.

But when then do you ever really want a Sealed trait?

P.

Quoted 2 lines

Alice

maintainer
#58 lore raw

Re: [PATCH 3/4] rust: Add dma_fence abstractions

Quoted 66 lines · Philipp Stanner
Quoted 55 lines · Alice Ryhl
Quoted 54 lines · Philipp Stanner
Quoted 38 lines · Alice Ryhl
Quoted 19 lines · Philipp Stanner
Quoted 17 lines · Alice Ryhl

Review

Quoted 13 lines · Philipp Stanner
(quoted diff) +13 -0
+/// A trait to enforce that all data in a [`DriverFence`] either does not need
+/// drop, or lives in a [`RcuBox`].
+pub trait DriverFenceAllowedData: private::Sealed {}
+
+mod private {
+    pub trait Sealed {}
+}
+
+impl<F: Copy> DriverFenceAllowedData for F {}
+impl<F: Send> DriverFenceAllowedData for RcuBox<F> {}
+
+impl<F: Copy> private::Sealed for F {}
+impl<F: Send> private::Sealed for RcuBox<F> {}

Why sealed? Just make the trait unsafe and require the things you require from the user.

Quoted 2 lines

This is far better. We definitely only allow the user to pass A or B, and only then it compiles.

What if I have another type that I want to use here? For example, maybe I have a struct containing a copy field and an RcuBox. Or maybe I have an ARef<_> of some C type that uses rcu for cleanup. Then I must edit this file to add support for it?

Quoted 3 lines

The unsafe implementation could be messed up.

I thought that's what Sealed is for. Or isn't it?

Sealed is for making 100% sure that downstream crates/drivers cannot provide their own implementations. But I don't see why you need that. All you require is that the value remains valid for one grace period after cleanup begins. As long as the type satisfies that, you are happy. An unsafe trait can require that sort of requirement from the user.

Quoted 4 lines

I think what you want is expressed well by RcuFreeSafe from this thread:
https://rust-for-linux.zulipchat.com/#narrow/channel/291566-Library/topic/Consolidate.20.60PollCondVarBox.60.20into.20.60Rcu.2ABox.60/near/598726724

I guess this is a question of design principles. If you demand an RcuBox, you have a guarantee that it's safe.

If you demand an unsafe trait, you open the possibility for people messing up.

Due to the unsafe-contract you'd have moved the responsibility for the soundness to the driver.

Quoted 8 lines
Quoted 2 lines

I would not want to block your suggestion, but I am not sure whether that's really the better design idea.

Yes, it's a design principle. You are saying that if someone needs to do X but might get it wrong, we should take away the ability to do X? I fundamentally disagree with that principle. Unsafe traits is the tool Rust created for the exact problem you have; marking places where you should be careful is the entire point of 'unsafe'.

Quoted 3 lines

I mean, fine by me if the others don't disagree.

But when then do you ever really want a Sealed trait?

Sealed traits are a very niche feature. The main use-case is forwards-compatibility. It's not a breaking change to add a new method to a sealed trait because no downstream crates could implement the trait.

Alice

#21 lore raw

Re: [PATCH 3/4] rust: Add dma_fence abstractions

In reply to Philipp Stanner
Quoted 119 lines · Philipp Stanner

+Cc Boris

Quoted 39 lines · Alice Ryhl
Quoted 38 lines · Philipp Stanner
Quoted 35 lines · Alice Ryhl
Quoted 8 lines · On Sat, May 30, 2026 at 03:06:21PM +0000, [email protected] wrote:
Quoted 4 lines

+impl<F: Send + Sync, C: Send + Sync> DriverFence<F, C> { +    fn as_raw(&self) -> *mut bindings::dma_fence { +        // SAFETY: Valid because self is valid.
+        let fence_data = unsafe { &mut *self.data.as_ptr() };

[Severity: High]
Does this create an exclusive mutable reference to actively shared memory?

Quoted 7 lines

DriverFenceData can be accessed concurrently by other threads holding Fence references (for instance, when checking if the fence is signaled). Creating a mutable reference (&mut) in Rust asserts exclusive access and violates aliasing rules, which allows the compiler to make invalid optimization assumptions.

Could this use an immutable reference &*self.data.as_ptr() instead?

Yes, please use an immutable reference here.

Quoted 6 lines
Quoted 3 lines

+
+        fence_data.inner.inner.get()
+    }

[ ... ]

Quoted 8 lines
Quoted 4 lines

+impl<F: Send + Sync, C: Send + Sync> DriverFenceBorrow<F, C> { +    fn as_raw(&self) -> *mut bindings::dma_fence {
+        // SAFETY: Valid because self is valid.
+        let fence_data = unsafe { &mut *self.data.as_ptr() };

[Severity: High]
Similar to DriverFence::as_raw(), does this also incorrectly create a mutable reference to shared data?

Here as well.

Quoted 8 lines
Quoted 4 lines

data is not shared. By design there is only ever one DriverFence, and the driver's data (data.data) is Sync.

But I guess an immutable one should do the trick, too.

There's only one DriverFence, but I can perform shared access to that one DriverFence from two threads in parallel. You made the type Sync, and this is what you are allowing when you do so.

Nope, DriverFence is just Send, not Sync.

data.data is Sync, but data in the code above is not the actual user data, but the memory backing it up.

Quoted 51 lines
Quoted 51 lines
Quoted 28 lines
Quoted 28 lines
Quoted 13 lines

+                bindings::dma_fence_signal_locked(fence);
+            }
+            bindings::dma_fence_unlock_irqrestore(fence, flag_ptr); +        }
+
+        // SAFETY: self.data is owned by the DriverFence, but could be accessed +        // through some dma_fence callbacks right now. Access is being revoked +        // above by signalling the fence. The DriverFenceAllowedData trait +        // ensures that the data either does not need drop, or if it does it +        // lives in a RcuBox which will delay dropping by one grace period, hence +        // ensuring that all readers have disappeared.
+        unsafe { drop_in_place(self.data.as_ptr()) };

[Severity: Critical]
Could this eagerly drop FenceCtx and user data before the C dma_fence refcount reaches 0?

The C dma_fence object can remain alive as long as consumers (like sync_file or the GPU scheduler) hold references to it. If the backend later invokes callbacks like ops->get_driver_name(), FenceCtx::get_driver_name() would attempt to read fctx.driver_name from the already-dropped FenceCtx:

FenceCtx::get_driver_name() {
    ...
    fctx.driver_name.as_char_ptr()
}

Quoted 5 lines
Quoted 1 line

Can this result in a use-after-free?

Hmm, I don't know about what sahisko said, but I don't think this is the right way to do rcu freeing. I think the type's destructor should be reserved for cases where the value becomes immediately unusable.

We could guard the strings with RcuBox, but we could not then guard the FenceCtx code against code UAF if we don't have the rcu_barrier().

Or could we?

If a rust module unloads, module::remove() should contain an rcu_barrier() (right??). Would that be enough to guard against the FenceCtx code being unloaded?

Quoted 5 lines

For example, let's say I'm using RcuBox<_> here. Yes, the data you get from dereferencing the RcuBox will stay alive for a grace period, but IMO once you run the destructor of the box itself, the *pointer* becomes immediately unusable.

I don't know why you're stressing the pointer?

Quoted 8 lines
Quoted 2 lines

The trick above is simply that drop / dealloc *and* code unloading is delayed by a grace period.

Sorry let me try to rephrase. I'm not worried about the stuff behind the pointer. After all, you're using RcuBox to protect that stuff. What I'm worried about is the pointer itself. You invoked drop_in_place() on the pointer to the fence context,

on the pointer to DriverFenceData, which contains a refcount to the FenceCtx, which might then want to drop.

Quoted 9 lines
Quoted 4 lines

 so even though
the fence context may be valid for another grace period, the *pointer* to the fence context is not. The pointer could have been zeroed by the destructor.

That particular pointer to the DriverFenceData could have been zeroed. But potential other accessors have already crafted themselves a new pointer to the, by the power of RCU, still valid data.

correction:
it is refcounting that ensures the memory is still valid.

dma_fence_put() in DriverFence::drop() could earliest free the memory.

P.

Quoted 26 lines

That new pointer
is container-of-ed from struct dma_fence *f.

Quoted 9 lines

Actually maybe I am worried about the strings too. Let's say that one thread does:

rcu_read_lock()
get_driver_name()
// .. read the driver name
rcu_read_unlock()

Quoted 4 lines

and another thread signals the fence. If there are no queued call_rcu() callbacks right now, then the rcu_barrier() could be a no-op and the rcu reader will UAF on the driver name. It looks like you need synchronize_rcu() rather than rcu_barrier() here?

It would seem to me that you are right.

Boris, do you have an opinion on that?

P.

Quoted 2 lines

Alice

maintainer
#9 lore raw

Re: [PATCH 2/4] rust: rcu: add RcuBox type

In reply to Philipp Stanner
Quoted 31 lines · Philipp Stanner
Quoted 12 lines · Boqun Feng
Quoted 10 lines · Philipp Stanner

From: Alice Ryhl <[email protected]>

This adds an RcuBox container, which is like KBox except that the value is freed with kfree_rcu.

To allow containers to rely on the rcu properties of RcuBox, an extension of ForeignOwnable is added.

---

Quoted 6 lines

I have the following on top of Alice's patch. @Alice, @Danilo, thoughts?

Then we can have:

type RcuKBox<T> = RcuBox<T, Kmalloc>;
type RcuVBox<T> = RcuBox<T, Vmalloc>;

No objections by me.

I just think we have to decide how the treat the namespaces, though. Probably Alice wrote it like that so that it's very apparent that this is not a normal box. It still breaks the naming convention in my opinion.

rcu::Box vs rcu::RcuBox

With all other subsystems, naming like that seems not allowed.

Quoted 8 lines

dma::Fence vs dma::DmaFence

I probably would allow the user to decide whether he wants to just use it as rcu::Box in all his code.

But no hard feelings.

For this I think that rcu::RcuBox is a bit different than dma::Fence, because Box has its widely-accepted meaning through all Rust code, while Fence doesn't. Hence my current thought is rcu::RcuBox and dma::Fence. My personal preference is using namespace as much as we could until there might be some misleading.

Quoted 10 lines
Quoted 5 lines

and Philipp can use the RcuKBox in this patchset. We also need to impl InPlaceInit for RcuBox, but that can be added later.

So shall we merge my series with Alice's patch, and later we add your patch and other features, or would you prefer to have the additional boxes from your patch from the get-go?

I would like to have it from the get-go mainly because of RcuBox vs RcuKBox naming. Thank you!

Regards,
Boqun

Quoted 218 lines

P.

Quoted 215 lines

Regards,
Boqun

------------->8
Subject: [PATCH] rust: rcu: Make RcuBox generic over Allocator

To support RCU-protected vmalloc allocation, we need to make RcuBox generic over Allocator. Currently this works since all Allocators are either kmalloc() or vmalloc(), and kvfree_call_rcu() works with both allocations.

While we are at it, add some basic test cases.

---
 rust/kernel/sync/rcu/rcu_box.rs | 96 +++++++++++++++++++++++----------

 1 file changed, 67 insertions(+), 29 deletions(-)
rust/kernel/sync/rcu/rcu_box.rs +0 -0
index 2508fdb609ec..5c344d82c0d9 100644
@@ -4,47 +4,59 @@


 //! Provides the RcuBox type for Rust allocations that live for a grace period.

rust/kernel/sync/rcu/rcu_box.rs +5 -1
-use core::{ops::Deref, ptr::NonNull};
+use core::{
+    marker::PhantomData,
+    ops::Deref,
+    ptr::NonNull, //
+};


 use kernel::{
-    alloc::{self, AllocError},
+    alloc::{
+        self,
+        AllocError,
+        Allocator, //
+    },
     bindings,
     ffi::c_void,
     prelude::*,
-    sync::rcu::{ForeignOwnableRcu, Guard},
     types::ForeignOwnable,
 };

rust/kernel/sync/rcu/rcu_box.rs +5 -0
+use super::{
+    ForeignOwnableRcu,
+    Guard, //
+};
+

 /// A box that is freed with rcu.
 ///
 /// The value must be Send, as rcu may drop it on another thread. ///
 /// # Invariants
 ///

rust/kernel/sync/rcu/rcu_box.rs +1 -1
-/// * The pointer is valid and references a pinned `RcuBoxInner<T>` allocated with `kmalloc`.
+/// * The pointer is valid and references a pinned `RcuBoxInner<T>` allocated with `A`.

 /// * This RcuBox holds exclusive permissions to rcu free the allocation.

rust/kernel/sync/rcu/rcu_box.rs +1 -1
-pub struct RcuBox<T: Send>(NonNull<RcuBoxInner<T>>);
+pub struct RcuBox<T: Send, A: Allocator>(NonNull<RcuBoxInner<T>>, PhantomData<A>);


 struct RcuBoxInner<T> {
     value: T,
     rcu_head: bindings::callback_head,
 }

rust/kernel/sync/rcu/rcu_box.rs +2 -2
-// Note that `T: Sync` is required since when moving an `RcuBox<T>`, the previous owner may still
-// access `&T` for one grace period.
+// Note that `T: Sync` is required since when moving an `RcuBox<T, A>`, the previous owner may
+// still access `&T` for one grace period.

 //

rust/kernel/sync/rcu/rcu_box.rs +3 -3
-// SAFETY: Ownership of the `RcuBox<T>` allows for `&T` and dropping the `T`, so `T: Send + Sync`
-// implies `RcuBox<T>: Send`.
-unsafe impl<T: Send + Sync> Send for RcuBox<T> {}
+// SAFETY: Ownership of the `RcuBox<T, A>` allows for `&T` and dropping the `T`, so `T: Send +
+// Sync` implies `RcuBox<T, A>: Send`.
+unsafe impl<T: Send + Sync, A: Allocator> Send for RcuBox<T, A> {}

rust/kernel/sync/rcu/rcu_box.rs +3 -3
-// SAFETY: `&RcuBox<T>` allows for no operations other than those permitted by `&T`, so `T: Sync`
-// implies `RcuBox<T>: Sync`.
-unsafe impl<T: Send + Sync> Sync for RcuBox<T> {}
+// SAFETY: `&RcuBox<T, A>` allows for no operations other than those permitted by `&T`, so `T:
+// Sync` implies `RcuBox<T, A>: Sync`.
+unsafe impl<T: Send + Sync, A: Allocator> Sync for RcuBox<T, A> {}

rust/kernel/sync/rcu/rcu_box.rs +1 -1
-impl<T: Send> RcuBox<T> {
+impl<T: Send, A: Allocator> RcuBox<T, A> {

     /// Create a new RcuBox.
     pub fn new(x: T, flags: alloc::Flags) -> Result<Self, AllocError> { -        let b = KBox::new(
+        let b = Box::<_, A>::new(
             RcuBoxInner {
                 value: x,
                 rcu_head: Default::default(),

rust/kernel/sync/rcu/rcu_box.rs +0 -0
@@ -53,9 +65,9 @@ pub fn new(x: T, flags: alloc::Flags) -> Result<Self, AllocError> {
          )?;


         // INVARIANT:
-        // * The pointer contains a valid RcuBoxInner allocated with kmalloc. +        // * The pointer contains a valid RcuBoxInner allocated with A. // * We just allocated it, so we own free permissions.
-        Ok(RcuBox(NonNull::from(KBox::leak(b))))
+        Ok(RcuBox(NonNull::from(Box::leak(b)), PhantomData))
     }

     /// Access the value for a grace period.

rust/kernel/sync/rcu/rcu_box.rs +0 -0
@@ -66,7 +78,7 @@ pub fn with_rcu<'rcu>(&self, _read_guard: &'rcu Guard) -> &'rcu T {
      }
  }

rust/kernel/sync/rcu/rcu_box.rs +1 -1
-impl<T: Send> Deref for RcuBox<T> {
+impl<T: Send, A: Allocator> Deref for RcuBox<T, A> {

     type Target = T;
     fn deref(&self) -> &T {
         // SAFETY: While the RcuBox<T> exists, the value remains valid.

rust/kernel/sync/rcu/rcu_box.rs +0 -0
@@ -75,10 +87,10 @@ fn deref(&self) -> &T {
  }


 // SAFETY:

rust/kernel/sync/rcu/rcu_box.rs +1 -1
-// * The `RcuBoxInner<T>` was allocated with `kmalloc`.
+// * The `RcuBoxInner<T>` was allocated with `A`.

 // * NonNull::as_ptr returns a non-null pointer.

rust/kernel/sync/rcu/rcu_box.rs +2 -2
-unsafe impl<T: Send + 'static> ForeignOwnable for RcuBox<T> {
-    const FOREIGN_ALIGN: usize = <KBox<RcuBoxInner<T>> as ForeignOwnable>::FOREIGN_ALIGN;
+unsafe impl<T: Send + 'static, A: Allocator> ForeignOwnable for RcuBox<T, A> {
+    const FOREIGN_ALIGN: usize = <Box<RcuBoxInner<T>, A> as ForeignOwnable>::FOREIGN_ALIGN;


     type Borrowed<'a> = &'a T;
     type BorrowedMut<'a> = &'a T;

rust/kernel/sync/rcu/rcu_box.rs +0 -0
@@ -88,9 +100,9 @@ fn into_foreign(self) -> *mut c_void {
      }


     unsafe fn from_foreign(ptr: *mut c_void) -> Self {
-        // INVARIANT: Pointer returned by into_foreign carries same invariants as RcuBox<T>. +        // INVARIANT: Pointer returned by into_foreign, A carries same invariants as RcuBox<T>. // SAFETY: into_foreign never returns a null pointer.
-        Self(unsafe { NonNull::new_unchecked(ptr.cast()) })
+        Self(unsafe { NonNull::new_unchecked(ptr.cast()) }, PhantomData)
     }

     unsafe fn borrow<'a>(ptr: *mut c_void) -> &'a T {

rust/kernel/sync/rcu/rcu_box.rs +0 -0
@@ -104,7 +116,7 @@ unsafe fn borrow_mut<'a>(ptr: *mut c_void) -> &'a T {
      }
  }

rust/kernel/sync/rcu/rcu_box.rs +1 -1
-impl<T: Send + 'static> ForeignOwnableRcu for RcuBox<T> {
+impl<T: Send + 'static, A: Allocator> ForeignOwnableRcu for RcuBox<T, A> {

     type RcuBorrowed<'a> = &'a T;

     unsafe fn rcu_borrow<'a>(ptr: *mut c_void) -> &'a T {

rust/kernel/sync/rcu/rcu_box.rs +0 -0
@@ -114,7 +126,7 @@ unsafe fn rcu_borrow<'a>(ptr: *mut c_void) -> &'a T {
      }
  }

rust/kernel/sync/rcu/rcu_box.rs +1 -1
-impl<T: Send> Drop for RcuBox<T> {
+impl<T: Send, A: Allocator> Drop for RcuBox<T, A> {

     fn drop(&mut self) {
         // SAFETY: The rcu_head field is in-bounds of a valid allocation. let rcu_head = unsafe { &raw mut (*self.0.as_ptr()).rcu_head };

rust/kernel/sync/rcu/rcu_box.rs +5 -3
@@ -122,9 +134,11 @@ fn drop(&mut self) {
              // SAFETY: `rcu_head` is the `rcu_head` field of `RcuBoxInner<T>`. All users will be
              // gone in an rcu grace period. This is the destructor, so we may pass ownership of the
              // allocation.
-            unsafe { bindings::call_rcu(rcu_head, Some(drop_rcu_box::<T>)) };
+            unsafe { bindings::call_rcu(rcu_head, Some(drop_rcu_box::<T, A>)) };
          } else {
              // SAFETY: All users will be gone in an rcu grace period.
+            // TODO: We are luckily since `kvfree_call_rcu()` works on both kmalloc and vmalloc,
+            // maybe a new `Allocator` method is needed.
              unsafe { bindings::kvfree_call_rcu(rcu_head, self.0.as_ptr().cast()) };
          }
      }
@@ -135,11 +149,35 @@ fn drop(&mut self) {
  /// # Safety
  ///
  /// `head` references the `rcu_head` field of an `RcuBoxInner<T>` that has no references to it.
-/// Ownership of the `KBox<RcuBoxInner<T>>` must be passed.
-unsafe extern "C" fn drop_rcu_box<T>(head: *mut bindings::callback_head) {
+/// Ownership of the `Box<RcuBoxInner<T>, A>` must be passed.
+unsafe extern "C" fn drop_rcu_box<T, A: Allocator>(head: *mut bindings::callback_head) {
      // SAFETY: Caller provides a pointer to the `rcu_head` field of a `RcuBoxInner<T>`.
      let box_inner = unsafe { crate::container_of!(head, RcuBoxInner<T>, rcu_head) };


     // SAFETY: Caller ensures exclusive access and passed ownership. -    drop(unsafe { KBox::from_raw(box_inner) });
+    drop(unsafe { Box::<_, A>::from_raw(box_inner) });

rust/kernel/sync/rcu/rcu_box.rs +24 -0
+}
+
+#[kunit_tests(rust_rcu_box)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn rcu_box_basic() -> Result {
+        let rb = RcuBox::<_, alloc::allocator::Kmalloc>::new(42i32, alloc::flags::GFP_KERNEL)?;
+
+        assert_eq!(*rb, 42);
+        assert_eq!(*rb.with_rcu(&Guard::new()), 42);
+
+        drop(rb);
+
+        let rb = RcuBox::<_, alloc::allocator::Vmalloc>::new(42i32, alloc::flags::GFP_KERNEL)?;
+
+        assert_eq!(*rb, 42);
+        assert_eq!(*rb.with_rcu(&Guard::new()), 42);
+
+        drop(rb);
+
+        Ok(())
+    }

 }

maintainer
#23 lore raw

Re: [PATCH 3/4] rust: Add dma_fence abstractions

In reply to Philipp Stanner
Quoted 47 lines · Philipp Stanner

+Cc Boris

Quoted 44 lines · Alice Ryhl
Quoted 42 lines · Philipp Stanner
Quoted 30 lines · Alice Ryhl
Quoted 8 lines · On Sat, May 30, 2026 at 03:06:21PM +0000, [email protected] wrote:
Quoted 4 lines

+impl<F: Send + Sync, C: Send + Sync> DriverFence<F, C> { +    fn as_raw(&self) -> *mut bindings::dma_fence { +        // SAFETY: Valid because self is valid.
+        let fence_data = unsafe { &mut *self.data.as_ptr() };

[Severity: High]
Does this create an exclusive mutable reference to actively shared memory?

Quoted 7 lines

DriverFenceData can be accessed concurrently by other threads holding Fence references (for instance, when checking if the fence is signaled). Creating a mutable reference (&mut) in Rust asserts exclusive access and violates aliasing rules, which allows the compiler to make invalid optimization assumptions.

Could this use an immutable reference &*self.data.as_ptr() instead?

Yes, please use an immutable reference here.

Quoted 11 lines
Quoted 3 lines

+
+        fence_data.inner.inner.get()
+    }

[ ... ]

Quoted 4 lines

+impl<F: Send + Sync, C: Send + Sync> DriverFenceBorrow<F, C> { +    fn as_raw(&self) -> *mut bindings::dma_fence {
+        // SAFETY: Valid because self is valid.
+        let fence_data = unsafe { &mut *self.data.as_ptr() };

Quoted 5 lines
Quoted 3 lines

[Severity: High]
Similar to DriverFence::as_raw(), does this also incorrectly create a mutable reference to shared data?

Here as well.

data is not shared. By design there is only ever one DriverFence, and the driver's data (data.data) is Sync.

But I guess an immutable one should do the trick, too.

Quoted 8 lines
Quoted 3 lines

There's only one DriverFence, but I can perform shared access to that one DriverFence from two threads in parallel. You made the type Sync, and this is what you are allowing when you do so.

Nope, DriverFence is just Send, not Sync.

data.data is Sync, but data in the code above is not the actual user data, but the memory backing it up.

Ok, well, it probably should be Sync. I don't see any &self methods that can't be called from multiple threads in parallel.

Quoted 53 lines
Quoted 53 lines
Quoted 52 lines
Quoted 27 lines
Quoted 27 lines
Quoted 4 lines

+                bindings::dma_fence_signal_locked(fence); +            }
+            bindings::dma_fence_unlock_irqrestore(fence, flag_ptr); +        }

Quoted 8 lines

+
+        // SAFETY: self.data is owned by the DriverFence, but could be accessed +        // through some dma_fence callbacks right now. Access is being revoked +        // above by signalling the fence. The DriverFenceAllowedData trait +        // ensures that the data either does not need drop, or if it does it +        // lives in a RcuBox which will delay dropping by one grace period, hence +        // ensuring that all readers have disappeared.
+        unsafe { drop_in_place(self.data.as_ptr()) };

[Severity: Critical]
Could this eagerly drop FenceCtx and user data before the C dma_fence refcount reaches 0?

The C dma_fence object can remain alive as long as consumers (like sync_file or the GPU scheduler) hold references to it. If the backend later invokes callbacks like ops->get_driver_name(), FenceCtx::get_driver_name() would attempt to read fctx.driver_name from the already-dropped FenceCtx:

FenceCtx::get_driver_name() {
    ...
    fctx.driver_name.as_char_ptr()
}

Quoted 5 lines
Quoted 1 line

Can this result in a use-after-free?

Hmm, I don't know about what sahisko said, but I don't think this is the right way to do rcu freeing. I think the type's destructor should be reserved for cases where the value becomes immediately unusable.

We could guard the strings with RcuBox, but we could not then guard the FenceCtx code against code UAF if we don't have the rcu_barrier().

Or could we?

If a rust module unloads, module::remove() should contain an rcu_barrier() (right??). Would that be enough to guard against the FenceCtx code being unloaded?

Quoted 5 lines

For example, let's say I'm using RcuBox<_> here. Yes, the data you get from dereferencing the RcuBox will stay alive for a grace period, but IMO once you run the destructor of the box itself, the *pointer* becomes immediately unusable.

I don't know why you're stressing the pointer?

The trick above is simply that drop / dealloc *and* code unloading is delayed by a grace period.

Quoted 8 lines
Quoted 5 lines

Sorry let me try to rephrase. I'm not worried about the stuff behind the pointer. After all, you're using RcuBox to protect that stuff. What I'm worried about is the pointer itself. You invoked drop_in_place() on the pointer to the fence context,

on the pointer to DriverFenceData, which contains a refcount to the FenceCtx, which might then want to drop.

Let me clarify.

You invoked drop_in_place on DriverFenceData which is effectively this method:

fn drop_in_place::<DriverFenceData<F,C>>(self: *mut DriverFenceData) {
    drop_in_place::<Fence>(&raw mut (*self).fence);
    drop_in_place::<Arc<FenceCtx<F,C>>>(&raw mut (*self).fctx);
    drop_in_place::<F>(&raw mut (*self).data);
}

So yes you did indirectly invoke drop_in_place on Arc<FenceCtx<F,C>>.

Quoted 9 lines
Quoted 4 lines

so even though
the fence context may be valid for another grace period, the *pointer* to the fence context is not. The pointer could have been zeroed by the destructor.

That particular pointer to the DriverFenceData could have been zeroed. But potential other accessors have already crafted themselves a new pointer to the, by the power of RCU, still valid data. That new pointer is container-of-ed from struct dma_fence *f.

I'm not talking about the pointer to DriverFenceData, I'm talking about the pointer to the FenceCtx, or the pointer to the data (if F is RcuBox).

The Arc type is not a type that opts-out of &mut == exclusive, so the second drop_in_place() above is assumed exclusive access to the Arc<FenceCtx<F,C>> field. If another thread obtains a pointer to the FenceCtx via reading the fctx field of the DriverFence in parallel with this, then that's not allowed because the drop_in_place() call has exclusive access to that field.

This is why Boqun's RcuFreeSafe has a drop_in_place_before_gp() method that can be used instead of drop_in_place() for this kind of use-case. You must leave the fctx and data fields in a state where they contain data that's safe to use for at least one grace period, and drop_in_place() fundamentally does not leave the value in a usable state.

Alice

maintainer
#22 lore raw

Re: [PATCH 3/4] rust: Add dma_fence abstractions

In reply to Philipp Stanner
Quoted 36 lines · Philipp Stanner
Quoted 25 lines · Philipp Stanner

+Cc Boris

Quoted 10 lines · Alice Ryhl
Quoted 9 lines · Philipp Stanner
Quoted 4 lines · Alice Ryhl

For example, let's say I'm using RcuBox<_> here. Yes, the data you get from dereferencing the RcuBox will stay alive for a grace period, but IMO once you run the destructor of the box itself, the *pointer* becomes immediately unusable.

I don't know why you're stressing the pointer?

Quoted 8 lines
Quoted 2 lines

The trick above is simply that drop / dealloc *and* code unloading is delayed by a grace period.

Sorry let me try to rephrase. I'm not worried about the stuff behind the pointer. After all, you're using RcuBox to protect that stuff. What I'm worried about is the pointer itself. You invoked drop_in_place() on the pointer to the fence context,

on the pointer to DriverFenceData, which contains a refcount to the FenceCtx, which might then want to drop.

Quoted 9 lines
Quoted 4 lines

 so even though
the fence context may be valid for another grace period, the *pointer* to the fence context is not. The pointer could have been zeroed by the destructor.

That particular pointer to the DriverFenceData could have been zeroed. But potential other accessors have already crafted themselves a new pointer to the, by the power of RCU, still valid data.

Quoted 4 lines

correction:
it is refcounting that ensures the memory is still valid.

dma_fence_put() in DriverFence::drop() could earliest free the memory.

Hrm, actually I realized that this is not quite Boqun's RcuFreeSafe. That trait would require the fence allocation itself to remain valid for one grace period, but you don't require that.

Alice

#24 lore raw

Re: [PATCH 3/4] rust: Add dma_fence abstractions

In reply to Alice Ryhl
Quoted 52 lines · Alice Ryhl
Quoted 51 lines · Philipp Stanner

+Cc Boris

Quoted 47 lines · Alice Ryhl
Quoted 36 lines · Philipp Stanner
Quoted 34 lines · Alice Ryhl
Quoted 8 lines · On Sat, May 30, 2026 at 03:06:21PM +0000, [email protected] wrote:
Quoted 4 lines

+impl<F: Send + Sync, C: Send + Sync> DriverFence<F, C> { +    fn as_raw(&self) -> *mut bindings::dma_fence { +        // SAFETY: Valid because self is valid.
+        let fence_data = unsafe { &mut *self.data.as_ptr() };

[Severity: High]
Does this create an exclusive mutable reference to actively shared memory?

Quoted 7 lines

DriverFenceData can be accessed concurrently by other threads holding Fence references (for instance, when checking if the fence is signaled). Creating a mutable reference (&mut) in Rust asserts exclusive access and violates aliasing rules, which allows the compiler to make invalid optimization assumptions.

Could this use an immutable reference &*self.data.as_ptr() instead?

Yes, please use an immutable reference here.

Quoted 14 lines
Quoted 3 lines

+
+        fence_data.inner.inner.get()
+    }

[ ... ]

Quoted 4 lines

+impl<F: Send + Sync, C: Send + Sync> DriverFenceBorrow<F, C> { +    fn as_raw(&self) -> *mut bindings::dma_fence {
+        // SAFETY: Valid because self is valid.
+        let fence_data = unsafe { &mut *self.data.as_ptr() };

[Severity: High]
Similar to DriverFence::as_raw(), does this also incorrectly create a mutable reference to shared data?

Quoted 6 lines
Quoted 1 line

Here as well.

data is not shared. By design there is only ever one DriverFence, and the driver's data (data.data) is Sync.

But I guess an immutable one should do the trick, too.

There's only one DriverFence, but I can perform shared access to that one DriverFence from two threads in parallel. You made the type Sync, and this is what you are allowing when you do so.

Quoted 7 lines
Quoted 4 lines

Nope, DriverFence is just Send, not Sync.

data.data is Sync, but data in the code above is not the actual user data, but the memory backing it up.

Ok, well, it probably should be Sync. I don't see any &self methods that can't be called from multiple threads in parallel.

No. Why?

There can only be one owner of DriverFence.

Regarding the backend_ops that might access DriverFence::data, we take care to guard that.

And DriverFence itself can only be owned by one party. That can only be accessed by many threads if the driver stuffs that fence into something that implements Sync.

Quoted 68 lines
Quoted 54 lines
Quoted 54 lines
Quoted 53 lines
Quoted 28 lines
Quoted 28 lines
Quoted 13 lines

+                bindings::dma_fence_signal_locked(fence);
+            }
+            bindings::dma_fence_unlock_irqrestore(fence, flag_ptr); +        }
+
+        // SAFETY: self.data is owned by the DriverFence, but could be accessed +        // through some dma_fence callbacks right now. Access is being revoked +        // above by signalling the fence. The DriverFenceAllowedData trait +        // ensures that the data either does not need drop, or if it does it +        // lives in a RcuBox which will delay dropping by one grace period, hence +        // ensuring that all readers have disappeared.
+        unsafe { drop_in_place(self.data.as_ptr()) };

[Severity: Critical]
Could this eagerly drop FenceCtx and user data before the C dma_fence refcount reaches 0?

The C dma_fence object can remain alive as long as consumers (like sync_file or the GPU scheduler) hold references to it. If the backend later invokes callbacks like ops->get_driver_name(), FenceCtx::get_driver_name() would attempt to read fctx.driver_name from the already-dropped FenceCtx:

FenceCtx::get_driver_name() {
    ...
    fctx.driver_name.as_char_ptr()
}

Quoted 5 lines
Quoted 1 line

Can this result in a use-after-free?

Hmm, I don't know about what sahisko said, but I don't think this is the right way to do rcu freeing. I think the type's destructor should be reserved for cases where the value becomes immediately unusable.

We could guard the strings with RcuBox, but we could not then guard the FenceCtx code against code UAF if we don't have the rcu_barrier().

Or could we?

If a rust module unloads, module::remove() should contain an rcu_barrier() (right??). Would that be enough to guard against the FenceCtx code being unloaded?

Quoted 5 lines

For example, let's say I'm using RcuBox<_> here. Yes, the data you get from dereferencing the RcuBox will stay alive for a grace period, but IMO once you run the destructor of the box itself, the *pointer* becomes immediately unusable.

I don't know why you're stressing the pointer?

The trick above is simply that drop / dealloc *and* code unloading is delayed by a grace period.

Quoted 8 lines
Quoted 5 lines

Sorry let me try to rephrase. I'm not worried about the stuff behind the pointer. After all, you're using RcuBox to protect that stuff. What I'm worried about is the pointer itself. You invoked drop_in_place() on the pointer to the fence context,

on the pointer to DriverFenceData, which contains a refcount to the FenceCtx, which might then want to drop.

Let me clarify.

You invoked drop_in_place on DriverFenceData which is effectively this method:

Quoted 7 lines

fn drop_in_place::<DriverFenceData<F,C>>(self: *mut DriverFenceData) { drop_in_place::<Fence>(&raw mut (*self).fence); drop_in_place::<Arc<FenceCtx<F,C>>>(&raw mut (*self).fctx); drop_in_place::<F>(&raw mut (*self).data);
}

So yes you did indirectly invoke drop_in_place on Arc<FenceCtx<F,C>>.

Yes.

Quoted 6 lines
Quoted 6 lines
Quoted 5 lines

 so even though
the fence context may be valid for another grace period, the *pointer* to the fence context is not. The pointer could have been zeroed by the destructor.

Quoted 8 lines
Quoted 4 lines

That particular pointer to the DriverFenceData could have been zeroed. But potential other accessors have already crafted themselves a new pointer to the, by the power of RCU, still valid data. That new pointer is container-of-ed from struct dma_fence *f.

I'm not talking about the pointer to DriverFenceData, I'm talking about the pointer to the FenceCtx, or the pointer to the data (if F is RcuBox).

Yeah, but the backing memory is still alive. And new pointers to that memory get crafted by the accessors. If a callback accesses the data through container_of(Fence), it gets a new pointer.

So what's the problem?

Where is the invalid pointer that someone is accessing?

Quoted 4 lines

The Arc type is not a type that opts-out of &mut == exclusive, so the second drop_in_place() above is assumed exclusive access to the Arc<FenceCtx<F,C>> field.

OK, so I think I see the problem. So the invalid pointer is Arc<FenceCtx…>? And potentially the <F> pointer (although we don't have a picture yet as to how that would be accessed through other callbacks.

Quoted 4 lines

If another thread obtains a pointer to the
FenceCtx via reading the fctx field of the DriverFence in parallel with this, then that's not allowed because the drop_in_place() call has exclusive access to that field.

I think I have been asking in several of our meetings in the past whether it is actually a problem to access data that has been dropped() IF we know that drop does not cause UAF and the answer was kind of like a "well if it does not actually get freed…"

Anyways.

It would seem the way to get this right is then

synchronize_rcu();
drop_in_palace(data);

Agreed?

This would then mean, however, that every time a fence drops, you have to wait a grace period.

Or maybe stuff DriverFenceData into an RcuBox, too, and defer its dropping.

P.

Quoted 8 lines

This is why Boqun's RcuFreeSafe has a drop_in_place_before_gp() method that can be used instead of drop_in_place() for this kind of use-case. You must leave the fctx and data fields in a state where they contain data that's safe to use for at least one grace period, and drop_in_place() fundamentally does not leave the value in a usable state.

Alice

maintainer
#25 lore raw

Re: [PATCH 3/4] rust: Add dma_fence abstractions

Quoted 58 lines · Philipp Stanner
Quoted 57 lines · Alice Ryhl
Quoted 55 lines · Philipp Stanner

+Cc Boris

Quoted 39 lines · Alice Ryhl
Quoted 38 lines · Philipp Stanner
Quoted 35 lines · Alice Ryhl
Quoted 8 lines · On Sat, May 30, 2026 at 03:06:21PM +0000, [email protected] wrote:
Quoted 4 lines

+impl<F: Send + Sync, C: Send + Sync> DriverFence<F, C> { +    fn as_raw(&self) -> *mut bindings::dma_fence { +        // SAFETY: Valid because self is valid.
+        let fence_data = unsafe { &mut *self.data.as_ptr() };

[Severity: High]
Does this create an exclusive mutable reference to actively shared memory?

Quoted 7 lines

DriverFenceData can be accessed concurrently by other threads holding Fence references (for instance, when checking if the fence is signaled). Creating a mutable reference (&mut) in Rust asserts exclusive access and violates aliasing rules, which allows the compiler to make invalid optimization assumptions.

Could this use an immutable reference &*self.data.as_ptr() instead?

Yes, please use an immutable reference here.

Quoted 6 lines
Quoted 3 lines

+
+        fence_data.inner.inner.get()
+    }

[ ... ]

Quoted 8 lines
Quoted 4 lines

+impl<F: Send + Sync, C: Send + Sync> DriverFenceBorrow<F, C> { +    fn as_raw(&self) -> *mut bindings::dma_fence {
+        // SAFETY: Valid because self is valid.
+        let fence_data = unsafe { &mut *self.data.as_ptr() };

[Severity: High]
Similar to DriverFence::as_raw(), does this also incorrectly create a mutable reference to shared data?

Here as well.

Quoted 8 lines
Quoted 4 lines

data is not shared. By design there is only ever one DriverFence, and the driver's data (data.data) is Sync.

But I guess an immutable one should do the trick, too.

There's only one DriverFence, but I can perform shared access to that one DriverFence from two threads in parallel. You made the type Sync, and this is what you are allowing when you do so.

Nope, DriverFence is just Send, not Sync.

data.data is Sync, but data in the code above is not the actual user data, but the memory backing it up.

Quoted 6 lines
Quoted 2 lines

Ok, well, it probably should be Sync. I don't see any &self methods that can't be called from multiple threads in parallel.

No. Why?

There can only be one owner of DriverFence.

Values normally have only one owner, and they are also normally Sync.

Quoted 6 lines

Regarding the backend_ops that might access DriverFence::data, we take care to guard that.

And DriverFence itself can only be owned by one party. That can only be accessed by many threads if the driver stuffs that fence into something that implements Sync.

If you don't implement Sync, then DriverFence cannot be stored in an Arc. I wouldn't take away that ability unless you have to, and I don't see anything in the DriverFence API that would mean you can't do that.

Quoted 36 lines
Quoted 5 lines
Quoted 5 lines
Quoted 4 lines

 so even though
the fence context may be valid for another grace period, the *pointer* to the fence context is not. The pointer could have been zeroed by the destructor.

Quoted 8 lines
Quoted 4 lines

That particular pointer to the DriverFenceData could have been zeroed. But potential other accessors have already crafted themselves a new pointer to the, by the power of RCU, still valid data. That new pointer is container-of-ed from struct dma_fence *f.

I'm not talking about the pointer to DriverFenceData, I'm talking about the pointer to the FenceCtx, or the pointer to the data (if F is RcuBox).

Yeah, but the backing memory is still alive. And new pointers to that memory get crafted by the accessors. If a callback accesses the data through container_of(Fence), it gets a new pointer.

So what's the problem?

Where is the invalid pointer that someone is accessing?

Quoted 4 lines

The Arc type is not a type that opts-out of &mut == exclusive, so the second drop_in_place() above is assumed exclusive access to the Arc<FenceCtx<F,C>> field.

OK, so I think I see the problem. So the invalid pointer is Arc<FenceCtx…>? And potentially the <F> pointer (although we don't have a picture yet as to how that would be accessed through other callbacks.

Quoted 4 lines

If another thread obtains a pointer to the
FenceCtx via reading the fctx field of the DriverFence in parallel with this, then that's not allowed because the drop_in_place() call has exclusive access to that field.

Quoted 4 lines

I think I have been asking in several of our meetings in the past whether it is actually a problem to access data that has been dropped() IF we know that drop does not cause UAF and the answer was kind of like a "well if it does not actually get freed…"

Ok, well, IMO the simplest approach is to say you can't. There may be roundabout ways to do it, but I would suggest that we just ... don't.

Quoted 8 lines

Anyways.

It would seem the way to get this right is then

synchronize_rcu();
drop_in_palace(data);

Quoted 7 lines

Agreed?

This would then mean, however, that every time a fence drops, you have to wait a grace period.

Or maybe stuff DriverFenceData into an RcuBox, too, and defer its dropping.

That would work, but I think we can do better and avoid the synchronize_rcu() along these lines:

unsafe trait RcuRevocable {
    unsafe fn rcu_revoke_in_place(ptr: *mut Self);
}

This trait provides a method that's like drop_in_place(), except that when you use this destructor, the value remains usable for one grace period. You could implement it for RcuBox, and for any Copy type, and for ARef<T> when T is cleaned up with rcu, and probably also other stuff.

Alice

#26 lore raw

Re: [PATCH 3/4] rust: Add dma_fence abstractions

Quoted 71 lines · Alice Ryhl
Quoted 58 lines · Philipp Stanner
Quoted 57 lines · Alice Ryhl
Quoted 55 lines · Philipp Stanner

+Cc Boris

Quoted 39 lines · Alice Ryhl
Quoted 38 lines · Philipp Stanner
Quoted 35 lines · Alice Ryhl
Quoted 8 lines · On Sat, May 30, 2026 at 03:06:21PM +0000, [email protected] wrote:
Quoted 4 lines

+impl<F: Send + Sync, C: Send + Sync> DriverFence<F, C> { +    fn as_raw(&self) -> *mut bindings::dma_fence { +        // SAFETY: Valid because self is valid.
+        let fence_data = unsafe { &mut *self.data.as_ptr() };

[Severity: High]
Does this create an exclusive mutable reference to actively shared memory?

Quoted 7 lines

DriverFenceData can be accessed concurrently by other threads holding Fence references (for instance, when checking if the fence is signaled). Creating a mutable reference (&mut) in Rust asserts exclusive access and violates aliasing rules, which allows the compiler to make invalid optimization assumptions.

Could this use an immutable reference &*self.data.as_ptr() instead?

Yes, please use an immutable reference here.

Quoted 6 lines
Quoted 3 lines

+
+        fence_data.inner.inner.get()
+    }

[ ... ]

Quoted 8 lines
Quoted 4 lines

+impl<F: Send + Sync, C: Send + Sync> DriverFenceBorrow<F, C> { +    fn as_raw(&self) -> *mut bindings::dma_fence {
+        // SAFETY: Valid because self is valid.
+        let fence_data = unsafe { &mut *self.data.as_ptr() };

[Severity: High]
Similar to DriverFence::as_raw(), does this also incorrectly create a mutable reference to shared data?

Here as well.

Quoted 8 lines
Quoted 4 lines

data is not shared. By design there is only ever one DriverFence, and the driver's data (data.data) is Sync.

But I guess an immutable one should do the trick, too.

There's only one DriverFence, but I can perform shared access to that one DriverFence from two threads in parallel. You made the type Sync, and this is what you are allowing when you do so.

Nope, DriverFence is just Send, not Sync.

data.data is Sync, but data in the code above is not the actual user data, but the memory backing it up.

Quoted 6 lines
Quoted 2 lines

Ok, well, it probably should be Sync. I don't see any &self methods that can't be called from multiple threads in parallel.

No. Why?

There can only be one owner of DriverFence.

Values normally have only one owner, and they are also normally Sync.

Quoted 3 lines

Regarding the backend_ops that might access DriverFence::data, we take care to guard that.

Quoted 7 lines
Quoted 3 lines

And DriverFence itself can only be owned by one party. That can only be accessed by many threads if the driver stuffs that fence into something that implements Sync.

If you don't implement Sync, then DriverFence cannot be stored in an Arc. I wouldn't take away that ability unless you have to, and I don't see anything in the DriverFence API that would mean you can't do that.

Nope. We explicitly agreed on this design.

Just 1 DriverFence. Just 1 party that can signal it.
Note that we also agreed upon the Driverfence disappearing with .signal(), which certainly prevents several from existing, unless you do an Option.take()

Quoted 37 lines
Quoted 37 lines
Quoted 6 lines
Quoted 6 lines
Quoted 5 lines

 so even though
the fence context may be valid for another grace period, the *pointer* to the fence context is not. The pointer could have been zeroed by the destructor.

Quoted 8 lines
Quoted 4 lines

That particular pointer to the DriverFenceData could have been zeroed. But potential other accessors have already crafted themselves a new pointer to the, by the power of RCU, still valid data. That new pointer is container-of-ed from struct dma_fence *f.

I'm not talking about the pointer to DriverFenceData, I'm talking about the pointer to the FenceCtx, or the pointer to the data (if F is RcuBox).

Yeah, but the backing memory is still alive. And new pointers to that memory get crafted by the accessors. If a callback accesses the data through container_of(Fence), it gets a new pointer.

So what's the problem?

Where is the invalid pointer that someone is accessing?

Quoted 4 lines

The Arc type is not a type that opts-out of &mut == exclusive, so the second drop_in_place() above is assumed exclusive access to the Arc<FenceCtx<F,C>> field.

OK, so I think I see the problem. So the invalid pointer is Arc<FenceCtx…>? And potentially the <F> pointer (although we don't have a picture yet as to how that would be accessed through other callbacks.

Quoted 4 lines

 If another thread obtains a pointer to the
FenceCtx via reading the fctx field of the DriverFence in parallel with this, then that's not allowed because the drop_in_place() call has exclusive access to that field.

Quoted 7 lines
Quoted 4 lines

I think I have been asking in several of our meetings in the past whether it is actually a problem to access data that has been dropped() IF we know that drop does not cause UAF and the answer was kind of like a "well if it does not actually get freed…"

Ok, well, IMO the simplest approach is to say you can't. There may be roundabout ways to do it, but I would suggest that we just ... don't.

Ack.

Quoted 24 lines
Quoted 9 lines

Anyways.

It would seem the way to get this right is then

synchronize_rcu();
drop_in_palace(data);

Quoted 7 lines

Agreed?

This would then mean, however, that every time a fence drops, you have to wait a grace period.

Or maybe stuff DriverFenceData into an RcuBox, too, and defer its dropping.

That would work, but I think we can do better and avoid the synchronize_rcu() along these lines:

unsafe trait RcuRevocable {
    unsafe fn rcu_revoke_in_place(ptr: *mut Self); }

Quoted 5 lines

This trait provides a method that's like drop_in_place(), except that when you use this destructor, the value remains usable for one grace period. You could implement it for RcuBox, and for any Copy type, and for ARef<T> when T is cleaned up with rcu, and probably also other stuff.

I mean, this cannot be magic. It also boils down to executing one RCU callback per DriverFence dropping.

Is there a significant difference to stuffing DriverFenceData into an RcuBox?

P.

Quoted 2 lines

Alice

maintainer
#27 lore raw

Re: [PATCH 3/4] rust: Add dma_fence abstractions

Quoted 80 lines · Philipp Stanner
Quoted 71 lines · Alice Ryhl
Quoted 58 lines · Philipp Stanner
Quoted 57 lines · Alice Ryhl
Quoted 55 lines · Philipp Stanner

+Cc Boris

Quoted 39 lines · Alice Ryhl
Quoted 38 lines · Philipp Stanner
Quoted 35 lines · Alice Ryhl
Quoted 8 lines · On Sat, May 30, 2026 at 03:06:21PM +0000, [email protected] wrote:
Quoted 4 lines

+impl<F: Send + Sync, C: Send + Sync> DriverFence<F, C> { +    fn as_raw(&self) -> *mut bindings::dma_fence { +        // SAFETY: Valid because self is valid.
+        let fence_data = unsafe { &mut *self.data.as_ptr() };

[Severity: High]
Does this create an exclusive mutable reference to actively shared memory?

Quoted 7 lines

DriverFenceData can be accessed concurrently by other threads holding Fence references (for instance, when checking if the fence is signaled). Creating a mutable reference (&mut) in Rust asserts exclusive access and violates aliasing rules, which allows the compiler to make invalid optimization assumptions.

Could this use an immutable reference &*self.data.as_ptr() instead?

Yes, please use an immutable reference here.

Quoted 6 lines
Quoted 3 lines

+
+        fence_data.inner.inner.get()
+    }

[ ... ]

Quoted 8 lines
Quoted 4 lines

+impl<F: Send + Sync, C: Send + Sync> DriverFenceBorrow<F, C> { +    fn as_raw(&self) -> *mut bindings::dma_fence {
+        // SAFETY: Valid because self is valid.
+        let fence_data = unsafe { &mut *self.data.as_ptr() };

[Severity: High]
Similar to DriverFence::as_raw(), does this also incorrectly create a mutable reference to shared data?

Here as well.

Quoted 8 lines
Quoted 4 lines

data is not shared. By design there is only ever one DriverFence, and the driver's data (data.data) is Sync.

But I guess an immutable one should do the trick, too.

There's only one DriverFence, but I can perform shared access to that one DriverFence from two threads in parallel. You made the type Sync, and this is what you are allowing when you do so.

Nope, DriverFence is just Send, not Sync.

data.data is Sync, but data in the code above is not the actual user data, but the memory backing it up.

Quoted 6 lines
Quoted 2 lines

Ok, well, it probably should be Sync. I don't see any &self methods that can't be called from multiple threads in parallel.

No. Why?

There can only be one owner of DriverFence.

Values normally have only one owner, and they are also normally Sync.

Quoted 3 lines

Regarding the backend_ops that might access DriverFence::data, we take care to guard that.

Quoted 7 lines
Quoted 3 lines

And DriverFence itself can only be owned by one party. That can only be accessed by many threads if the driver stuffs that fence into something that implements Sync.

If you don't implement Sync, then DriverFence cannot be stored in an Arc. I wouldn't take away that ability unless you have to, and I don't see anything in the DriverFence API that would mean you can't do that.

Quoted 6 lines

Nope. We explicitly agreed on this design.

Just 1 DriverFence. Just 1 party that can signal it.
Note that we also agreed upon the Driverfence disappearing with .signal(), which certainly prevents several from existing, unless you do an Option.take()

I would like to clarify that I'm not suggesting any changes to the design. Implementing Sync is not the same as having multiple driver fences.

Quoted 76 lines
Quoted 36 lines
Quoted 36 lines
Quoted 5 lines
Quoted 5 lines
Quoted 4 lines

 so even though
the fence context may be valid for another grace period, the *pointer* to the fence context is not. The pointer could have been zeroed by the destructor.

Quoted 8 lines
Quoted 4 lines

That particular pointer to the DriverFenceData could have been zeroed. But potential other accessors have already crafted themselves a new pointer to the, by the power of RCU, still valid data. That new pointer is container-of-ed from struct dma_fence *f.

I'm not talking about the pointer to DriverFenceData, I'm talking about the pointer to the FenceCtx, or the pointer to the data (if F is RcuBox).

Yeah, but the backing memory is still alive. And new pointers to that memory get crafted by the accessors. If a callback accesses the data through container_of(Fence), it gets a new pointer.

So what's the problem?

Where is the invalid pointer that someone is accessing?

Quoted 4 lines

The Arc type is not a type that opts-out of &mut == exclusive, so the second drop_in_place() above is assumed exclusive access to the Arc<FenceCtx<F,C>> field.

OK, so I think I see the problem. So the invalid pointer is Arc<FenceCtx…>? And potentially the <F> pointer (although we don't have a picture yet as to how that would be accessed through other callbacks.

Quoted 4 lines

 If another thread obtains a pointer to the
FenceCtx via reading the fctx field of the DriverFence in parallel with this, then that's not allowed because the drop_in_place() call has exclusive access to that field.

Quoted 7 lines
Quoted 4 lines

I think I have been asking in several of our meetings in the past whether it is actually a problem to access data that has been dropped() IF we know that drop does not cause UAF and the answer was kind of like a "well if it does not actually get freed…"

Ok, well, IMO the simplest approach is to say you can't. There may be roundabout ways to do it, but I would suggest that we just ... don't.

Ack.

Quoted 24 lines
Quoted 9 lines

Anyways.

It would seem the way to get this right is then

synchronize_rcu();
drop_in_palace(data);

Quoted 7 lines

Agreed?

This would then mean, however, that every time a fence drops, you have to wait a grace period.

Or maybe stuff DriverFenceData into an RcuBox, too, and defer its dropping.

That would work, but I think we can do better and avoid the synchronize_rcu() along these lines:

unsafe trait RcuRevocable {
    unsafe fn rcu_revoke_in_place(ptr: *mut Self); }

Quoted 5 lines

This trait provides a method that's like drop_in_place(), except that when you use this destructor, the value remains usable for one grace period. You could implement it for RcuBox, and for any Copy type, and for ARef<T> when T is cleaned up with rcu, and probably also other stuff.

Quoted 5 lines

I mean, this cannot be magic. It also boils down to executing one RCU callback per DriverFence dropping.

Is there a significant difference to stuffing DriverFenceData into an RcuBox?

Do you mean hard-coding that the user-data of a driver fence is always stored in an RcuBox?

Alice

#28 lore raw

Re: [PATCH 3/4] rust: Add dma_fence abstractions

Quoted 1 line · Alice Ryhl

On

[…]

Quoted 7 lines
Quoted 7 lines
Quoted 4 lines

If you don't implement Sync, then DriverFence cannot be stored in an Arc. I wouldn't take away that ability unless you have to, and I don't see anything in the DriverFence API that would mean you can't do that.

Nope. We explicitly agreed on this design.

Quoted 8 lines
Quoted 4 lines

Just 1 DriverFence. Just 1 party that can signal it.
Note that we also agreed upon the Driverfence disappearing with .signal(), which certainly prevents several from existing, unless you do an Option.take()

I would like to clarify that I'm not suggesting any changes to the design. Implementing Sync is not the same as having multiple driver fences.

I mean, I guess one can do that. But it's up to the driver then to see how it can signal its fence.

Quoted 77 lines
Quoted 77 lines
Quoted 37 lines
Quoted 37 lines
Quoted 6 lines
Quoted 6 lines
Quoted 5 lines

 so even though
the fence context may be valid for another grace period, the *pointer* to the fence context is not. The pointer could have been zeroed by the destructor.

Quoted 8 lines
Quoted 4 lines

That particular pointer to the DriverFenceData could have been zeroed. But potential other accessors have already crafted themselves a new pointer to the, by the power of RCU, still valid data. That new pointer is container-of-ed from struct dma_fence *f.

I'm not talking about the pointer to DriverFenceData, I'm talking about the pointer to the FenceCtx, or the pointer to the data (if F is RcuBox).

Yeah, but the backing memory is still alive. And new pointers to that memory get crafted by the accessors. If a callback accesses the data through container_of(Fence), it gets a new pointer.

So what's the problem?

Where is the invalid pointer that someone is accessing?

Quoted 4 lines

The Arc type is not a type that opts-out of &mut == exclusive, so the second drop_in_place() above is assumed exclusive access to the Arc<FenceCtx<F,C>> field.

OK, so I think I see the problem. So the invalid pointer is Arc<FenceCtx…>? And potentially the <F> pointer (although we don't have a picture yet as to how that would be accessed through other callbacks.

Quoted 4 lines

 If another thread obtains a pointer to the
FenceCtx via reading the fctx field of the DriverFence in parallel with this, then that's not allowed because the drop_in_place() call has exclusive access to that field.

Quoted 7 lines
Quoted 4 lines

I think I have been asking in several of our meetings in the past whether it is actually a problem to access data that has been dropped() IF we know that drop does not cause UAF and the answer was kind of like a "well if it does not actually get freed…"

Ok, well, IMO the simplest approach is to say you can't. There may be roundabout ways to do it, but I would suggest that we just ... don't.

Ack.

Quoted 29 lines
Quoted 9 lines

Anyways.

It would seem the way to get this right is then

synchronize_rcu();
drop_in_palace(data);

Quoted 7 lines

Agreed?

This would then mean, however, that every time a fence drops, you have to wait a grace period.

Or maybe stuff DriverFenceData into an RcuBox, too, and defer its dropping.

That would work, but I think we can do better and avoid the synchronize_rcu() along these lines:

unsafe trait RcuRevocable {
    unsafe fn rcu_revoke_in_place(ptr: *mut Self); }

This trait provides a method that's like drop_in_place(), except that when you use this destructor, the value remains usable for one grace period. You could implement it for RcuBox, and for any Copy type, and for ARef<T> when T is cleaned up with rcu, and probably also other stuff.

Quoted 8 lines
Quoted 5 lines

I mean, this cannot be magic. It also boils down to executing one RCU callback per DriverFence dropping.

Is there a significant difference to stuffing DriverFenceData into an RcuBox?

Do you mean hard-coding that the user-data of a driver fence is always stored in an RcuBox?

I'm talking about this:

impl<F: Send + Sync + DriverFenceAllowedData, C: Send + Sync> DriverFenceAllocation<F, C> {
    /// Create a new allocation slot that can later be used to create a fully
    /// initialized [`DriverFence`] without the need to allocate.
    pub fn new(fctx: Arc<FenceCtx<F, C>>, data: F) -> Result<Self> {
        let fence_data = DriverFenceData {
            // `inner` remains uninitialized until a [`DriverFence`] takes over.
            inner: Fence {
                inner: Opaque::uninit(),
            },
            fctx,
            data,
        };

        // In order to support the C dma_fence callbacks, it is necessary for
        // a `Fence` and a `DriverFence` to live in the same allocation,
        // because the C backend passes a dma_fence, from which the driver most
        // likely wants to be able to access its `data` in `DriverFence`.
        //
        // Hence, we need the manage the memory manually. It will be freed by the
        // C backend automatically once the refcount within `Fence` drops to 0.
        let data = RcuBox::new(fence_data, GFP_KERNEL | __GFP_ZERO)?;

        Ok(Self { data })
    }

This way, the entire DriverFenceData will remain valid for an additional grace period. I suppose this would solve your pointer- invalid concern.

However, it appears like overkill to me because the refcounting + C backend already ensure that nothing drops too soon, and the backend frees with kfree_rcu(), so…

P.

Quoted 2 lines

Alice

maintainer
#29 lore raw

Re: [PATCH 3/4] rust: Add dma_fence abstractions

Quoted 17 lines · Philipp Stanner
Quoted 1 line · Alice Ryhl

On

[…]

Quoted 12 lines
Quoted 11 lines
Quoted 4 lines

If you don't implement Sync, then DriverFence cannot be stored in an Arc. I wouldn't take away that ability unless you have to, and I don't see anything in the DriverFence API that would mean you can't do that.

Nope. We explicitly agreed on this design.

Just 1 DriverFence. Just 1 party that can signal it.
Note that we also agreed upon the Driverfence disappearing with .signal(), which certainly prevents several from existing, unless you do an Option.take()

Quoted 5 lines
Quoted 3 lines

I would like to clarify that I'm not suggesting any changes to the design. Implementing Sync is not the same as having multiple driver fences.

I mean, I guess one can do that. But it's up to the driver then to see how it can signal its fence.

I don't believe Sync changes anything with that regard. The signal method takes 'self', but the Sync trait only affects how '&self' methods can be called.

Quoted 116 lines
Quoted 76 lines
Quoted 76 lines
Quoted 36 lines
Quoted 36 lines
Quoted 5 lines
Quoted 5 lines
Quoted 4 lines

 so even though
the fence context may be valid for another grace period, the *pointer* to the fence context is not. The pointer could have been zeroed by the destructor.

Quoted 8 lines
Quoted 4 lines

That particular pointer to the DriverFenceData could have been zeroed. But potential other accessors have already crafted themselves a new pointer to the, by the power of RCU, still valid data. That new pointer is container-of-ed from struct dma_fence *f.

I'm not talking about the pointer to DriverFenceData, I'm talking about the pointer to the FenceCtx, or the pointer to the data (if F is RcuBox).

Yeah, but the backing memory is still alive. And new pointers to that memory get crafted by the accessors. If a callback accesses the data through container_of(Fence), it gets a new pointer.

So what's the problem?

Where is the invalid pointer that someone is accessing?

Quoted 4 lines

The Arc type is not a type that opts-out of &mut == exclusive, so the second drop_in_place() above is assumed exclusive access to the Arc<FenceCtx<F,C>> field.

OK, so I think I see the problem. So the invalid pointer is Arc<FenceCtx…>? And potentially the <F> pointer (although we don't have a picture yet as to how that would be accessed through other callbacks.

Quoted 4 lines

 If another thread obtains a pointer to the
FenceCtx via reading the fctx field of the DriverFence in parallel with this, then that's not allowed because the drop_in_place() call has exclusive access to that field.

Quoted 7 lines
Quoted 4 lines

I think I have been asking in several of our meetings in the past whether it is actually a problem to access data that has been dropped() IF we know that drop does not cause UAF and the answer was kind of like a "well if it does not actually get freed…"

Ok, well, IMO the simplest approach is to say you can't. There may be roundabout ways to do it, but I would suggest that we just ... don't.

Ack.

Quoted 29 lines
Quoted 9 lines

Anyways.

It would seem the way to get this right is then

synchronize_rcu();
drop_in_palace(data);

Quoted 7 lines

Agreed?

This would then mean, however, that every time a fence drops, you have to wait a grace period.

Or maybe stuff DriverFenceData into an RcuBox, too, and defer its dropping.

That would work, but I think we can do better and avoid the synchronize_rcu() along these lines:

unsafe trait RcuRevocable {
    unsafe fn rcu_revoke_in_place(ptr: *mut Self); }

This trait provides a method that's like drop_in_place(), except that when you use this destructor, the value remains usable for one grace period. You could implement it for RcuBox, and for any Copy type, and for ARef<T> when T is cleaned up with rcu, and probably also other stuff.

Quoted 8 lines
Quoted 5 lines

I mean, this cannot be magic. It also boils down to executing one RCU callback per DriverFence dropping.

Is there a significant difference to stuffing DriverFenceData into an RcuBox?

Do you mean hard-coding that the user-data of a driver fence is always stored in an RcuBox?

I'm talking about this:

impl<F: Send + Sync + DriverFenceAllowedData, C: Send + Sync> DriverFenceAllocation<F, C> {
    /// Create a new allocation slot that can later be used to create a fully
    /// initialized [`DriverFence`] without the need to allocate.
    pub fn new(fctx: Arc<FenceCtx<F, C>>, data: F) -> Result<Self> {
        let fence_data = DriverFenceData {
            // `inner` remains uninitialized until a [`DriverFence`] takes over.
            inner: Fence {
                inner: Opaque::uninit(),
            },
            fctx,
            data,
        };

        // In order to support the C dma_fence callbacks, it is necessary for
        // a `Fence` and a `DriverFence` to live in the same allocation,
        // because the C backend passes a dma_fence, from which the driver most
        // likely wants to be able to access its `data` in `DriverFence`.
        //
        // Hence, we need the manage the memory manually. It will be freed by the
        // C backend automatically once the refcount within `Fence` drops to 0.
        let data = RcuBox::new(fence_data, GFP_KERNEL | __GFP_ZERO)?;

        Ok(Self { data })
    }
Quoted 7 lines

This way, the entire DriverFenceData will remain valid for an additional grace period. I suppose this would solve your pointer- invalid concern.

However, it appears like overkill to me because the refcounting + C backend already ensure that nothing drops too soon, and the backend frees with kfree_rcu(), so…

I agree that it doesn't sound like we want RcuBox here.

What kind of metadata are we actually planning to store in the DriverFence in practice?

Alice

#30 lore raw

Re: [PATCH 3/4] rust: Add dma_fence abstractions

+Cc Danilo, Gary, Daniel, Christian
(who seem to have been lost because Sashiko dropped them and this thread is a reply to that tool)

Quoted 148 lines · Alice Ryhl
Quoted 17 lines · Philipp Stanner
Quoted 1 line · Alice Ryhl

On

[…]

Quoted 12 lines
Quoted 11 lines
Quoted 4 lines

If you don't implement Sync, then DriverFence cannot be stored in an Arc. I wouldn't take away that ability unless you have to, and I don't see anything in the DriverFence API that would mean you can't do that.

Nope. We explicitly agreed on this design.

Just 1 DriverFence. Just 1 party that can signal it.
Note that we also agreed upon the Driverfence disappearing with .signal(), which certainly prevents several from existing, unless you do an Option.take()

Quoted 5 lines
Quoted 3 lines

I would like to clarify that I'm not suggesting any changes to the design. Implementing Sync is not the same as having multiple driver fences.

I mean, I guess one can do that. But it's up to the driver then to see how it can signal its fence.

I don't believe Sync changes anything with that regard. The signal method takes 'self', but the Sync trait only affects how '&self' methods can be called.

Quoted 120 lines
Quoted 76 lines
Quoted 76 lines
Quoted 36 lines
Quoted 36 lines
Quoted 5 lines
Quoted 5 lines
Quoted 4 lines

 so even though
the fence context may be valid for another grace period, the *pointer* to the fence context is not. The pointer could have been zeroed by the destructor.

Quoted 8 lines
Quoted 4 lines

That particular pointer to the DriverFenceData could have been zeroed. But potential other accessors have already crafted themselves a new pointer to the, by the power of RCU, still valid data. That new pointer is container-of-ed from struct dma_fence *f.

I'm not talking about the pointer to DriverFenceData, I'm talking about the pointer to the FenceCtx, or the pointer to the data (if F is RcuBox).

Yeah, but the backing memory is still alive. And new pointers to that memory get crafted by the accessors. If a callback accesses the data through container_of(Fence), it gets a new pointer.

So what's the problem?

Where is the invalid pointer that someone is accessing?

Quoted 4 lines

The Arc type is not a type that opts-out of &mut == exclusive, so the second drop_in_place() above is assumed exclusive access to the Arc<FenceCtx<F,C>> field.

OK, so I think I see the problem. So the invalid pointer is Arc<FenceCtx…>? And potentially the <F> pointer (although we don't have a picture yet as to how that would be accessed through other callbacks.

Quoted 4 lines

 If another thread obtains a pointer to the
FenceCtx via reading the fctx field of the DriverFence in parallel with this, then that's not allowed because the drop_in_place() call has exclusive access to that field.

Quoted 7 lines
Quoted 4 lines

I think I have been asking in several of our meetings in the past whether it is actually a problem to access data that has been dropped() IF we know that drop does not cause UAF and the answer was kind of like a "well if it does not actually get freed…"

Ok, well, IMO the simplest approach is to say you can't. There may be roundabout ways to do it, but I would suggest that we just ... don't.

Ack.

Quoted 29 lines
Quoted 9 lines

Anyways.

It would seem the way to get this right is then

synchronize_rcu();
drop_in_palace(data);

Quoted 7 lines

Agreed?

This would then mean, however, that every time a fence drops, you have to wait a grace period.

Or maybe stuff DriverFenceData into an RcuBox, too, and defer its dropping.

That would work, but I think we can do better and avoid the synchronize_rcu() along these lines:

unsafe trait RcuRevocable {
    unsafe fn rcu_revoke_in_place(ptr: *mut Self); }

This trait provides a method that's like drop_in_place(), except that when you use this destructor, the value remains usable for one grace period. You could implement it for RcuBox, and for any Copy type, and for ARef<T> when T is cleaned up with rcu, and probably also other stuff.

Quoted 8 lines
Quoted 5 lines

I mean, this cannot be magic. It also boils down to executing one RCU callback per DriverFence dropping.

Is there a significant difference to stuffing DriverFenceData into an RcuBox?

Do you mean hard-coding that the user-data of a driver fence is always stored in an RcuBox?

I'm talking about this:

impl<F: Send + Sync + DriverFenceAllowedData, C: Send + Sync> DriverFenceAllocation<F, C> { /// Create a new allocation slot that can later be used to create a fully /// initialized [DriverFence] without the need to allocate.
    pub fn new(fctx: Arc<FenceCtx<F, C>>, data: F) -> Result<Self> {
        let fence_data = DriverFenceData {
            // inner remains uninitialized until a [DriverFence] takes over.
            inner: Fence {
                inner: Opaque::uninit(),
            },
            fctx,
            data,
        };

        // In order to support the C dma_fence callbacks, it is necessary for // a Fence and a DriverFence to live in the same allocation, // because the C backend passes a dma_fence, from which the driver most // likely wants to be able to access its data in DriverFence. //
        // Hence, we need the manage the memory manually. It will be freed by the // C backend automatically once the refcount within Fence drops to 0. let data = RcuBox::new(fence_data, GFP_KERNEL | __GFP_ZERO)?;

        Ok(Self { data })
    }

This way, the entire DriverFenceData will remain valid for an additional grace period. I suppose this would solve your pointer- invalid concern.

Quoted 8 lines
Quoted 3 lines

However, it appears like overkill to me because the refcounting + C backend already ensure that nothing drops too soon, and the backend frees with kfree_rcu(), so…

I agree that it doesn't sound like we want RcuBox here.

What kind of metadata are we actually planning to store in the DriverFence in practice?

I suppose it might be GPUVM payloads. IIRC in Tyr it's currently just empty structs as some sort of identifier. I guess copies of the actual command buffers will also reside in their associated fences.

But Boris, Daniel and Danilo probably have something smart to say about that.

P.

Quoted 2 lines

Alice

#31 lore raw

Re: [PATCH 3/4] rust: Add dma_fence abstractions

Quoted 162 lines · On Wed, 03 Jun 2026 08:10:51 +0200 Philipp Stanner <[email protected]> wrote:

+Cc Danilo, Gary, Daniel, Christian
(who seem to have been lost because Sashiko dropped them and this thread is a reply to that tool)

Quoted 148 lines · Alice Ryhl
Quoted 17 lines · Philipp Stanner
Quoted 1 line · Alice Ryhl

On

[…]

Quoted 12 lines
Quoted 11 lines
Quoted 4 lines

If you don't implement Sync, then DriverFence cannot be stored in an Arc. I wouldn't take away that ability unless you have to, and I don't see anything in the DriverFence API that would mean you can't do that.

Nope. We explicitly agreed on this design.

Just 1 DriverFence. Just 1 party that can signal it.
Note that we also agreed upon the Driverfence disappearing with .signal(), which certainly prevents several from existing, unless you do an Option.take()

Quoted 5 lines
Quoted 3 lines

I would like to clarify that I'm not suggesting any changes to the design. Implementing Sync is not the same as having multiple driver fences.

I mean, I guess one can do that. But it's up to the driver then to see how it can signal its fence.

I don't believe Sync changes anything with that regard. The signal method takes 'self', but the Sync trait only affects how '&self' methods can be called.

Quoted 120 lines
Quoted 76 lines
Quoted 76 lines
Quoted 36 lines
Quoted 36 lines
Quoted 5 lines
Quoted 5 lines
Quoted 4 lines

 so even though
the fence context may be valid for another grace period, the *pointer* to the fence context is not. The pointer could have been zeroed by the destructor.

Quoted 8 lines
Quoted 4 lines

That particular pointer to the DriverFenceData could have been zeroed. But potential other accessors have already crafted themselves a new pointer to the, by the power of RCU, still valid data. That new pointer is container-of-ed from struct dma_fence *f.

I'm not talking about the pointer to DriverFenceData, I'm talking about the pointer to the FenceCtx, or the pointer to the data (if F is RcuBox).

Yeah, but the backing memory is still alive. And new pointers to that memory get crafted by the accessors. If a callback accesses the data through container_of(Fence), it gets a new pointer.

So what's the problem?

Where is the invalid pointer that someone is accessing?

Quoted 4 lines

The Arc type is not a type that opts-out of &mut == exclusive, so the second drop_in_place() above is assumed exclusive access to the Arc<FenceCtx<F,C>> field.

OK, so I think I see the problem. So the invalid pointer is Arc<FenceCtx…>? And potentially the <F> pointer (although we don't have a picture yet as to how that would be accessed through other callbacks.

Quoted 4 lines

 If another thread obtains a pointer to the
FenceCtx via reading the fctx field of the DriverFence in parallel with this, then that's not allowed because the drop_in_place() call has exclusive access to that field.

Quoted 7 lines
Quoted 4 lines

I think I have been asking in several of our meetings in the past whether it is actually a problem to access data that has been dropped() IF we know that drop does not cause UAF and the answer was kind of like a "well if it does not actually get freed…"

Ok, well, IMO the simplest approach is to say you can't. There may be roundabout ways to do it, but I would suggest that we just ... don't.

Ack.

Quoted 29 lines
Quoted 9 lines

Anyways.

It would seem the way to get this right is then

synchronize_rcu();
drop_in_palace(data);

Quoted 7 lines

Agreed?

This would then mean, however, that every time a fence drops, you have to wait a grace period.

Or maybe stuff DriverFenceData into an RcuBox, too, and defer its dropping.

That would work, but I think we can do better and avoid the synchronize_rcu() along these lines:

unsafe trait RcuRevocable {
    unsafe fn rcu_revoke_in_place(ptr: *mut Self); }

This trait provides a method that's like drop_in_place(), except that when you use this destructor, the value remains usable for one grace period. You could implement it for RcuBox, and for any Copy type, and for ARef<T> when T is cleaned up with rcu, and probably also other stuff.

Quoted 8 lines
Quoted 5 lines

I mean, this cannot be magic. It also boils down to executing one RCU callback per DriverFence dropping.

Is there a significant difference to stuffing DriverFenceData into an RcuBox?

Do you mean hard-coding that the user-data of a driver fence is always stored in an RcuBox?

I'm talking about this:

impl<F: Send + Sync + DriverFenceAllowedData, C: Send + Sync> DriverFenceAllocation<F, C> { /// Create a new allocation slot that can later be used to create a fully /// initialized [DriverFence] without the need to allocate.
    pub fn new(fctx: Arc<FenceCtx<F, C>>, data: F) -> Result<Self> {
        let fence_data = DriverFenceData {
            // inner remains uninitialized until a [DriverFence] takes over.
            inner: Fence {
                inner: Opaque::uninit(),
            },
            fctx,
            data,
        };

        // In order to support the C dma_fence callbacks, it is necessary for // a Fence and a DriverFence to live in the same allocation, // because the C backend passes a dma_fence, from which the driver most // likely wants to be able to access its data in DriverFence. //
        // Hence, we need the manage the memory manually. It will be freed by the // C backend automatically once the refcount within Fence drops to 0. let data = RcuBox::new(fence_data, GFP_KERNEL | __GFP_ZERO)?;

        Ok(Self { data })
    }

This way, the entire DriverFenceData will remain valid for an additional grace period. I suppose this would solve your pointer- invalid concern.

Quoted 8 lines
Quoted 3 lines

However, it appears like overkill to me because the refcounting + C backend already ensure that nothing drops too soon, and the backend frees with kfree_rcu(), so…

I agree that it doesn't sound like we want RcuBox here.

What kind of metadata are we actually planning to store in the DriverFence in practice?

Quoted 6 lines

I suppose it might be GPUVM payloads. IIRC in Tyr it's currently just empty structs as some sort of identifier. I guess copies of the actual command buffers will also reside in their associated fences.

But Boris, Daniel and Danilo probably have something smart to say about that.

In Tyr we don't need any, because we're using SW signalling: we get an interrupt, look at our HW fence seqno, and walk the set of pending fences to signal them with the ::signal() method (that's an over-simplified view of it, but functionally that's how it works).

Now, the question is more, what drivers planning to support HW signaling want attached to the DriverFence. My gut feeling is that now that we have a way to get back to the FenceCtx, and given that FenceCtx also has driver-specific data attached to it, most common use cases where the fence contains a backpointer to its timeline/creator are covered. If I look at nouveau_fence [1] (I intentionally looked at the most likely next user of this) for instance:

struct nouveau_fence {
	struct dma_fence base;

	struct list_head head;

	struct nouveau_channel __rcu *channel;
	unsigned long timeout;
};

- channel is basically the FenceCtx in your new design
- head is used to insert the fence in the fence context list. Not too sure how it translates in rust (insertion in an XArray instead?). Anyway, that's just stuff you need to make your fence part of the context timeline, so it's again related to how you link a fence to its context and let the context walk back pending fences when it needs to. Feels like my original FenceTimeline abstract proposal [2] could help make this thing generic, but let's reconsider this once we've got the basics sorted out, please
- timeout is probably the only thing of interest here, assuming things can be signaled out of order (if they are signaled in order, like is the case in Tyr, the timeout can just be on the FenceCtxData and represent the timeout of the first fence in the set). Anyway, that's a scalar, so no fancy ::drop() needed if that's what you're worried about

[1]https://elixir.bootlin.com/linux/v7.1-rc5/source/drivers/gpu/drm/nouveau/nouveau_fence.h#L11 [2]https://gitlab.freedesktop.org/bbrezillon/linux/-/blob/8bec931a3a222873bf93cd8111d88c2f79df30cd/rust/kernel/sync/dma_fence.rs#L836

#32 lore raw

Re: [PATCH 3/4] rust: Add dma_fence abstractions

Quoted 185 lines · Boris Brezillon
Quoted 162 lines · On Wed, 03 Jun 2026 08:10:51 +0200 Philipp Stanner <[email protected]> wrote:

+Cc Danilo, Gary, Daniel, Christian
(who seem to have been lost because Sashiko dropped them and this thread is a reply to that tool)

Quoted 148 lines · Alice Ryhl
Quoted 17 lines · Philipp Stanner
Quoted 1 line · Alice Ryhl

On

[…]

Quoted 12 lines
Quoted 11 lines
Quoted 4 lines

If you don't implement Sync, then DriverFence cannot be stored in an Arc. I wouldn't take away that ability unless you have to, and I don't see anything in the DriverFence API that would mean you can't do that.

Nope. We explicitly agreed on this design.

Just 1 DriverFence. Just 1 party that can signal it.
Note that we also agreed upon the Driverfence disappearing with .signal(), which certainly prevents several from existing, unless you do an Option.take()

Quoted 5 lines
Quoted 3 lines

I would like to clarify that I'm not suggesting any changes to the design. Implementing Sync is not the same as having multiple driver fences.

I mean, I guess one can do that. But it's up to the driver then to see how it can signal its fence.

I don't believe Sync changes anything with that regard. The signal method takes 'self', but the Sync trait only affects how '&self' methods can be called.

Quoted 120 lines
Quoted 76 lines
Quoted 76 lines
Quoted 36 lines
Quoted 36 lines
Quoted 5 lines
Quoted 5 lines
Quoted 4 lines

 so even though
the fence context may be valid for another grace period, the *pointer* to the fence context is not. The pointer could have been zeroed by the destructor.

Quoted 8 lines
Quoted 4 lines

That particular pointer to the DriverFenceData could have been zeroed. But potential other accessors have already crafted themselves a new pointer to the, by the power of RCU, still valid data. That new pointer is container-of-ed from struct dma_fence *f.

I'm not talking about the pointer to DriverFenceData, I'm talking about the pointer to the FenceCtx, or the pointer to the data (if F is RcuBox).

Yeah, but the backing memory is still alive. And new pointers to that memory get crafted by the accessors. If a callback accesses the data through container_of(Fence), it gets a new pointer.

So what's the problem?

Where is the invalid pointer that someone is accessing?

Quoted 4 lines

The Arc type is not a type that opts-out of &mut == exclusive, so the second drop_in_place() above is assumed exclusive access to the Arc<FenceCtx<F,C>> field.

OK, so I think I see the problem. So the invalid pointer is Arc<FenceCtx…>? And potentially the <F> pointer (although we don't have a picture yet as to how that would be accessed through other callbacks.

Quoted 4 lines

 If another thread obtains a pointer to the
FenceCtx via reading the fctx field of the DriverFence in parallel with this, then that's not allowed because the drop_in_place() call has exclusive access to that field.

Quoted 7 lines
Quoted 4 lines

I think I have been asking in several of our meetings in the past whether it is actually a problem to access data that has been dropped() IF we know that drop does not cause UAF and the answer was kind of like a "well if it does not actually get freed…"

Ok, well, IMO the simplest approach is to say you can't. There may be roundabout ways to do it, but I would suggest that we just ... don't.

Ack.

Quoted 29 lines

Quoted 8 lines

Anyways.

It would seem the way to get this right is then

synchronize_rcu();
drop_in_palace(data);

Quoted 7 lines

Agreed?

This would then mean, however, that every time a fence drops, you have to wait a grace period.

Or maybe stuff DriverFenceData into an RcuBox, too, and defer its dropping.

That would work, but I think we can do better and avoid the synchronize_rcu() along these lines:

unsafe trait RcuRevocable {
    unsafe fn rcu_revoke_in_place(ptr: *mut Self); }

This trait provides a method that's like drop_in_place(), except that when you use this destructor, the value remains usable for one grace period. You could implement it for RcuBox, and for any Copy type, and for ARef<T> when T is cleaned up with rcu, and probably also other stuff.

Quoted 8 lines
Quoted 5 lines

I mean, this cannot be magic. It also boils down to executing one RCU callback per DriverFence dropping.

Is there a significant difference to stuffing DriverFenceData into an RcuBox?

Do you mean hard-coding that the user-data of a driver fence is always stored in an RcuBox?

I'm talking about this:

impl<F: Send + Sync + DriverFenceAllowedData, C: Send + Sync> DriverFenceAllocation<F, C> { /// Create a new allocation slot that can later be used to create a fully /// initialized [DriverFence] without the need to allocate.
    pub fn new(fctx: Arc<FenceCtx<F, C>>, data: F) -> Result<Self> {
        let fence_data = DriverFenceData {
            // inner remains uninitialized until a [DriverFence] takes over.
            inner: Fence {
                inner: Opaque::uninit(),
            },
            fctx,
            data,
        };

        // In order to support the C dma_fence callbacks, it is necessary for // a Fence and a DriverFence to live in the same allocation, // because the C backend passes a dma_fence, from which the driver most // likely wants to be able to access its data in DriverFence. //
        // Hence, we need the manage the memory manually. It will be freed by the // C backend automatically once the refcount within Fence drops to 0. let data = RcuBox::new(fence_data, GFP_KERNEL | __GFP_ZERO)?;

        Ok(Self { data })
    }

This way, the entire DriverFenceData will remain valid for an additional grace period. I suppose this would solve your pointer- invalid concern.

Quoted 8 lines
Quoted 3 lines

However, it appears like overkill to me because the refcounting + C backend already ensure that nothing drops too soon, and the backend frees with kfree_rcu(), so…

I agree that it doesn't sound like we want RcuBox here.

What kind of metadata are we actually planning to store in the DriverFence in practice?

Quoted 6 lines

I suppose it might be GPUVM payloads. IIRC in Tyr it's currently just empty structs as some sort of identifier. I guess copies of the actual command buffers will also reside in their associated fences.

But Boris, Daniel and Danilo probably have something smart to say about that.

In Tyr we don't need any, because we're using SW signalling: we get an interrupt, look at our HW fence seqno, and walk the set of pending fences to signal them with the ::signal() method (that's an over-simplified view of it, but functionally that's how it works).

Now, the question is more, what drivers planning to support HW signaling want attached to the DriverFence. My gut feeling is that now that we have a way to get back to the FenceCtx, and given that FenceCtx also has driver-specific data attached to it, most common use cases where the fence contains a backpointer to its timeline/creator are covered. If I look at nouveau_fence [1] (I intentionally looked at the most likely next user of this) for instance:

Quoted 8 lines
struct nouveau_fence {
	struct dma_fence base;

	struct list_head head;

	struct nouveau_channel __rcu *channel;
	unsigned long timeout;
};

Thx for the explanation

Quoted 16 lines

- channel is basically the FenceCtx in your new design
- head is used to insert the fence in the fence context list. Not too sure how it translates in rust (insertion in an XArray instead?). Anyway, that's just stuff you need to make your fence part of the context timeline, so it's again related to how you link a fence to its context and let the context walk back pending fences when it needs to. Feels like my original FenceTimeline abstract proposal [2] could help make this thing generic, but let's reconsider this once we've got the basics sorted out, please
- timeout is probably the only thing of interest here, assuming things can be signaled out of order (if they are signaled in order, like is the case in Tyr, the timeout can just be on the FenceCtxData and represent the timeout of the first fence in the set). Anyway, that's a scalar, so no fancy ::drop() needed if that's what you're worried about

What we / Alice are worried about is multiple things.

One issue is that Alice believes that

        // SAFETY: `self.data` is owned by the DriverFence, but could be accessed
        // through some dma_fence callbacks right now. Access is being revoked
        // above by signalling the fence. The DriverFenceSafeToDrop trait
        // ensures that the data either does not need drop, or if it does it
        // lives in a RcuBox which will delay dropping by one grace period, hence
        // ensuring that all readers have disappeared.
        unsafe { drop_in_place(self.data.as_ptr()) };

from DriverFence::drop() invalidates the pointers that thread A accesses, even if DriverFence::data.data's drop is delayed with RCU.

Now if thread B drops DriverFence, thread A could use invalid pointers, but while still operating on non-deallocated memory (because our refcounting guards against that).

The only way I can see to make that bullet proof would be to do synchronize_rcu() before the drop_in_place() above – but that would mean that we delay each fence drop by one grace period.

I'm still not sure how real the problem really is, though. Because refcounting guards, and drop() doesn't actually *do* something to the pointers, or does it?

--

Another, related issue would be
- thread A accesses DriverFence::data through a backend_ops
- thread B drops DriverFence
- there are no RCU callbacks (call_rcu()) pending
- thus, the rcu_barrier() in FenceCtx::drop() takes no effect. We would actually need synchronize_rcu() there.

So would seem we still didn't get fence and fence_ctx teardown completely right.

P.

Quoted 4 lines

[1]https://elixir.bootlin.com/linux/v7.1-rc5/source/drivers/gpu/drm/nouveau/nouveau_fence.h#L11 [2]https://gitlab.freedesktop.org/bbrezillon/linux/-/blob/8bec931a3a222873bf93cd8111d88c2f79df30cd/rust/kernel/sync/dma_fence.rs#L836

#33 lore raw

Re: [PATCH 3/4] rust: Add dma_fence abstractions

Quoted 234 lines · Philipp Stanner
Quoted 185 lines · Boris Brezillon
Quoted 162 lines · On Wed, 03 Jun 2026 08:10:51 +0200 Philipp Stanner <[email protected]> wrote:

+Cc Danilo, Gary, Daniel, Christian
(who seem to have been lost because Sashiko dropped them and this thread is a reply to that tool)

Quoted 148 lines · Alice Ryhl
Quoted 17 lines · Philipp Stanner
Quoted 1 line · Alice Ryhl

On

[…]

Quoted 12 lines
Quoted 11 lines
Quoted 4 lines

If you don't implement Sync, then DriverFence cannot be stored in an Arc. I wouldn't take away that ability unless you have to, and I don't see anything in the DriverFence API that would mean you can't do that.

Nope. We explicitly agreed on this design.

Just 1 DriverFence. Just 1 party that can signal it.
Note that we also agreed upon the Driverfence disappearing with .signal(), which certainly prevents several from existing, unless you do an Option.take()

Quoted 5 lines
Quoted 3 lines

I would like to clarify that I'm not suggesting any changes to the design. Implementing Sync is not the same as having multiple driver fences.

I mean, I guess one can do that. But it's up to the driver then to see how it can signal its fence.

I don't believe Sync changes anything with that regard. The signal method takes 'self', but the Sync trait only affects how '&self' methods can be called.

Quoted 120 lines
Quoted 76 lines
Quoted 76 lines
Quoted 36 lines
Quoted 36 lines
Quoted 5 lines
Quoted 5 lines
Quoted 4 lines

 so even though
the fence context may be valid for another grace period, the *pointer* to the fence context is not. The pointer could have been zeroed by the destructor.

Quoted 8 lines
Quoted 4 lines

That particular pointer to the DriverFenceData could have been zeroed. But potential other accessors have already crafted themselves a new pointer to the, by the power of RCU, still valid data. That new pointer is container-of-ed from struct dma_fence *f.

I'm not talking about the pointer to DriverFenceData, I'm talking about the pointer to the FenceCtx, or the pointer to the data (if F is RcuBox).

Yeah, but the backing memory is still alive. And new pointers to that memory get crafted by the accessors. If a callback accesses the data through container_of(Fence), it gets a new pointer.

So what's the problem?

Where is the invalid pointer that someone is accessing?

Quoted 4 lines

The Arc type is not a type that opts-out of &mut == exclusive, so the second drop_in_place() above is assumed exclusive access to the Arc<FenceCtx<F,C>> field.

OK, so I think I see the problem. So the invalid pointer is Arc<FenceCtx…>? And potentially the <F> pointer (although we don't have a picture yet as to how that would be accessed through other callbacks.

Quoted 4 lines

 If another thread obtains a pointer to the
FenceCtx via reading the fctx field of the DriverFence in parallel with this, then that's not allowed because the drop_in_place() call has exclusive access to that field.

Quoted 7 lines
Quoted 4 lines

I think I have been asking in several of our meetings in the past whether it is actually a problem to access data that has been dropped() IF we know that drop does not cause UAF and the answer was kind of like a "well if it does not actually get freed…"

Ok, well, IMO the simplest approach is to say you can't. There may be roundabout ways to do it, but I would suggest that we just ... don't.

Ack.

Quoted 29 lines

Quoted 8 lines

Anyways.

It would seem the way to get this right is then

synchronize_rcu();
drop_in_palace(data);

Quoted 7 lines

Agreed?

This would then mean, however, that every time a fence drops, you have to wait a grace period.

Or maybe stuff DriverFenceData into an RcuBox, too, and defer its dropping.

That would work, but I think we can do better and avoid the synchronize_rcu() along these lines:

unsafe trait RcuRevocable {
    unsafe fn rcu_revoke_in_place(ptr: *mut Self); }

This trait provides a method that's like drop_in_place(), except that when you use this destructor, the value remains usable for one grace period. You could implement it for RcuBox, and for any Copy type, and for ARef<T> when T is cleaned up with rcu, and probably also other stuff.

Quoted 8 lines
Quoted 5 lines

I mean, this cannot be magic. It also boils down to executing one RCU callback per DriverFence dropping.

Is there a significant difference to stuffing DriverFenceData into an RcuBox?

Do you mean hard-coding that the user-data of a driver fence is always stored in an RcuBox?

I'm talking about this:

impl<F: Send + Sync + DriverFenceAllowedData, C: Send + Sync> DriverFenceAllocation<F, C> { /// Create a new allocation slot that can later be used to create a fully /// initialized [DriverFence] without the need to allocate.
    pub fn new(fctx: Arc<FenceCtx<F, C>>, data: F) -> Result<Self> {
        let fence_data = DriverFenceData {
            // inner remains uninitialized until a [DriverFence] takes over.
            inner: Fence {
                inner: Opaque::uninit(),
            },
            fctx,
            data,
        };

        // In order to support the C dma_fence callbacks, it is necessary for // a Fence and a DriverFence to live in the same allocation, // because the C backend passes a dma_fence, from which the driver most // likely wants to be able to access its data in DriverFence. //
        // Hence, we need the manage the memory manually. It will be freed by the // C backend automatically once the refcount within Fence drops to 0. let data = RcuBox::new(fence_data, GFP_KERNEL | __GFP_ZERO)?;

        Ok(Self { data })
    }

This way, the entire DriverFenceData will remain valid for an additional grace period. I suppose this would solve your pointer- invalid concern.

Quoted 8 lines
Quoted 3 lines

However, it appears like overkill to me because the refcounting + C backend already ensure that nothing drops too soon, and the backend frees with kfree_rcu(), so…

I agree that it doesn't sound like we want RcuBox here.

What kind of metadata are we actually planning to store in the DriverFence in practice?

Quoted 6 lines

I suppose it might be GPUVM payloads. IIRC in Tyr it's currently just empty structs as some sort of identifier. I guess copies of the actual command buffers will also reside in their associated fences.

But Boris, Daniel and Danilo probably have something smart to say about that.

In Tyr we don't need any, because we're using SW signalling: we get an interrupt, look at our HW fence seqno, and walk the set of pending fences to signal them with the ::signal() method (that's an over-simplified view of it, but functionally that's how it works).

Now, the question is more, what drivers planning to support HW signaling want attached to the DriverFence. My gut feeling is that now that we have a way to get back to the FenceCtx, and given that FenceCtx also has driver-specific data attached to it, most common use cases where the fence contains a backpointer to its timeline/creator are covered. If I look at nouveau_fence [1] (I intentionally looked at the most likely next user of this) for instance:

Quoted 8 lines
struct nouveau_fence {
	struct dma_fence base;

	struct list_head head;

	struct nouveau_channel __rcu *channel;
	unsigned long timeout;
};

Thx for the explanation

Quoted 16 lines

- channel is basically the FenceCtx in your new design
- head is used to insert the fence in the fence context list. Not too sure how it translates in rust (insertion in an XArray instead?). Anyway, that's just stuff you need to make your fence part of the context timeline, so it's again related to how you link a fence to its context and let the context walk back pending fences when it needs to. Feels like my original FenceTimeline abstract proposal [2] could help make this thing generic, but let's reconsider this once we've got the basics sorted out, please
- timeout is probably the only thing of interest here, assuming things can be signaled out of order (if they are signaled in order, like is the case in Tyr, the timeout can just be on the FenceCtxData and represent the timeout of the first fence in the set). Anyway, that's a scalar, so no fancy ::drop() needed if that's what you're worried about

What we / Alice are worried about is multiple things.

One issue is that Alice believes that

        // SAFETY: self.data is owned by the DriverFence, but could be accessed // through some dma_fence callbacks right now. Access is being revoked // above by signalling the fence. The DriverFenceSafeToDrop trait // ensures that the data either does not need drop, or if it does it // lives in a RcuBox which will delay dropping by one grace period, hence // ensuring that all readers have disappeared.
        unsafe { drop_in_place(self.data.as_ptr()) };

from DriverFence::drop() invalidates the pointers that thread A accesses, even if DriverFence::data.data's drop is delayed with RCU.

Now if thread B drops DriverFence, thread A could use invalid pointers, but while still operating on non-deallocated memory (because our refcounting guards against that).

Quoted 7 lines

The only way I can see to make that bullet proof would be to do synchronize_rcu() before the drop_in_place() above – but that would mean that we delay each fence drop by one grace period.

I'm still not sure how real the problem really is, though. Because refcounting guards, and drop() doesn't actually *do* something to the pointers, or does it?

Re: this, brainstorming a bit:

An idea that has been floating in my head for a while is whether Revocable might be a good choice for a job like this.

The reason against it is that we already *have* a Revocable: the dma_fence. It guards with an atomic boolean (signaled bit) + RCU, just like Revocable.

I think it might give the formal protection that Alice desires, but then we'd have the same mechanism twice.

P.

Quoted 22 lines

--

Another, related issue would be
- thread A accesses DriverFence::data through a backend_ops
- thread B drops DriverFence
- there are no RCU callbacks (call_rcu()) pending
- thus, the rcu_barrier() in FenceCtx::drop() takes no effect. We would actually need synchronize_rcu() there.

So would seem we still didn't get fence and fence_ctx teardown completely right.

P.

Quoted 4 lines

[1]https://elixir.bootlin.com/linux/v7.1-rc5/source/drivers/gpu/drm/nouveau/nouveau_fence.h#L11 [2]https://gitlab.freedesktop.org/bbrezillon/linux/-/blob/8bec931a3a222873bf93cd8111d88c2f79df30cd/rust/kernel/sync/dma_fence.rs#L836

maintainer
#34 lore raw

Re: [PATCH 3/4] rust: Add dma_fence abstractions

Quoted 250 lines · Philipp Stanner
Quoted 234 lines · Philipp Stanner
Quoted 185 lines · Boris Brezillon
Quoted 162 lines · On Wed, 03 Jun 2026 08:10:51 +0200 Philipp Stanner <[email protected]> wrote:

+Cc Danilo, Gary, Daniel, Christian
(who seem to have been lost because Sashiko dropped them and this thread is a reply to that tool)

Quoted 148 lines · Alice Ryhl
Quoted 17 lines · Philipp Stanner
Quoted 1 line · Alice Ryhl

On

[…]

Quoted 12 lines
Quoted 11 lines
Quoted 4 lines

If you don't implement Sync, then DriverFence cannot be stored in an Arc. I wouldn't take away that ability unless you have to, and I don't see anything in the DriverFence API that would mean you can't do that.

Nope. We explicitly agreed on this design.

Just 1 DriverFence. Just 1 party that can signal it.
Note that we also agreed upon the Driverfence disappearing with .signal(), which certainly prevents several from existing, unless you do an Option.take()

Quoted 5 lines
Quoted 3 lines

I would like to clarify that I'm not suggesting any changes to the design. Implementing Sync is not the same as having multiple driver fences.

I mean, I guess one can do that. But it's up to the driver then to see how it can signal its fence.

I don't believe Sync changes anything with that regard. The signal method takes 'self', but the Sync trait only affects how '&self' methods can be called.

Quoted 120 lines
Quoted 76 lines
Quoted 76 lines
Quoted 36 lines
Quoted 36 lines
Quoted 5 lines
Quoted 5 lines
Quoted 4 lines

so even though
the fence context may be valid for another grace period, the *pointer*
to the fence context is not. The pointer could have been zeroed by the
destructor.

Quoted 8 lines
Quoted 4 lines

That particular pointer to the DriverFenceData could have been zeroed. But potential other accessors have already crafted themselves a new pointer to the, by the power of RCU, still valid data. That new pointer is container-of-ed from struct dma_fence *f.

I'm not talking about the pointer to DriverFenceData, I'm talking about the pointer to the FenceCtx, or the pointer to the data (if F is RcuBox).

Yeah, but the backing memory is still alive. And new pointers to that memory get crafted by the accessors. If a callback accesses the data through container_of(Fence), it gets a new pointer.

So what's the problem?

Where is the invalid pointer that someone is accessing?

Quoted 4 lines

The Arc type is not a type that opts-out of &mut == exclusive, so the second drop_in_place() above is assumed exclusive access to the Arc<FenceCtx<F,C>> field.

OK, so I think I see the problem. So the invalid pointer is Arc<FenceCtx…>? And potentially the <F> pointer (although we don't have a picture yet as to how that would be accessed through other callbacks.

Quoted 4 lines

If another thread obtains a pointer to the
FenceCtx via reading the fctx field of the DriverFence in parallel with this, then that's not allowed because the drop_in_place() call has exclusive access to that field.

Quoted 7 lines
Quoted 4 lines

I think I have been asking in several of our meetings in the past whether it is actually a problem to access data that has been dropped() IF we know that drop does not cause UAF and the answer was kind of like a "well if it does not actually get freed…"

Ok, well, IMO the simplest approach is to say you can't. There may be roundabout ways to do it, but I would suggest that we just ... don't.

Ack.

Quoted 29 lines
Quoted 9 lines

Anyways.

It would seem the way to get this right is then

synchronize_rcu();
drop_in_palace(data);

Quoted 7 lines

Agreed?

This would then mean, however, that every time a fence drops, you have to wait a grace period.

Or maybe stuff DriverFenceData into an RcuBox, too, and defer its dropping.

That would work, but I think we can do better and avoid the synchronize_rcu() along these lines:

unsafe trait RcuRevocable {
    unsafe fn rcu_revoke_in_place(ptr: *mut Self);
}

This trait provides a method that's like drop_in_place(), except that when you use this destructor, the value remains usable for one grace period. You could implement it for RcuBox, and for any Copy type, and for ARef<T> when T is cleaned up with rcu, and probably also other stuff.

Quoted 8 lines
Quoted 5 lines

I mean, this cannot be magic. It also boils down to executing one RCU callback per DriverFence dropping.

Is there a significant difference to stuffing DriverFenceData into an RcuBox?

Do you mean hard-coding that the user-data of a driver fence is always stored in an RcuBox?

I'm talking about this:

impl<F: Send + Sync + DriverFenceAllowedData, C: Send + Sync> DriverFenceAllocation<F, C> {
    /// Create a new allocation slot that can later be used to create a fully
    /// initialized [`DriverFence`] without the need to allocate.
    pub fn new(fctx: Arc<FenceCtx<F, C>>, data: F) -> Result<Self> {
        let fence_data = DriverFenceData {
            // `inner` remains uninitialized until a [`DriverFence`] takes over.
            inner: Fence {
                inner: Opaque::uninit(),
            },
            fctx,
            data,
        };

        // In order to support the C dma_fence callbacks, it is necessary for
        // a `Fence` and a `DriverFence` to live in the same allocation,
        // because the C backend passes a dma_fence, from which the driver most
        // likely wants to be able to access its `data` in `DriverFence`.
        //
        // Hence, we need the manage the memory manually. It will be freed by the
        // C backend automatically once the refcount within `Fence` drops to 0.
        let data = RcuBox::new(fence_data, GFP_KERNEL | __GFP_ZERO)?;

        Ok(Self { data })
    }

This way, the entire DriverFenceData will remain valid for an additional grace period. I suppose this would solve your pointer- invalid concern.

Quoted 8 lines
Quoted 3 lines

However, it appears like overkill to me because the refcounting + C backend already ensure that nothing drops too soon, and the backend frees with kfree_rcu(), so…

I agree that it doesn't sound like we want RcuBox here.

What kind of metadata are we actually planning to store in the DriverFence in practice?

Quoted 6 lines

I suppose it might be GPUVM payloads. IIRC in Tyr it's currently just empty structs as some sort of identifier. I guess copies of the actual command buffers will also reside in their associated fences.

But Boris, Daniel and Danilo probably have something smart to say about that.

In Tyr we don't need any, because we're using SW signalling: we get an interrupt, look at our HW fence seqno, and walk the set of pending fences to signal them with the ::signal() method (that's an over-simplified view of it, but functionally that's how it works).

Now, the question is more, what drivers planning to support HW signaling want attached to the DriverFence. My gut feeling is that now that we have a way to get back to the FenceCtx, and given that FenceCtx also has driver-specific data attached to it, most common use cases where the fence contains a backpointer to its timeline/creator are covered. If I look at nouveau_fence [1] (I intentionally looked at the most likely next user of this) for instance:

Quoted 8 lines
struct nouveau_fence {
    struct dma_fence base;

    struct list_head head;

    struct nouveau_channel __rcu *channel;
    unsigned long timeout;
};

Thx for the explanation

Quoted 16 lines

- channel is basically the FenceCtx in your new design
- head is used to insert the fence in the fence context list. Not too sure how it translates in rust (insertion in an XArray instead?). Anyway, that's just stuff you need to make your fence part of the context timeline, so it's again related to how you link a fence to its context and let the context walk back pending fences when it needs to. Feels like my original FenceTimeline abstract proposal [2] could help make this thing generic, but let's reconsider this once we've got the basics sorted out, please
- timeout is probably the only thing of interest here, assuming things can be signaled out of order (if they are signaled in order, like is the case in Tyr, the timeout can just be on the FenceCtxData and represent the timeout of the first fence in the set). Anyway, that's a scalar, so no fancy ::drop() needed if that's what you're worried about

What we / Alice are worried about is multiple things.

One issue is that Alice believes that

        // SAFETY: `self.data` is owned by the DriverFence, but could be accessed
        // through some dma_fence callbacks right now. Access is being revoked
        // above by signalling the fence. The DriverFenceSafeToDrop trait
        // ensures that the data either does not need drop, or if it does it
        // lives in a RcuBox which will delay dropping by one grace period, hence
        // ensuring that all readers have disappeared.
        unsafe { drop_in_place(self.data.as_ptr()) };

from DriverFence::drop() invalidates the pointers that thread A accesses, even if DriverFence::data.data's drop is delayed with RCU.

Now if thread B drops DriverFence, thread A could use invalid pointers, but while still operating on non-deallocated memory (because our refcounting guards against that).

Quoted 7 lines

The only way I can see to make that bullet proof would be to do synchronize_rcu() before the drop_in_place() above – but that would mean that we delay each fence drop by one grace period.

I'm still not sure how real the problem really is, though. Because refcounting guards, and drop() doesn't actually *do* something to the pointers, or does it?

Re: this, brainstorming a bit:

An idea that has been floating in my head for a while is whether Revocable might be a good choice for a job like this.

Quoted 6 lines

The reason against it is that we already *have* a Revocable: the dma_fence. It guards with an atomic boolean (signaled bit) + RCU, just like Revocable.

I think it might give the formal protection that Alice desires, but then we'd have the same mechanism twice.

We could also just start with only supporting types that are Copy. That'd be really simple because no drop is required.

Alice

#10 lore raw

Re: [PATCH 2/4] rust: rcu: add RcuBox type

In reply to Boqun Feng
Quoted 38 lines · Boqun Feng
Quoted 37 lines · Philipp Stanner
Quoted 12 lines · Boqun Feng
Quoted 10 lines · Philipp Stanner

From: Alice Ryhl <[email protected]>

This adds an RcuBox container, which is like KBox except that the value is freed with kfree_rcu.

To allow containers to rely on the rcu properties of RcuBox, an extension of ForeignOwnable is added.

---

Quoted 6 lines

I have the following on top of Alice's patch. @Alice, @Danilo, thoughts?

Then we can have:

type RcuKBox<T> = RcuBox<T, Kmalloc>;
type RcuVBox<T> = RcuBox<T, Vmalloc>;

No objections by me.

I just think we have to decide how the treat the namespaces, though. Probably Alice wrote it like that so that it's very apparent that this is not a normal box. It still breaks the naming convention in my opinion.

rcu::Box vs rcu::RcuBox

With all other subsystems, naming like that seems not allowed.

dma::Fence vs dma::DmaFence

I probably would allow the user to decide whether he wants to just use it as rcu::Box in all his code.

Quoted 8 lines
Quoted 2 lines

But no hard feelings.

For this I think that rcu::RcuBox is a bit different than dma::Fence, because Box has its widely-accepted meaning through all Rust code, while Fence doesn't. Hence my current thought is rcu::RcuBox and dma::Fence. My personal preference is using namespace as much as we could until there might be some misleading.

Yoah, probably better we're safer rather than hyper-consistent.

Quoted 7 lines
Quoted 7 lines
Quoted 6 lines

and Philipp can use the RcuKBox in this patchset. We also need to impl InPlaceInit for RcuBox, but that can be added later.

Quoted 7 lines
Quoted 4 lines

So shall we merge my series with Alice's patch, and later we add your patch and other features, or would you prefer to have the additional boxes from your patch from the get-go?

I would like to have it from the get-go mainly because of RcuBox vs RcuKBox naming. Thank you!

Fine by me. Just process-wise: how should we do it?

I could include your patch on top of Alice's. Would be a bit more consistent regarding the git-workflow if we'd squash the two patches, but then you two would have to agree on authorship.

All is fine by me, but I wanted to ask instead of just do A or B.

P.

Quoted 222 lines

Regards,
Boqun

Quoted 218 lines

P.

Quoted 215 lines

Regards,
Boqun

------------->8
Subject: [PATCH] rust: rcu: Make RcuBox generic over Allocator

To support RCU-protected vmalloc allocation, we need to make RcuBox generic over Allocator. Currently this works since all Allocators are either kmalloc() or vmalloc(), and kvfree_call_rcu() works with both allocations.

While we are at it, add some basic test cases.

---
 rust/kernel/sync/rcu/rcu_box.rs | 96 +++++++++++++++++++++++----------

 1 file changed, 67 insertions(+), 29 deletions(-)
rust/kernel/sync/rcu/rcu_box.rs +0 -0
index 2508fdb609ec..5c344d82c0d9 100644
@@ -4,47 +4,59 @@


 //! Provides the RcuBox type for Rust allocations that live for a grace period.

rust/kernel/sync/rcu/rcu_box.rs +5 -1
-use core::{ops::Deref, ptr::NonNull};
+use core::{
+    marker::PhantomData,
+    ops::Deref,
+    ptr::NonNull, //
+};


 use kernel::{
-    alloc::{self, AllocError},
+    alloc::{
+        self,
+        AllocError,
+        Allocator, //
+    },
     bindings,
     ffi::c_void,
     prelude::*,
-    sync::rcu::{ForeignOwnableRcu, Guard},
     types::ForeignOwnable,
 };

rust/kernel/sync/rcu/rcu_box.rs +5 -0
+use super::{
+    ForeignOwnableRcu,
+    Guard, //
+};
+

 /// A box that is freed with rcu.
 ///
 /// The value must be Send, as rcu may drop it on another thread. ///
 /// # Invariants
 ///

rust/kernel/sync/rcu/rcu_box.rs +1 -1
-/// * The pointer is valid and references a pinned `RcuBoxInner<T>` allocated with `kmalloc`.
+/// * The pointer is valid and references a pinned `RcuBoxInner<T>` allocated with `A`.

 /// * This RcuBox holds exclusive permissions to rcu free the allocation.

rust/kernel/sync/rcu/rcu_box.rs +1 -1
-pub struct RcuBox<T: Send>(NonNull<RcuBoxInner<T>>);
+pub struct RcuBox<T: Send, A: Allocator>(NonNull<RcuBoxInner<T>>, PhantomData<A>);


 struct RcuBoxInner<T> {
     value: T,
     rcu_head: bindings::callback_head,
 }

rust/kernel/sync/rcu/rcu_box.rs +2 -2
-// Note that `T: Sync` is required since when moving an `RcuBox<T>`, the previous owner may still
-// access `&T` for one grace period.
+// Note that `T: Sync` is required since when moving an `RcuBox<T, A>`, the previous owner may
+// still access `&T` for one grace period.

 //

rust/kernel/sync/rcu/rcu_box.rs +3 -3
-// SAFETY: Ownership of the `RcuBox<T>` allows for `&T` and dropping the `T`, so `T: Send + Sync`
-// implies `RcuBox<T>: Send`.
-unsafe impl<T: Send + Sync> Send for RcuBox<T> {}
+// SAFETY: Ownership of the `RcuBox<T, A>` allows for `&T` and dropping the `T`, so `T: Send +
+// Sync` implies `RcuBox<T, A>: Send`.
+unsafe impl<T: Send + Sync, A: Allocator> Send for RcuBox<T, A> {}

rust/kernel/sync/rcu/rcu_box.rs +3 -3
-// SAFETY: `&RcuBox<T>` allows for no operations other than those permitted by `&T`, so `T: Sync`
-// implies `RcuBox<T>: Sync`.
-unsafe impl<T: Send + Sync> Sync for RcuBox<T> {}
+// SAFETY: `&RcuBox<T, A>` allows for no operations other than those permitted by `&T`, so `T:
+// Sync` implies `RcuBox<T, A>: Sync`.
+unsafe impl<T: Send + Sync, A: Allocator> Sync for RcuBox<T, A> {}

rust/kernel/sync/rcu/rcu_box.rs +1 -1
-impl<T: Send> RcuBox<T> {
+impl<T: Send, A: Allocator> RcuBox<T, A> {

     /// Create a new RcuBox.
     pub fn new(x: T, flags: alloc::Flags) -> Result<Self, AllocError> { -        let b = KBox::new(
+        let b = Box::<_, A>::new(
             RcuBoxInner {
                 value: x,
                 rcu_head: Default::default(),

rust/kernel/sync/rcu/rcu_box.rs +0 -0
@@ -53,9 +65,9 @@ pub fn new(x: T, flags: alloc::Flags) -> Result<Self, AllocError> {
          )?;


         // INVARIANT:
-        // * The pointer contains a valid RcuBoxInner allocated with kmalloc. +        // * The pointer contains a valid RcuBoxInner allocated with A. // * We just allocated it, so we own free permissions.
-        Ok(RcuBox(NonNull::from(KBox::leak(b))))
+        Ok(RcuBox(NonNull::from(Box::leak(b)), PhantomData))
     }

     /// Access the value for a grace period.

rust/kernel/sync/rcu/rcu_box.rs +0 -0
@@ -66,7 +78,7 @@ pub fn with_rcu<'rcu>(&self, _read_guard: &'rcu Guard) -> &'rcu T {
      }
  }

rust/kernel/sync/rcu/rcu_box.rs +1 -1
-impl<T: Send> Deref for RcuBox<T> {
+impl<T: Send, A: Allocator> Deref for RcuBox<T, A> {

     type Target = T;
     fn deref(&self) -> &T {
         // SAFETY: While the RcuBox<T> exists, the value remains valid.

rust/kernel/sync/rcu/rcu_box.rs +0 -0
@@ -75,10 +87,10 @@ fn deref(&self) -> &T {
  }


 // SAFETY:

rust/kernel/sync/rcu/rcu_box.rs +1 -1
-// * The `RcuBoxInner<T>` was allocated with `kmalloc`.
+// * The `RcuBoxInner<T>` was allocated with `A`.

 // * NonNull::as_ptr returns a non-null pointer.

rust/kernel/sync/rcu/rcu_box.rs +2 -2
-unsafe impl<T: Send + 'static> ForeignOwnable for RcuBox<T> {
-    const FOREIGN_ALIGN: usize = <KBox<RcuBoxInner<T>> as ForeignOwnable>::FOREIGN_ALIGN;
+unsafe impl<T: Send + 'static, A: Allocator> ForeignOwnable for RcuBox<T, A> {
+    const FOREIGN_ALIGN: usize = <Box<RcuBoxInner<T>, A> as ForeignOwnable>::FOREIGN_ALIGN;


     type Borrowed<'a> = &'a T;
     type BorrowedMut<'a> = &'a T;

rust/kernel/sync/rcu/rcu_box.rs +0 -0
@@ -88,9 +100,9 @@ fn into_foreign(self) -> *mut c_void {
      }


     unsafe fn from_foreign(ptr: *mut c_void) -> Self {
-        // INVARIANT: Pointer returned by into_foreign carries same invariants as RcuBox<T>. +        // INVARIANT: Pointer returned by into_foreign, A carries same invariants as RcuBox<T>. // SAFETY: into_foreign never returns a null pointer.
-        Self(unsafe { NonNull::new_unchecked(ptr.cast()) })
+        Self(unsafe { NonNull::new_unchecked(ptr.cast()) }, PhantomData)
     }

     unsafe fn borrow<'a>(ptr: *mut c_void) -> &'a T {

rust/kernel/sync/rcu/rcu_box.rs +0 -0
@@ -104,7 +116,7 @@ unsafe fn borrow_mut<'a>(ptr: *mut c_void) -> &'a T {
      }
  }

rust/kernel/sync/rcu/rcu_box.rs +1 -1
-impl<T: Send + 'static> ForeignOwnableRcu for RcuBox<T> {
+impl<T: Send + 'static, A: Allocator> ForeignOwnableRcu for RcuBox<T, A> {

     type RcuBorrowed<'a> = &'a T;

     unsafe fn rcu_borrow<'a>(ptr: *mut c_void) -> &'a T {

rust/kernel/sync/rcu/rcu_box.rs +0 -0
@@ -114,7 +126,7 @@ unsafe fn rcu_borrow<'a>(ptr: *mut c_void) -> &'a T {
      }
  }

rust/kernel/sync/rcu/rcu_box.rs +1 -1
-impl<T: Send> Drop for RcuBox<T> {
+impl<T: Send, A: Allocator> Drop for RcuBox<T, A> {

     fn drop(&mut self) {
         // SAFETY: The rcu_head field is in-bounds of a valid allocation. let rcu_head = unsafe { &raw mut (*self.0.as_ptr()).rcu_head };

rust/kernel/sync/rcu/rcu_box.rs +5 -3
@@ -122,9 +134,11 @@ fn drop(&mut self) {
              // SAFETY: `rcu_head` is the `rcu_head` field of `RcuBoxInner<T>`. All users will be
              // gone in an rcu grace period. This is the destructor, so we may pass ownership of the
              // allocation.
-            unsafe { bindings::call_rcu(rcu_head, Some(drop_rcu_box::<T>)) };
+            unsafe { bindings::call_rcu(rcu_head, Some(drop_rcu_box::<T, A>)) };
          } else {
              // SAFETY: All users will be gone in an rcu grace period.
+            // TODO: We are luckily since `kvfree_call_rcu()` works on both kmalloc and vmalloc,
+            // maybe a new `Allocator` method is needed.
              unsafe { bindings::kvfree_call_rcu(rcu_head, self.0.as_ptr().cast()) };
          }
      }
@@ -135,11 +149,35 @@ fn drop(&mut self) {
  /// # Safety
  ///
  /// `head` references the `rcu_head` field of an `RcuBoxInner<T>` that has no references to it.
-/// Ownership of the `KBox<RcuBoxInner<T>>` must be passed.
-unsafe extern "C" fn drop_rcu_box<T>(head: *mut bindings::callback_head) {
+/// Ownership of the `Box<RcuBoxInner<T>, A>` must be passed.
+unsafe extern "C" fn drop_rcu_box<T, A: Allocator>(head: *mut bindings::callback_head) {
      // SAFETY: Caller provides a pointer to the `rcu_head` field of a `RcuBoxInner<T>`.
      let box_inner = unsafe { crate::container_of!(head, RcuBoxInner<T>, rcu_head) };


     // SAFETY: Caller ensures exclusive access and passed ownership. -    drop(unsafe { KBox::from_raw(box_inner) });
+    drop(unsafe { Box::<_, A>::from_raw(box_inner) });

rust/kernel/sync/rcu/rcu_box.rs +24 -0
+}
+
+#[kunit_tests(rust_rcu_box)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn rcu_box_basic() -> Result {
+        let rb = RcuBox::<_, alloc::allocator::Kmalloc>::new(42i32, alloc::flags::GFP_KERNEL)?;
+
+        assert_eq!(*rb, 42);
+        assert_eq!(*rb.with_rcu(&Guard::new()), 42);
+
+        drop(rb);
+
+        let rb = RcuBox::<_, alloc::allocator::Vmalloc>::new(42i32, alloc::flags::GFP_KERNEL)?;
+
+        assert_eq!(*rb, 42);
+        assert_eq!(*rb.with_rcu(&Guard::new()), 42);
+
+        drop(rb);
+
+        Ok(())
+    }

 }

maintainer
#11 lore raw

Re: [PATCH 2/4] rust: rcu: add RcuBox type

Quoted 66 lines · Philipp Stanner
Quoted 38 lines · Boqun Feng
Quoted 37 lines · Philipp Stanner
Quoted 12 lines · Boqun Feng
Quoted 10 lines · Philipp Stanner

From: Alice Ryhl <[email protected]>

This adds an RcuBox container, which is like KBox except that the value is freed with kfree_rcu.

To allow containers to rely on the rcu properties of RcuBox, an extension of ForeignOwnable is added.

---

Quoted 6 lines

I have the following on top of Alice's patch. @Alice, @Danilo, thoughts?

Then we can have:

type RcuKBox<T> = RcuBox<T, Kmalloc>;
type RcuVBox<T> = RcuBox<T, Vmalloc>;

No objections by me.

I just think we have to decide how the treat the namespaces, though. Probably Alice wrote it like that so that it's very apparent that this is not a normal box. It still breaks the naming convention in my opinion.

rcu::Box vs rcu::RcuBox

With all other subsystems, naming like that seems not allowed.

dma::Fence vs dma::DmaFence

I probably would allow the user to decide whether he wants to just use it as rcu::Box in all his code.

Quoted 8 lines
Quoted 2 lines

But no hard feelings.

For this I think that rcu::RcuBox is a bit different than dma::Fence, because Box has its widely-accepted meaning through all Rust code, while Fence doesn't. Hence my current thought is rcu::RcuBox and dma::Fence. My personal preference is using namespace as much as we could until there might be some misleading.

Yoah, probably better we're safer rather than hyper-consistent.

Quoted 7 lines
Quoted 7 lines
Quoted 6 lines

and Philipp can use the RcuKBox in this patchset. We also need to impl InPlaceInit for RcuBox, but that can be added later.

Quoted 7 lines
Quoted 4 lines

So shall we merge my series with Alice's patch, and later we add your patch and other features, or would you prefer to have the additional boxes from your patch from the get-go?

I would like to have it from the get-go mainly because of RcuBox vs RcuKBox naming. Thank you!

Quoted 7 lines

Fine by me. Just process-wise: how should we do it?

I could include your patch on top of Alice's. Would be a bit more consistent regarding the git-workflow if we'd squash the two patches, but then you two would have to agree on authorship.

All is fine by me, but I wanted to ask instead of just do A or B.

Squashing is fine with me, thanks.

Alice

#35 lore raw

Re: [PATCH 3/4] rust: Add dma_fence abstractions

In reply to Alice Ryhl
Quoted 255 lines · Alice Ryhl
Quoted 254 lines · Philipp Stanner
Quoted 234 lines · Philipp Stanner
Quoted 185 lines · Boris Brezillon
Quoted 162 lines · On Wed, 03 Jun 2026 08:10:51 +0200 Philipp Stanner <[email protected]> wrote:

+Cc Danilo, Gary, Daniel, Christian
(who seem to have been lost because Sashiko dropped them and this thread is a reply to that tool)

Quoted 148 lines · Alice Ryhl
Quoted 17 lines · Philipp Stanner
Quoted 1 line · Alice Ryhl

On

[…]

Quoted 12 lines
Quoted 11 lines
Quoted 4 lines

If you don't implement Sync, then DriverFence cannot be stored in an Arc. I wouldn't take away that ability unless you have to, and I don't see anything in the DriverFence API that would mean you can't do that.

Nope. We explicitly agreed on this design.

Just 1 DriverFence. Just 1 party that can signal it.
Note that we also agreed upon the Driverfence disappearing with .signal(), which certainly prevents several from existing, unless you do an Option.take()

Quoted 5 lines
Quoted 3 lines

I would like to clarify that I'm not suggesting any changes to the design. Implementing Sync is not the same as having multiple driver fences.

I mean, I guess one can do that. But it's up to the driver then to see how it can signal its fence.

I don't believe Sync changes anything with that regard. The signal method takes 'self', but the Sync trait only affects how '&self' methods can be called.

Quoted 120 lines
Quoted 76 lines
Quoted 76 lines
Quoted 36 lines
Quoted 36 lines
Quoted 5 lines
Quoted 5 lines

 so even though
the fence context may be valid for another grace period, the *pointer*
to the fence context is not. The pointer could have been zeroed by the
destructor.

Quoted 8 lines
Quoted 4 lines

That particular pointer to the DriverFenceData could have been zeroed.
But potential other accessors have already crafted themselves a new
pointer to the, by the power of RCU, still valid data. That new pointer
is container-of-ed from struct dma_fence *f.

I'm not talking about the pointer to DriverFenceData, I'm talking about the pointer to the FenceCtx, or the pointer to the data (if F is RcuBox).

Yeah, but the backing memory is still alive. And new pointers to that memory get crafted by the accessors. If a callback accesses the data through container_of(Fence), it gets a new pointer.

So what's the problem?

Where is the invalid pointer that someone is accessing?

Quoted 4 lines

The Arc type is not a type that opts-out of &mut == exclusive, so the second drop_in_place() above is assumed exclusive access to the Arc<FenceCtx<F,C>> field.

OK, so I think I see the problem. So the invalid pointer is Arc<FenceCtx…>? And potentially the <F> pointer (although we don't have a picture yet as to how that would be accessed through other callbacks.

Quoted 4 lines

 If another thread obtains a pointer to the
FenceCtx via reading the fctx field of the DriverFence in parallel with this, then that's not allowed because the drop_in_place() call has exclusive access to that field.

Quoted 7 lines
Quoted 4 lines

I think I have been asking in several of our meetings in the past whether it is actually a problem to access data that has been dropped() IF we know that drop does not cause UAF and the answer was kind of like a "well if it does not actually get freed…"

Ok, well, IMO the simplest approach is to say you can't. There may be roundabout ways to do it, but I would suggest that we just ... don't.

Ack.

Quoted 29 lines
Quoted 9 lines

Anyways.

It would seem the way to get this right is then

synchronize_rcu();
drop_in_palace(data);

Quoted 7 lines

Agreed?

This would then mean, however, that every time a fence drops, you have to wait a grace period.

Or maybe stuff DriverFenceData into an RcuBox, too, and defer its dropping.

That would work, but I think we can do better and avoid the synchronize_rcu() along these lines:

unsafe trait RcuRevocable {
    unsafe fn rcu_revoke_in_place(ptr: *mut Self); }

This trait provides a method that's like drop_in_place(), except that when you use this destructor, the value remains usable for one grace period. You could implement it for RcuBox, and for any Copy type, and for ARef<T> when T is cleaned up with rcu, and probably also other stuff.

Quoted 8 lines
Quoted 5 lines

I mean, this cannot be magic. It also boils down to executing one RCU callback per DriverFence dropping.

Is there a significant difference to stuffing DriverFenceData into an RcuBox?

Do you mean hard-coding that the user-data of a driver fence is always stored in an RcuBox?

I'm talking about this:

impl<F: Send + Sync + DriverFenceAllowedData, C: Send + Sync> DriverFenceAllocation<F, C> { /// Create a new allocation slot that can later be used to create a fully /// initialized [DriverFence] without the need to allocate.
    pub fn new(fctx: Arc<FenceCtx<F, C>>, data: F) -> Result<Self> {
        let fence_data = DriverFenceData {
            // inner remains uninitialized until a [DriverFence] takes over.
            inner: Fence {
                inner: Opaque::uninit(),
            },
            fctx,
            data,
        };

        // In order to support the C dma_fence callbacks, it is necessary for // a Fence and a DriverFence to live in the same allocation, // because the C backend passes a dma_fence, from which the driver most // likely wants to be able to access its data in DriverFence. //
        // Hence, we need the manage the memory manually. It will be freed by the // C backend automatically once the refcount within Fence drops to 0. let data = RcuBox::new(fence_data, GFP_KERNEL | __GFP_ZERO)?;

        Ok(Self { data })
    }

This way, the entire DriverFenceData will remain valid for an additional grace period. I suppose this would solve your pointer- invalid concern.

Quoted 8 lines
Quoted 3 lines

However, it appears like overkill to me because the refcounting + C backend already ensure that nothing drops too soon, and the backend frees with kfree_rcu(), so…

I agree that it doesn't sound like we want RcuBox here.

What kind of metadata are we actually planning to store in the DriverFence in practice?

Quoted 6 lines

I suppose it might be GPUVM payloads. IIRC in Tyr it's currently just empty structs as some sort of identifier. I guess copies of the actual command buffers will also reside in their associated fences.

But Boris, Daniel and Danilo probably have something smart to say about that.

In Tyr we don't need any, because we're using SW signalling: we get an interrupt, look at our HW fence seqno, and walk the set of pending fences to signal them with the ::signal() method (that's an over-simplified view of it, but functionally that's how it works).

Now, the question is more, what drivers planning to support HW signaling want attached to the DriverFence. My gut feeling is that now that we have a way to get back to the FenceCtx, and given that FenceCtx also has driver-specific data attached to it, most common use cases where the fence contains a backpointer to its timeline/creator are covered. If I look at nouveau_fence [1] (I intentionally looked at the most likely next user of this) for instance:

Quoted 8 lines

struct nouveau_fence {
    struct dma_fence base;

    struct list_head head;

    struct nouveau_channel __rcu *channel;
    unsigned long timeout;
};

Thx for the explanation

Quoted 16 lines

- channel is basically the FenceCtx in your new design
- head is used to insert the fence in the fence context list. Not too sure how it translates in rust (insertion in an XArray instead?). Anyway, that's just stuff you need to make your fence part of the context timeline, so it's again related to how you link a fence to its context and let the context walk back pending fences when it needs to. Feels like my original FenceTimeline abstract proposal [2] could help make this thing generic, but let's reconsider this once we've got the basics sorted out, please
- timeout is probably the only thing of interest here, assuming things can be signaled out of order (if they are signaled in order, like is the case in Tyr, the timeout can just be on the FenceCtxData and represent the timeout of the first fence in the set). Anyway, that's a scalar, so no fancy ::drop() needed if that's what you're worried about

What we / Alice are worried about is multiple things.

One issue is that Alice believes that

        // SAFETY: self.data is owned by the DriverFence, but could be accessed // through some dma_fence callbacks right now. Access is being revoked // above by signalling the fence. The DriverFenceSafeToDrop trait // ensures that the data either does not need drop, or if it does it // lives in a RcuBox which will delay dropping by one grace period, hence // ensuring that all readers have disappeared.
        unsafe { drop_in_place(self.data.as_ptr()) };

from DriverFence::drop() invalidates the pointers that thread A accesses, even if DriverFence::data.data's drop is delayed with RCU.

Now if thread B drops DriverFence, thread A could use invalid pointers, but while still operating on non-deallocated memory (because our refcounting guards against that).

Quoted 7 lines

The only way I can see to make that bullet proof would be to do synchronize_rcu() before the drop_in_place() above – but that would mean that we delay each fence drop by one grace period.

I'm still not sure how real the problem really is, though. Because refcounting guards, and drop() doesn't actually *do* something to the pointers, or does it?

Re: this, brainstorming a bit:

An idea that has been floating in my head for a while is whether Revocable might be a good choice for a job like this.

The reason against it is that we already *have* a Revocable: the dma_fence. It guards with an atomic boolean (signaled bit) + RCU, just like Revocable.

Quoted 5 lines
Quoted 2 lines

I think it might give the formal protection that Alice desires, but then we'd have the same mechanism twice.

We could also just start with only supporting types that are Copy. That'd be really simple because no drop is required.

Didn't you just point out that it's explicitly the fctx: Arc<FenceCtx> pointer that is becoming invalid?

That will remain the case, no matter what kind of payload generic data the user passes. These are layered, related but distinct issues. See get_timeline_name().

P.

Quoted 2 lines

Alice

#36 lore raw

Re: [PATCH 3/4] rust: Add dma_fence abstractions

In reply to Philipp Stanner
Quoted 227 lines · On Wed, 03 Jun 2026 09:43:55 +0200 Philipp Stanner <[email protected]> wrote:
Quoted 185 lines · Boris Brezillon
Quoted 162 lines · On Wed, 03 Jun 2026 08:10:51 +0200 Philipp Stanner <[email protected]> wrote:

+Cc Danilo, Gary, Daniel, Christian
(who seem to have been lost because Sashiko dropped them and this thread is a reply to that tool)

Quoted 148 lines · Alice Ryhl
Quoted 17 lines · Philipp Stanner
Quoted 1 line · Alice Ryhl

On

[…]

Quoted 12 lines
Quoted 11 lines
Quoted 4 lines

If you don't implement Sync, then DriverFence cannot be stored in an Arc. I wouldn't take away that ability unless you have to, and I don't see anything in the DriverFence API that would mean you can't do that.

Nope. We explicitly agreed on this design.

Just 1 DriverFence. Just 1 party that can signal it.
Note that we also agreed upon the Driverfence disappearing with .signal(), which certainly prevents several from existing, unless you do an Option.take()

Quoted 5 lines
Quoted 3 lines

I would like to clarify that I'm not suggesting any changes to the design. Implementing Sync is not the same as having multiple driver fences.

I mean, I guess one can do that. But it's up to the driver then to see how it can signal its fence.

I don't believe Sync changes anything with that regard. The signal method takes 'self', but the Sync trait only affects how '&self' methods can be called.

Quoted 120 lines
Quoted 76 lines
Quoted 76 lines
Quoted 36 lines
Quoted 36 lines
Quoted 5 lines
Quoted 5 lines
Quoted 4 lines

 so even though
the fence context may be valid for another grace period, the *pointer* to the fence context is not. The pointer could have been zeroed by the destructor.

Quoted 8 lines
Quoted 4 lines

That particular pointer to the DriverFenceData could have been zeroed. But potential other accessors have already crafted themselves a new pointer to the, by the power of RCU, still valid data. That new pointer is container-of-ed from struct dma_fence *f.

I'm not talking about the pointer to DriverFenceData, I'm talking about the pointer to the FenceCtx, or the pointer to the data (if F is RcuBox).

Yeah, but the backing memory is still alive. And new pointers to that memory get crafted by the accessors. If a callback accesses the data through container_of(Fence), it gets a new pointer.

So what's the problem?

Where is the invalid pointer that someone is accessing?

Quoted 4 lines

The Arc type is not a type that opts-out of &mut == exclusive, so the second drop_in_place() above is assumed exclusive access to the Arc<FenceCtx<F,C>> field.

OK, so I think I see the problem. So the invalid pointer is Arc<FenceCtx…>? And potentially the <F> pointer (although we don't have a picture yet as to how that would be accessed through other callbacks.

Quoted 4 lines

 If another thread obtains a pointer to the
FenceCtx via reading the fctx field of the DriverFence in parallel with this, then that's not allowed because the drop_in_place() call has exclusive access to that field.

Quoted 7 lines
Quoted 4 lines

I think I have been asking in several of our meetings in the past whether it is actually a problem to access data that has been dropped() IF we know that drop does not cause UAF and the answer was kind of like a "well if it does not actually get freed…"

Ok, well, IMO the simplest approach is to say you can't. There may be roundabout ways to do it, but I would suggest that we just ... don't.

Ack.

Quoted 29 lines

Quoted 8 lines

Anyways.

It would seem the way to get this right is then

synchronize_rcu();
drop_in_palace(data);

Quoted 7 lines

Agreed?

This would then mean, however, that every time a fence drops, you have to wait a grace period.

Or maybe stuff DriverFenceData into an RcuBox, too, and defer its dropping.

That would work, but I think we can do better and avoid the synchronize_rcu() along these lines:

unsafe trait RcuRevocable {
    unsafe fn rcu_revoke_in_place(ptr: *mut Self); }

This trait provides a method that's like drop_in_place(), except that when you use this destructor, the value remains usable for one grace period. You could implement it for RcuBox, and for any Copy type, and for ARef<T> when T is cleaned up with rcu, and probably also other stuff.

Quoted 8 lines
Quoted 5 lines

I mean, this cannot be magic. It also boils down to executing one RCU callback per DriverFence dropping.

Is there a significant difference to stuffing DriverFenceData into an RcuBox?

Do you mean hard-coding that the user-data of a driver fence is always stored in an RcuBox?

I'm talking about this:

impl<F: Send + Sync + DriverFenceAllowedData, C: Send + Sync> DriverFenceAllocation<F, C> { /// Create a new allocation slot that can later be used to create a fully /// initialized [DriverFence] without the need to allocate.
    pub fn new(fctx: Arc<FenceCtx<F, C>>, data: F) -> Result<Self> {
        let fence_data = DriverFenceData {
            // inner remains uninitialized until a [DriverFence] takes over.
            inner: Fence {
                inner: Opaque::uninit(),
            },
            fctx,
            data,
        };

        // In order to support the C dma_fence callbacks, it is necessary for // a Fence and a DriverFence to live in the same allocation, // because the C backend passes a dma_fence, from which the driver most // likely wants to be able to access its data in DriverFence. //
        // Hence, we need the manage the memory manually. It will be freed by the // C backend automatically once the refcount within Fence drops to 0. let data = RcuBox::new(fence_data, GFP_KERNEL | __GFP_ZERO)?;

        Ok(Self { data })
    }

This way, the entire DriverFenceData will remain valid for an additional grace period. I suppose this would solve your pointer- invalid concern.

Quoted 8 lines
Quoted 3 lines

However, it appears like overkill to me because the refcounting + C backend already ensure that nothing drops too soon, and the backend frees with kfree_rcu(), so…

I agree that it doesn't sound like we want RcuBox here.

What kind of metadata are we actually planning to store in the DriverFence in practice?

Quoted 6 lines

I suppose it might be GPUVM payloads. IIRC in Tyr it's currently just empty structs as some sort of identifier. I guess copies of the actual command buffers will also reside in their associated fences.

But Boris, Daniel and Danilo probably have something smart to say about that.

In Tyr we don't need any, because we're using SW signalling: we get an interrupt, look at our HW fence seqno, and walk the set of pending fences to signal them with the ::signal() method (that's an over-simplified view of it, but functionally that's how it works).

Now, the question is more, what drivers planning to support HW signaling want attached to the DriverFence. My gut feeling is that now that we have a way to get back to the FenceCtx, and given that FenceCtx also has driver-specific data attached to it, most common use cases where the fence contains a backpointer to its timeline/creator are covered. If I look at nouveau_fence [1] (I intentionally looked at the most likely next user of this) for instance:

Quoted 8 lines
struct nouveau_fence {
	struct dma_fence base;

	struct list_head head;

	struct nouveau_channel __rcu *channel;
	unsigned long timeout;
};  

Thx for the explanation

Quoted 16 lines

- channel is basically the FenceCtx in your new design
- head is used to insert the fence in the fence context list. Not too sure how it translates in rust (insertion in an XArray instead?). Anyway, that's just stuff you need to make your fence part of the context timeline, so it's again related to how you link a fence to its context and let the context walk back pending fences when it needs to. Feels like my original FenceTimeline abstract proposal [2] could help make this thing generic, but let's reconsider this once we've got the basics sorted out, please
- timeout is probably the only thing of interest here, assuming things can be signaled out of order (if they are signaled in order, like is the case in Tyr, the timeout can just be on the FenceCtxData and represent the timeout of the first fence in the set). Anyway, that's a scalar, so no fancy ::drop() needed if that's what you're worried about

What we / Alice are worried about is multiple things.

One issue is that Alice believes that

        // SAFETY: `self.data` is owned by the DriverFence, but could be accessed
        // through some dma_fence callbacks right now. Access is being revoked
        // above by signalling the fence. The DriverFenceSafeToDrop trait
        // ensures that the data either does not need drop, or if it does it
        // lives in a RcuBox which will delay dropping by one grace period, hence
        // ensuring that all readers have disappeared.
        unsafe { drop_in_place(self.data.as_ptr()) };
Quoted 6 lines

from DriverFence::drop() invalidates the pointers that thread A accesses, even if DriverFence::data.data's drop is delayed with RCU.

Now if thread B drops DriverFence, thread A could use invalid pointers, but while still operating on non-deallocated memory (because our refcounting guards against that).

This is where I think our very permissive dma_fence callback design is hitting us in the face. There are callbacks that are called with the fence lock held (::enable_signalling(), ::signaled()), and because we forcibly signal on DriverFence::drop() (which requires locking as well) and then drop the DriverFenceData::data (I'd expect DriverFenceData::fctx to be manually dropped too, but I don't see that done in the current version), the problem you're describing doesn't exist, I think.

That leaves those that are not called with the lock held, and for which we rely on the RCU lock:

- ::get_{driver,timeline}_name() get back to the FenceCtx to get those names, and if the Arc<FenceCtx> inside the DriverFenceData is dropped, I guess it can't be trusted
- ::set_deadline() is problematic as well
- ::wait() and ::release() are deprecated

Quoted 4 lines

The only way I can see to make that bullet proof would be to do synchronize_rcu() before the drop_in_place() above – but that would mean that we delay each fence drop by one grace period.

synchronize_rcu() in the DriverFence::drop() path is indeed not a great idea. Not only because it adds an heavy synchronization point in a potentially hot-path, but also because you're back to a situation where DriverFence users need to be very careful about the context they are when they drop these objects, and because drop() is an implicit operation most of the time, it becomes super fragile.

If we're going to force this synchronize_rcu(), I think it should be done when the FenceCtx is dropped, meaning we're back to a situation where DriverFenceData::data is also RCU-deferred with an rcu_call(), which, IIRC, was the original idea.

Quoted 4 lines

I'm still not sure how real the problem really is, though. Because refcounting guards, and drop() doesn't actually *do* something to the pointers, or does it?

It doesn't do anything to the dma_fence part of the DriverFenceData, but it can touch the driver-specific part, and most importantly, it might leave it with stale information that are then re-interpreted by the dma_fence_ops callback that's still in-flight.

Quoted 12 lines

--

Another, related issue would be
- thread A accesses DriverFence::data through a backend_ops
- thread B drops DriverFence
- there are no RCU callbacks (call_rcu()) pending
- thus, the rcu_barrier() in FenceCtx::drop() takes no effect. We would actually need synchronize_rcu() there.

Quoted 2 lines

So would seem we still didn't get fence and fence_ctx teardown completely right.

Nope, indeed. The driver_name/timeline_name retrieval is already problematic. You probably get away with it because you're leaking the context (no drop on DriverFenceData::fctx AFAICT).

#37 lore raw

Re: [PATCH 3/4] rust: Add dma_fence abstractions

Quoted 294 lines · On Wed, 3 Jun 2026 11:52:47 +0200 Boris Brezillon…
Quoted 227 lines · On Wed, 03 Jun 2026 09:43:55 +0200 Philipp Stanner <[email protected]> wrote:
Quoted 185 lines · Boris Brezillon
Quoted 162 lines · On Wed, 03 Jun 2026 08:10:51 +0200 Philipp Stanner <[email protected]> wrote:

+Cc Danilo, Gary, Daniel, Christian
(who seem to have been lost because Sashiko dropped them and this thread is a reply to that tool)

Quoted 148 lines · Alice Ryhl
Quoted 17 lines · Philipp Stanner
Quoted 1 line · Alice Ryhl

On

[…]

Quoted 12 lines
Quoted 11 lines
Quoted 4 lines

If you don't implement Sync, then DriverFence cannot be stored in an Arc. I wouldn't take away that ability unless you have to, and I don't see anything in the DriverFence API that would mean you can't do that.

Nope. We explicitly agreed on this design.

Just 1 DriverFence. Just 1 party that can signal it.
Note that we also agreed upon the Driverfence disappearing with .signal(), which certainly prevents several from existing, unless you do an Option.take()

Quoted 5 lines
Quoted 3 lines

I would like to clarify that I'm not suggesting any changes to the design. Implementing Sync is not the same as having multiple driver fences.

I mean, I guess one can do that. But it's up to the driver then to see how it can signal its fence.

I don't believe Sync changes anything with that regard. The signal method takes 'self', but the Sync trait only affects how '&self' methods can be called.

Quoted 120 lines
Quoted 76 lines
Quoted 76 lines
Quoted 36 lines
Quoted 36 lines
Quoted 5 lines
Quoted 5 lines
Quoted 4 lines

 so even though
the fence context may be valid for another grace period, the *pointer*
to the fence context is not. The pointer could have been zeroed by the
destructor.

Quoted 8 lines
Quoted 4 lines

That particular pointer to the DriverFenceData could have been zeroed. But potential other accessors have already crafted themselves a new pointer to the, by the power of RCU, still valid data. That new pointer is container-of-ed from struct dma_fence *f.

I'm not talking about the pointer to DriverFenceData, I'm talking about the pointer to the FenceCtx, or the pointer to the data (if F is RcuBox).

Yeah, but the backing memory is still alive. And new pointers to that memory get crafted by the accessors. If a callback accesses the data through container_of(Fence), it gets a new pointer.

So what's the problem?

Where is the invalid pointer that someone is accessing?

Quoted 4 lines

The Arc type is not a type that opts-out of &mut == exclusive, so the second drop_in_place() above is assumed exclusive access to the Arc<FenceCtx<F,C>> field.

OK, so I think I see the problem. So the invalid pointer is Arc<FenceCtx…>? And potentially the <F> pointer (although we don't have a picture yet as to how that would be accessed through other callbacks.

Quoted 4 lines

 If another thread obtains a pointer to the
FenceCtx via reading the fctx field of the DriverFence in parallel with this, then that's not allowed because the drop_in_place() call has exclusive access to that field.

Quoted 7 lines
Quoted 4 lines

I think I have been asking in several of our meetings in the past whether it is actually a problem to access data that has been dropped() IF we know that drop does not cause UAF and the answer was kind of like a "well if it does not actually get freed…"

Ok, well, IMO the simplest approach is to say you can't. There may be roundabout ways to do it, but I would suggest that we just ... don't.

Ack.

Quoted 29 lines

Quoted 8 lines

Anyways.

It would seem the way to get this right is then

synchronize_rcu();
drop_in_palace(data);

Quoted 7 lines

Agreed?

This would then mean, however, that every time a fence drops, you have to wait a grace period.

Or maybe stuff DriverFenceData into an RcuBox, too, and defer its dropping.

That would work, but I think we can do better and avoid the synchronize_rcu() along these lines:

unsafe trait RcuRevocable {
    unsafe fn rcu_revoke_in_place(ptr: *mut Self); }

This trait provides a method that's like drop_in_place(), except that when you use this destructor, the value remains usable for one grace period. You could implement it for RcuBox, and for any Copy type, and for ARef<T> when T is cleaned up with rcu, and probably also other stuff.

Quoted 8 lines
Quoted 5 lines

I mean, this cannot be magic. It also boils down to executing one RCU callback per DriverFence dropping.

Is there a significant difference to stuffing DriverFenceData into an RcuBox?

Do you mean hard-coding that the user-data of a driver fence is always stored in an RcuBox?

I'm talking about this:

impl<F: Send + Sync + DriverFenceAllowedData, C: Send + Sync> DriverFenceAllocation<F, C> { /// Create a new allocation slot that can later be used to create a fully /// initialized [DriverFence] without the need to allocate.
    pub fn new(fctx: Arc<FenceCtx<F, C>>, data: F) -> Result<Self> {
        let fence_data = DriverFenceData {
            // inner remains uninitialized until a [DriverFence] takes over.
            inner: Fence {
                inner: Opaque::uninit(),
            },
            fctx,
            data,
        };

        // In order to support the C dma_fence callbacks, it is necessary for // a Fence and a DriverFence to live in the same allocation, // because the C backend passes a dma_fence, from which the driver most // likely wants to be able to access its data in DriverFence. //
        // Hence, we need the manage the memory manually. It will be freed by the // C backend automatically once the refcount within Fence drops to 0. let data = RcuBox::new(fence_data, GFP_KERNEL | __GFP_ZERO)?;

        Ok(Self { data })
    }

This way, the entire DriverFenceData will remain valid for an additional grace period. I suppose this would solve your pointer- invalid concern.

Quoted 8 lines
Quoted 3 lines

However, it appears like overkill to me because the refcounting + C backend already ensure that nothing drops too soon, and the backend frees with kfree_rcu(), so…

I agree that it doesn't sound like we want RcuBox here.

What kind of metadata are we actually planning to store in the DriverFence in practice?

Quoted 6 lines

I suppose it might be GPUVM payloads. IIRC in Tyr it's currently just empty structs as some sort of identifier. I guess copies of the actual command buffers will also reside in their associated fences.

But Boris, Daniel and Danilo probably have something smart to say about that.

In Tyr we don't need any, because we're using SW signalling: we get an interrupt, look at our HW fence seqno, and walk the set of pending fences to signal them with the ::signal() method (that's an over-simplified view of it, but functionally that's how it works).

Now, the question is more, what drivers planning to support HW signaling want attached to the DriverFence. My gut feeling is that now that we have a way to get back to the FenceCtx, and given that FenceCtx also has driver-specific data attached to it, most common use cases where the fence contains a backpointer to its timeline/creator are covered. If I look at nouveau_fence [1] (I intentionally looked at the most likely next user of this) for instance:

Quoted 8 lines
struct nouveau_fence {
	struct dma_fence base;

	struct list_head head;

	struct nouveau_channel __rcu *channel;
	unsigned long timeout;
};    

Thx for the explanation

Quoted 16 lines

- channel is basically the FenceCtx in your new design
- head is used to insert the fence in the fence context list. Not too sure how it translates in rust (insertion in an XArray instead?). Anyway, that's just stuff you need to make your fence part of the context timeline, so it's again related to how you link a fence to its context and let the context walk back pending fences when it needs to. Feels like my original FenceTimeline abstract proposal [2] could help make this thing generic, but let's reconsider this once we've got the basics sorted out, please
- timeout is probably the only thing of interest here, assuming things can be signaled out of order (if they are signaled in order, like is the case in Tyr, the timeout can just be on the FenceCtxData and represent the timeout of the first fence in the set). Anyway, that's a scalar, so no fancy ::drop() needed if that's what you're worried about

What we / Alice are worried about is multiple things.

One issue is that Alice believes that

        // SAFETY: `self.data` is owned by the DriverFence, but could be accessed
        // through some dma_fence callbacks right now. Access is being revoked
        // above by signalling the fence. The DriverFenceSafeToDrop trait
        // ensures that the data either does not need drop, or if it does it
        // lives in a RcuBox which will delay dropping by one grace period, hence
        // ensuring that all readers have disappeared.
        unsafe { drop_in_place(self.data.as_ptr()) };
Quoted 6 lines

from DriverFence::drop() invalidates the pointers that thread A accesses, even if DriverFence::data.data's drop is delayed with RCU.

Now if thread B drops DriverFence, thread A could use invalid pointers, but while still operating on non-deallocated memory (because our refcounting guards against that).

This is where I think our very permissive dma_fence callback design is hitting us in the face. There are callbacks that are called with the fence lock held (::enable_signalling(), ::signaled()), and because we forcibly signal on DriverFence::drop() (which requires locking as well) and then drop the DriverFenceData::data (I'd expect DriverFenceData::fctx to be manually dropped too, but I don't see that done in the current version), the problem you're describing doesn't exist, I think.

That leaves those that are not called with the lock held, and for which we rely on the RCU lock:

- ::get_{driver,timeline}_name() get back to the FenceCtx to get those names, and if the Arc<FenceCtx> inside the DriverFenceData is dropped, I guess it can't be trusted
- ::set_deadline() is problematic as well
- ::wait() and ::release() are deprecated

Quoted 4 lines

The only way I can see to make that bullet proof would be to do synchronize_rcu() before the drop_in_place() above – but that would mean that we delay each fence drop by one grace period.

synchronize_rcu() in the DriverFence::drop() path is indeed not a great idea. Not only because it adds an heavy synchronization point in a potentially hot-path, but also because you're back to a situation where DriverFence users need to be very careful about the context they are when they drop these objects, and because drop() is an implicit operation most of the time, it becomes super fragile.

If we're going to force this synchronize_rcu(), I think it should be done when the FenceCtx is dropped, meaning we're back to a situation where DriverFenceData::data is also RCU-deferred with an rcu_call(), which, IIRC, was the original idea.

Quoted 4 lines

I'm still not sure how real the problem really is, though. Because refcounting guards, and drop() doesn't actually *do* something to the pointers, or does it?

It doesn't do anything to the dma_fence part of the DriverFenceData, but it can touch the driver-specific part, and most importantly, it might leave it with stale information that are then re-interpreted by the dma_fence_ops callback that's still in-flight.

Quoted 12 lines

--

Another, related issue would be
- thread A accesses DriverFence::data through a backend_ops
- thread B drops DriverFence
- there are no RCU callbacks (call_rcu()) pending
- thus, the rcu_barrier() in FenceCtx::drop() takes no effect. We would actually need synchronize_rcu() there.

Quoted 6 lines
Quoted 2 lines

So would seem we still didn't get fence and fence_ctx teardown completely right.

Nope, indeed. The driver_name/timeline_name retrieval is already problematic. You probably get away with it because you're leaking the context (no drop on DriverFenceData::fctx AFAICT).

My bad, it's the whole DriverFenceData you drop_in_place(), so there's no leak, but DriverFenceData::fctx becomes invalid, and any in-flight get_{driver,timeline}_name() will trigger the issue you were reporting (access to stale fctx).

#38 lore raw

Re: [PATCH 3/4] rust: Add dma_fence abstractions

Quoted 240 lines · Boris Brezillon
Quoted 237 lines · On Wed, 3 Jun 2026 11:52:47 +0200 Boris Brezillon…
Quoted 233 lines · On Wed, 03 Jun 2026 09:43:55 +0200 Philipp Stanner <[email protected]> wrote:
Quoted 185 lines · Boris Brezillon

On Wed, 03 Jun 2026 08:10:51 +0200
Philipp Stanner <[email protected]> wrote:

Quoted 162 lines

+Cc Danilo, Gary, Daniel, Christian
(who seem to have been lost because Sashiko dropped them and this thread is a reply to that tool)

Quoted 148 lines · Alice Ryhl
Quoted 17 lines · Philipp Stanner
Quoted 1 line · Alice Ryhl

On

[…]

Quoted 12 lines
Quoted 11 lines
Quoted 4 lines

If you don't implement Sync, then DriverFence cannot be stored in an Arc. I wouldn't take away that ability unless you have to, and I don't see anything in the DriverFence API that would mean you can't do that.

Nope. We explicitly agreed on this design.

Just 1 DriverFence. Just 1 party that can signal it.
Note that we also agreed upon the Driverfence disappearing with .signal(), which certainly prevents several from existing, unless you do an Option.take()

Quoted 5 lines
Quoted 3 lines

I would like to clarify that I'm not suggesting any changes to the design. Implementing Sync is not the same as having multiple driver fences.

I mean, I guess one can do that. But it's up to the driver then to see how it can signal its fence.

I don't believe Sync changes anything with that regard. The signal method takes 'self', but the Sync trait only affects how '&self' methods can be called.

Quoted 120 lines
Quoted 76 lines
Quoted 76 lines
Quoted 36 lines
Quoted 36 lines
Quoted 5 lines
Quoted 5 lines

 so even though
the fence context may be valid for another grace period, the *pointer*
to the fence context is not. The pointer could have been zeroed by the
destructor.

Quoted 8 lines
Quoted 4 lines

That particular pointer to the DriverFenceData could have been zeroed.
But potential other accessors have already crafted themselves a new
pointer to the, by the power of RCU, still valid data. That new pointer
is container-of-ed from struct dma_fence *f.

I'm not talking about the pointer to DriverFenceData, I'm talking about the pointer to the FenceCtx, or the pointer to the data (if F is RcuBox).

Yeah, but the backing memory is still alive. And new pointers to that memory get crafted by the accessors. If a callback accesses the data through container_of(Fence), it gets a new pointer.

So what's the problem?

Where is the invalid pointer that someone is accessing?

Quoted 4 lines

The Arc type is not a type that opts-out of &mut == exclusive, so the second drop_in_place() above is assumed exclusive access to the Arc<FenceCtx<F,C>> field.

OK, so I think I see the problem. So the invalid pointer is Arc<FenceCtx…>? And potentially the <F> pointer (although we don't have a picture yet as to how that would be accessed through other callbacks.

Quoted 4 lines

 If another thread obtains a pointer to the
FenceCtx via reading the fctx field of the DriverFence in parallel with this, then that's not allowed because the drop_in_place() call has exclusive access to that field.

Quoted 7 lines
Quoted 4 lines

I think I have been asking in several of our meetings in the past whether it is actually a problem to access data that has been dropped() IF we know that drop does not cause UAF and the answer was kind of like a "well if it does not actually get freed…"

Ok, well, IMO the simplest approach is to say you can't. There may be roundabout ways to do it, but I would suggest that we just ... don't.

Ack.

Quoted 29 lines

Quoted 8 lines

Anyways.

It would seem the way to get this right is then

synchronize_rcu();
drop_in_palace(data);

Quoted 7 lines

Agreed?

This would then mean, however, that every time a fence drops, you have to wait a grace period.

Or maybe stuff DriverFenceData into an RcuBox, too, and defer its dropping.

That would work, but I think we can do better and avoid the synchronize_rcu() along these lines:

unsafe trait RcuRevocable {
    unsafe fn rcu_revoke_in_place(ptr: *mut Self); }

This trait provides a method that's like drop_in_place(), except that when you use this destructor, the value remains usable for one grace period. You could implement it for RcuBox, and for any Copy type, and for ARef<T> when T is cleaned up with rcu, and probably also other stuff.

Quoted 8 lines
Quoted 5 lines

I mean, this cannot be magic. It also boils down to executing one RCU callback per DriverFence dropping.

Is there a significant difference to stuffing DriverFenceData into an RcuBox?

Do you mean hard-coding that the user-data of a driver fence is always stored in an RcuBox?

I'm talking about this:

impl<F: Send + Sync + DriverFenceAllowedData, C: Send + Sync> DriverFenceAllocation<F, C> { /// Create a new allocation slot that can later be used to create a fully /// initialized [DriverFence] without the need to allocate.
    pub fn new(fctx: Arc<FenceCtx<F, C>>, data: F) -> Result<Self> {
        let fence_data = DriverFenceData {
            // inner remains uninitialized until a [DriverFence] takes over.
            inner: Fence {
                inner: Opaque::uninit(),
            },
            fctx,
            data,
        };

        // In order to support the C dma_fence callbacks, it is necessary for // a Fence and a DriverFence to live in the same allocation, // because the C backend passes a dma_fence, from which the driver most // likely wants to be able to access its data in DriverFence. //
        // Hence, we need the manage the memory manually. It will be freed by the // C backend automatically once the refcount within Fence drops to 0. let data = RcuBox::new(fence_data, GFP_KERNEL | __GFP_ZERO)?;

        Ok(Self { data })
    }

This way, the entire DriverFenceData will remain valid for an additional grace period. I suppose this would solve your pointer- invalid concern.

Quoted 8 lines
Quoted 3 lines

However, it appears like overkill to me because the refcounting + C backend already ensure that nothing drops too soon, and the backend frees with kfree_rcu(), so…

I agree that it doesn't sound like we want RcuBox here.

What kind of metadata are we actually planning to store in the DriverFence in practice?

Quoted 6 lines

I suppose it might be GPUVM payloads. IIRC in Tyr it's currently just empty structs as some sort of identifier. I guess copies of the actual command buffers will also reside in their associated fences.

But Boris, Daniel and Danilo probably have something smart to say about that.

In Tyr we don't need any, because we're using SW signalling: we get an interrupt, look at our HW fence seqno, and walk the set of pending fences to signal them with the ::signal() method (that's an over-simplified view of it, but functionally that's how it works).

Now, the question is more, what drivers planning to support HW signaling want attached to the DriverFence. My gut feeling is that now that we have a way to get back to the FenceCtx, and given that FenceCtx also has driver-specific data attached to it, most common use cases where the fence contains a backpointer to its timeline/creator are covered. If I look at nouveau_fence [1] (I intentionally looked at the most likely next user of this) for instance:

Quoted 8 lines
struct nouveau_fence {
	struct dma_fence base;

	struct list_head head;

	struct nouveau_channel __rcu *channel;
	unsigned long timeout;
};    

Thx for the explanation

Quoted 16 lines

- channel is basically the FenceCtx in your new design
- head is used to insert the fence in the fence context list. Not too sure how it translates in rust (insertion in an XArray instead?). Anyway, that's just stuff you need to make your fence part of the context timeline, so it's again related to how you link a fence to its context and let the context walk back pending fences when it needs to. Feels like my original FenceTimeline abstract proposal [2] could help make this thing generic, but let's reconsider this once we've got the basics sorted out, please
- timeout is probably the only thing of interest here, assuming things can be signaled out of order (if they are signaled in order, like is the case in Tyr, the timeout can just be on the FenceCtxData and represent the timeout of the first fence in the set). Anyway, that's a scalar, so no fancy ::drop() needed if that's what you're worried about

What we / Alice are worried about is multiple things.

One issue is that Alice believes that

        // SAFETY: self.data is owned by the DriverFence, but could be accessed // through some dma_fence callbacks right now. Access is being revoked // above by signalling the fence. The DriverFenceSafeToDrop trait // ensures that the data either does not need drop, or if it does it // lives in a RcuBox which will delay dropping by one grace period, hence // ensuring that all readers have disappeared.
        unsafe { drop_in_place(self.data.as_ptr()) };

from DriverFence::drop() invalidates the pointers that thread A accesses, even if DriverFence::data.data's drop is delayed with RCU.

Now if thread B drops DriverFence, thread A could use invalid pointers, but while still operating on non-deallocated memory (because our refcounting guards against that).

Quoted 8 lines
Quoted 8 lines

This is where I think our very permissive dma_fence callback design is hitting us in the face. There are callbacks that are called with the fence lock held (::enable_signalling(), ::signaled()), and because we forcibly signal on DriverFence::drop() (which requires locking as well) and then drop the DriverFenceData::data (I'd expect DriverFenceData::fctx to be manually dropped too, but I don't see that done in the current version), the problem you're describing doesn't exist, I think.

The problem of *nominally* those Rust pointers / references in DriverFenceData becoming invalid does exist.

But I tend to believe that this is a syntactic theoretical problem. All the memory is still valid.

The FenceCtx cannot disappear because its drop is delayed with synchronize_rcu() (previously rcu_barrier()).

It's really just about the question whether

rcu_read_lock()
let ptr = something_valid_for_a_grace_period; drop(something_valid_for_a_grace_period); // delayed by a GP do_stuff_with_(ptr);
rcu_read_unlock()

is dangerous or not.

Alice doesn't like it – but I do point out that the memory cannot go away. So in C this is perfectly safe.

About Rust, I don't know.

Quoted 15 lines
Quoted 15 lines

That leaves those that are not called with the lock held, and for which we rely on the RCU lock:

- ::get_{driver,timeline}_name() get back to the FenceCtx to get those names, and if the Arc<FenceCtx> inside the DriverFenceData is dropped, I guess it can't be trusted
- ::set_deadline() is problematic as well
- ::wait() and ::release() are deprecated

Quoted 4 lines

The only way I can see to make that bullet proof would be to do synchronize_rcu() before the drop_in_place() above – but that would mean that we delay each fence drop by one grace period.

Quoted 6 lines
Quoted 6 lines

synchronize_rcu() in the DriverFence::drop() path is indeed not a great idea. Not only because it adds an heavy synchronization point in a potentially hot-path, but also because you're back to a situation where DriverFence users need to be very careful about the context they are when they drop these objects, and because drop() is an implicit operation most of the time, it becomes super fragile.

We declared it illegal to drop a fence without signalling it. So it's not thaaat implicit.

Moreover, the current suggestion for v2 is that we force the user to obey to our safety requirements:

/// Trait to ensure that the fence implementation can safely drop the user data /// passed in [FenceCtx::new_fence_allocation()].
///
/// # Safety
///
/// You must only implement this trait for your type if the following rules are /// obeyed to:
///
/// 1. Your type either does not need [Drop] at all, OR drops only after 1 RCU
/// grace period has passed.
/// 2. If present, your [Drop] implementation performs no operations illegal in
/// atomic context, such as allocating with GFP_KERNEL.
pub unsafe trait DriverFenceSafeToDrop {}

https://gitlab.freedesktop.org/pstanner/linux-drm-work/-/blob/jobqueue-dma-fence-v2/rust/kernel/dma_buf/dma_fence.rs?ref_type=heads#L512

That way we hit several birds with one stone. We get our delayed-by-a- grace-period drop, *and* we can very explicitly document: "you must not do atomic-hostile stuff in drop()".

Pretty cool, right? :]

Quoted 5 lines
Quoted 5 lines

If we're going to force this synchronize_rcu(), I think it should be done when the FenceCtx is dropped, meaning we're back to a situation where DriverFenceData::data is also RCU-deferred with an rcu_call(), which, IIRC, was the original idea.

See the trait, yes.

Quoted 10 lines
Quoted 10 lines
Quoted 5 lines

I'm still not sure how real the problem really is, though. Because refcounting guards, and drop() doesn't actually *do* something to the pointers, or does it?

It doesn't do anything to the dma_fence part of the DriverFenceData, but it can touch the driver-specific part, and most importantly, it might leave it with stale information that are then re-interpreted by the dma_fence_ops callback that's still in-flight.

Hmm no, I think this is all safe.

It's really about the question whether you are allowed to access a pointer in Rust that has been dropped, even if you can guarantee that it is still valid after dropping (for a grace period).

P.

Quoted 24 lines
Quoted 13 lines
Quoted 13 lines

--

Another, related issue would be
- thread A accesses DriverFence::data through a backend_ops
- thread B drops DriverFence
- there are no RCU callbacks (call_rcu()) pending
- thus, the rcu_barrier() in FenceCtx::drop() takes no effect. We would actually need synchronize_rcu() there.

Quoted 6 lines
Quoted 2 lines

So would seem we still didn't get fence and fence_ctx teardown completely right.

Nope, indeed. The driver_name/timeline_name retrieval is already problematic. You probably get away with it because you're leaking the context (no drop on DriverFenceData::fctx AFAICT).

My bad, it's the whole DriverFenceData you drop_in_place(), so there's no leak, but DriverFenceData::fctx becomes invalid, and any in-flight get_{driver,timeline}_name() will trigger the issue you were reporting (access to stale fctx).

#39 lore raw

Re: [PATCH 3/4] rust: Add dma_fence abstractions

Quoted 250 lines · On Wed, 03 Jun 2026 12:07:42 +0200 Philipp Stanner <[email protected]> wrote:
Quoted 240 lines · Boris Brezillon
Quoted 237 lines · On Wed, 3 Jun 2026 11:52:47 +0200 Boris Brezillon…
Quoted 233 lines · On Wed, 03 Jun 2026 09:43:55 +0200 Philipp Stanner <[email protected]> wrote:
Quoted 185 lines · Boris Brezillon

On Wed, 03 Jun 2026 08:10:51 +0200
Philipp Stanner <[email protected]> wrote:

Quoted 162 lines

+Cc Danilo, Gary, Daniel, Christian
(who seem to have been lost because Sashiko dropped them and this thread is a reply to that tool)

Quoted 148 lines · Alice Ryhl
Quoted 17 lines · Philipp Stanner
Quoted 1 line · Alice Ryhl

On

[…]

Quoted 12 lines
Quoted 11 lines
Quoted 4 lines

If you don't implement Sync, then DriverFence cannot be stored in an Arc. I wouldn't take away that ability unless you have to, and I don't see anything in the DriverFence API that would mean you can't do that.

Nope. We explicitly agreed on this design.

Just 1 DriverFence. Just 1 party that can signal it.
Note that we also agreed upon the Driverfence disappearing with .signal(), which certainly prevents several from existing, unless you do an Option.take()

Quoted 5 lines
Quoted 3 lines

I would like to clarify that I'm not suggesting any changes to the design. Implementing Sync is not the same as having multiple driver fences.

I mean, I guess one can do that. But it's up to the driver then to see how it can signal its fence.

I don't believe Sync changes anything with that regard. The signal method takes 'self', but the Sync trait only affects how '&self' methods can be called.

Quoted 120 lines
Quoted 76 lines
Quoted 76 lines
Quoted 36 lines
Quoted 36 lines
Quoted 13 lines

 so even though
the fence context may be valid for another grace period, the *pointer*
to the fence context is not. The pointer could have been zeroed by the
destructor.

That particular pointer to the DriverFenceData could have been zeroed.
But potential other accessors have already crafted themselves a new
pointer to the, by the power of RCU, still valid data. That new pointer
is container-of-ed from struct dma_fence *f.

I'm not talking about the pointer to DriverFenceData, I'm talking about
the pointer to the FenceCtx, or the pointer to the data (if F is
RcuBox).

Yeah, but the backing memory is still alive. And new pointers to that memory get crafted by the accessors. If a callback accesses the data through container_of(Fence), it gets a new pointer.

So what's the problem?

Where is the invalid pointer that someone is accessing?

Quoted 4 lines

The Arc type is not a type that opts-out of &mut == exclusive, so the
second drop_in_place() above is assumed exclusive access to the
Arc<FenceCtx<F,C>> field.

OK, so I think I see the problem. So the invalid pointer is Arc<FenceCtx…>? And potentially the <F> pointer (although we don't have a picture yet as to how that would be accessed through other callbacks.

Quoted 4 lines

 If another thread obtains a pointer to the
FenceCtx via reading the fctx field of the DriverFence in parallel with
this, then that's not allowed because the drop_in_place() call has
exclusive access to that field.

Quoted 7 lines
Quoted 4 lines

I think I have been asking in several of our meetings in the past whether it is actually a problem to access data that has been dropped() IF we know that drop does not cause UAF and the answer was kind of like a "well if it does not actually get freed…"

Ok, well, IMO the simplest approach is to say you can't. There may be roundabout ways to do it, but I would suggest that we just ... don't.

Ack.

Quoted 29 lines

Quoted 8 lines

Anyways.

It would seem the way to get this right is then

synchronize_rcu();
drop_in_palace(data);

Quoted 7 lines

Agreed?

This would then mean, however, that every time a fence drops, you have to wait a grace period.

Or maybe stuff DriverFenceData into an RcuBox, too, and defer its dropping.

That would work, but I think we can do better and avoid the synchronize_rcu() along these lines:

unsafe trait RcuRevocable {
    unsafe fn rcu_revoke_in_place(ptr: *mut Self); }

This trait provides a method that's like drop_in_place(), except that when you use this destructor, the value remains usable for one grace period. You could implement it for RcuBox, and for any Copy type, and for ARef<T> when T is cleaned up with rcu, and probably also other stuff.

Quoted 8 lines
Quoted 5 lines

I mean, this cannot be magic. It also boils down to executing one RCU callback per DriverFence dropping.

Is there a significant difference to stuffing DriverFenceData into an RcuBox?

Do you mean hard-coding that the user-data of a driver fence is always stored in an RcuBox?

I'm talking about this:

impl<F: Send + Sync + DriverFenceAllowedData, C: Send + Sync> DriverFenceAllocation<F, C> { /// Create a new allocation slot that can later be used to create a fully /// initialized [DriverFence] without the need to allocate.
    pub fn new(fctx: Arc<FenceCtx<F, C>>, data: F) -> Result<Self> {
        let fence_data = DriverFenceData {
            // inner remains uninitialized until a [DriverFence] takes over.
            inner: Fence {
                inner: Opaque::uninit(),
            },
            fctx,
            data,
        };

        // In order to support the C dma_fence callbacks, it is necessary for // a Fence and a DriverFence to live in the same allocation, // because the C backend passes a dma_fence, from which the driver most // likely wants to be able to access its data in DriverFence. //
        // Hence, we need the manage the memory manually. It will be freed by the // C backend automatically once the refcount within Fence drops to 0. let data = RcuBox::new(fence_data, GFP_KERNEL | __GFP_ZERO)?;

        Ok(Self { data })
    }

This way, the entire DriverFenceData will remain valid for an additional grace period. I suppose this would solve your pointer- invalid concern.

Quoted 8 lines
Quoted 3 lines

However, it appears like overkill to me because the refcounting + C backend already ensure that nothing drops too soon, and the backend frees with kfree_rcu(), so…

I agree that it doesn't sound like we want RcuBox here.

What kind of metadata are we actually planning to store in the DriverFence in practice?

Quoted 6 lines

I suppose it might be GPUVM payloads. IIRC in Tyr it's currently just empty structs as some sort of identifier. I guess copies of the actual command buffers will also reside in their associated fences.

But Boris, Daniel and Danilo probably have something smart to say about that.

In Tyr we don't need any, because we're using SW signalling: we get an interrupt, look at our HW fence seqno, and walk the set of pending fences to signal them with the ::signal() method (that's an over-simplified view of it, but functionally that's how it works).

Now, the question is more, what drivers planning to support HW signaling want attached to the DriverFence. My gut feeling is that now that we have a way to get back to the FenceCtx, and given that FenceCtx also has driver-specific data attached to it, most common use cases where the fence contains a backpointer to its timeline/creator are covered. If I look at nouveau_fence [1] (I intentionally looked at the most likely next user of this) for instance:

Quoted 8 lines
struct nouveau_fence {
	struct dma_fence base;

	struct list_head head;

	struct nouveau_channel __rcu *channel;
	unsigned long timeout;
};      

Thx for the explanation

Quoted 16 lines

- channel is basically the FenceCtx in your new design
- head is used to insert the fence in the fence context list. Not too sure how it translates in rust (insertion in an XArray instead?). Anyway, that's just stuff you need to make your fence part of the context timeline, so it's again related to how you link a fence to its context and let the context walk back pending fences when it needs to. Feels like my original FenceTimeline abstract proposal [2] could help make this thing generic, but let's reconsider this once we've got the basics sorted out, please
- timeout is probably the only thing of interest here, assuming things can be signaled out of order (if they are signaled in order, like is the case in Tyr, the timeout can just be on the FenceCtxData and represent the timeout of the first fence in the set). Anyway, that's a scalar, so no fancy ::drop() needed if that's what you're worried about

What we / Alice are worried about is multiple things.

One issue is that Alice believes that

        // SAFETY: self.data is owned by the DriverFence, but could be accessed // through some dma_fence callbacks right now. Access is being revoked // above by signalling the fence. The DriverFenceSafeToDrop trait // ensures that the data either does not need drop, or if it does it // lives in a RcuBox which will delay dropping by one grace period, hence // ensuring that all readers have disappeared.
        unsafe { drop_in_place(self.data.as_ptr()) };

from DriverFence::drop() invalidates the pointers that thread A accesses, even if DriverFence::data.data's drop is delayed with RCU.

Now if thread B drops DriverFence, thread A could use invalid pointers, but while still operating on non-deallocated memory (because our refcounting guards against that).

Quoted 8 lines
Quoted 8 lines

This is where I think our very permissive dma_fence callback design is hitting us in the face. There are callbacks that are called with the fence lock held (::enable_signalling(), ::signaled()), and because we forcibly signal on DriverFence::drop() (which requires locking as well) and then drop the DriverFenceData::data (I'd expect DriverFenceData::fctx to be manually dropped too, but I don't see that done in the current version), the problem you're describing doesn't exist, I think.

Quoted 5 lines

The problem of *nominally* those Rust pointers / references in DriverFenceData becoming invalid does exist.

But I tend to believe that this is a syntactic theoretical problem. All the memory is still valid.

But accessing an Arc<Xx> that has been dropped is invalid. You just basically rely on the implementation leaving the memory as is after it dropped the ref it had, but there's nothing preventing it from zero-ing the Arc<>, at which point this becomes a NULL deref. Also, when you drop DriverFenceData::fctx, you might very well be the last FenceCtx owner, so FenceCtx can go away at this point, meaning your in-flight dma_fence_ops::{driver,timeline}_name() callbacks might now point to memory that has been returned to the system and lead to a UAF.

Quoted 3 lines

The FenceCtx cannot disappear because its drop is delayed with synchronize_rcu() (previously rcu_barrier()).

Okay, so that protects against the UAF I was mentioning, but fact the Arc implementation doesn't give any guarantee about what happens to the dropped Arc is still problematic.

Quoted 10 lines

It's really just about the question whether

rcu_read_lock()
let ptr = something_valid_for_a_grace_period; drop(something_valid_for_a_grace_period); // delayed by a GP do_stuff_with_(ptr);
rcu_read_unlock()

is dangerous or not.

It's more:

thread A | thread B | thread C

							|				|
rcu_read_lock()						|				|
 let ptr = something_valid_for_a_grace_period;		|				|
							| rcu_defer(drop(ptr))		|
 do_stuff_with_(ptr);					|				|
rcu_read_unlock()					|				|
							|				| drop(ptr)

And yes, as long as the drop is always deferred I don't see an issue. But IIUC, you were trying to not defer the drop unless the F on DriverFenceData needs a drop. The fact DriverFenceData::fctx is also impacted by that immediate ::drop() means you have to defer the drop() unconditionally now.

Quoted 3 lines

Alice doesn't like it – but I do point out that the memory cannot go away. So in C this is perfectly safe.

It might work, but it's very very fragile, to say the least. Honestly, the more I think about it, the more I believe we should defer the drop unconditionally and call it a day. Yes, it's a potential overhead, but it's, IMHO, way better than relying on some Arc implementation details that might change at some point.

Quoted 25 lines

About Rust, I don't know.

Quoted 15 lines
Quoted 15 lines

That leaves those that are not called with the lock held, and for which we rely on the RCU lock:

- ::get_{driver,timeline}_name() get back to the FenceCtx to get those names, and if the Arc<FenceCtx> inside the DriverFenceData is dropped, I guess it can't be trusted
- ::set_deadline() is problematic as well
- ::wait() and ::release() are deprecated

Quoted 4 lines

The only way I can see to make that bullet proof would be to do synchronize_rcu() before the drop_in_place() above – but that would mean that we delay each fence drop by one grace period.

Quoted 6 lines
Quoted 6 lines

synchronize_rcu() in the DriverFence::drop() path is indeed not a great idea. Not only because it adds an heavy synchronization point in a potentially hot-path, but also because you're back to a situation where DriverFence users need to be very careful about the context they are when they drop these objects, and because drop() is an implicit operation most of the time, it becomes super fragile.

Quoted 2 lines

We declared it illegal to drop a fence without signalling it. So it's not thaaat implicit.

What's implicit is that ::drop() is implicitly called when the object goes out of scope.

Quoted 7 lines

Moreover, the current suggestion for v2 is that we force the user to obey to our safety requirements:

/// Trait to ensure that the fence implementation can safely drop the user data /// passed in [FenceCtx::new_fence_allocation()].

So, if your fence is the last owner of FenceCtx, it drops the last ref, and FenceCtx::drop() is called. In there, I guess the first thing you do in FenceCtx::drop() which waits for a grace period to expire which might force an unexpected blocking operation in the otherwise super trivial atomic-safe FenceData::drop() path. Plus, then you have to drop FenceCtx::data, which might or might not be SafeToDrop.

This is exactly the sort of complications I wanted to avoid initially, and this is why I still think letting the user tell when it's DriverFenceSafeToDrop is just calling for more pain.

Quoted 12 lines

///
/// # Safety
///
/// You must only implement this trait for your type if the following rules are /// obeyed to:
///
/// 1. Your type either does not need [Drop] at all, OR drops only after 1 RCU
/// grace period has passed.
/// 2. If present, your [Drop] implementation performs no operations illegal in
/// atomic context, such as allocating with GFP_KERNEL.
pub unsafe trait DriverFenceSafeToDrop {}

Quoted 8 lines

https://gitlab.freedesktop.org/pstanner/linux-drm-work/-/blob/jobqueue-dma-fence-v2/rust/kernel/dma_buf/dma_fence.rs?ref_type=heads#L512

That way we hit several birds with one stone. We get our delayed-by-a- grace-period drop, *and* we can very explicitly document: "you must not do atomic-hostile stuff in drop()".

Pretty cool, right? :]

Except you don't know. Now you have to carefully audit

- FenceCtx::drop()
- FenceCtx::data::drop()

to guarantee that this is safe. Because with this API, there's no guarantee that the FenceCtx outlives the fences it emitted. BTW, maybe we should enforce that with lifetimes instead of having an Arc<FenceCtx> in DriverFenceData.

Quoted 15 lines
Quoted 6 lines
Quoted 6 lines

If we're going to force this synchronize_rcu(), I think it should be done when the FenceCtx is dropped, meaning we're back to a situation where DriverFenceData::data is also RCU-deferred with an rcu_call(), which, IIRC, was the original idea.

See the trait, yes.

Quoted 6 lines
Quoted 6 lines
Quoted 5 lines

I'm still not sure how real the problem really is, though. Because refcounting guards, and drop() doesn't actually *do* something to the pointers, or does it?

Quoted 6 lines
Quoted 4 lines
Quoted 4 lines

It doesn't do anything to the dma_fence part of the DriverFenceData, but it can touch the driver-specific part, and most importantly, it might leave it with stale information that are then re-interpreted by the dma_fence_ops callback that's still in-flight.

Hmm no, I think this is all safe.

Well, this might be safe (baring the Arc invalidation I mentioned), but also super convoluted design that's hard to audit and maintain overtime. I'm very skeptical that this is what we want to start with just to make sure we optimize the case where no drop on fence data is needed.

Quoted 4 lines

It's really about the question whether you are allowed to access a pointer in Rust that has been dropped, even if you can guarantee that it is still valid after dropping (for a grace period).

An Arc, I don't think so.

#40 lore raw

Re: [PATCH 3/4] rust: Add dma_fence abstractions

Quoted 259 lines · Boris Brezillon
Quoted 250 lines · On Wed, 03 Jun 2026 12:07:42 +0200 Philipp Stanner <[email protected]> wrote:
Quoted 248 lines · Boris Brezillon

On Wed, 3 Jun 2026 11:52:47 +0200
Boris Brezillon <[email protected]> wrote:

Quoted 245 lines

On Wed, 03 Jun 2026 09:43:55 +0200
Philipp Stanner <[email protected]> wrote:

Quoted 227 lines · Boris Brezillon
Quoted 185 lines

On Wed, 03 Jun 2026 08:10:51 +0200
Philipp Stanner <[email protected]> wrote:

Quoted 162 lines

+Cc Danilo, Gary, Daniel, Christian
(who seem to have been lost because Sashiko dropped them and this thread is a reply to that tool)

Quoted 148 lines · Alice Ryhl
Quoted 17 lines · Philipp Stanner
Quoted 1 line · Alice Ryhl

On

[…]

Quoted 12 lines
Quoted 11 lines
Quoted 4 lines

If you don't implement Sync, then DriverFence cannot be stored in an Arc. I wouldn't take away that ability unless you have to, and I don't see anything in the DriverFence API that would mean you can't do that.

Nope. We explicitly agreed on this design.

Just 1 DriverFence. Just 1 party that can signal it.
Note that we also agreed upon the Driverfence disappearing with .signal(), which certainly prevents several from existing, unless you do an Option.take()

Quoted 5 lines
Quoted 3 lines

I would like to clarify that I'm not suggesting any changes to the design. Implementing Sync is not the same as having multiple driver fences.

I mean, I guess one can do that. But it's up to the driver then to see how it can signal its fence.

I don't believe Sync changes anything with that regard. The signal method takes 'self', but the Sync trait only affects how '&self' methods can be called.

Quoted 120 lines
Quoted 76 lines
Quoted 76 lines
Quoted 36 lines
Quoted 36 lines

 so even though
the fence context may be valid for another grace period, the *pointer*
to the fence context is not. The pointer could have been zeroed by the
destructor.

That particular pointer to the DriverFenceData could have been zeroed.
But potential other accessors have already crafted themselves a new
pointer to the, by the power of RCU, still valid data. That new pointer
is container-of-ed from struct dma_fence *f.

I'm not talking about the pointer to DriverFenceData, I'm talking about
the pointer to the FenceCtx, or the pointer to the data (if F is
RcuBox).

Yeah, but the backing memory is still alive. And new pointers to that
memory get crafted by the accessors. If a callback accesses the data
through `container_of(Fence)`, it gets a new pointer.

So what's the problem?

Where is the invalid pointer that someone is accessing?

The Arc type is not a type that opts-out of &mut == exclusive, so the
second drop_in_place() above is assumed exclusive access to the
Arc<FenceCtx<F,C>> field.

OK, so I think I see the problem. So the invalid pointer is
Arc<FenceCtx…>? And potentially the <F> pointer (although we don't have
a picture yet as to how that would be accessed through other callbacks.

 If another thread obtains a pointer to the
FenceCtx via reading the fctx field of the DriverFence in parallel with
this, then that's not allowed because the drop_in_place() call has
exclusive access to that field.

Quoted 7 lines
Quoted 4 lines

I think I have been asking in several of our meetings in the past
whether it is actually a problem to access data that has been dropped()
IF we know that drop does not cause UAF and the answer was kind of like
a "well if it does not actually get freed…"

Ok, well, IMO the simplest approach is to say you can't. There may be roundabout ways to do it, but I would suggest that we just ... don't.

Ack.

Quoted 29 lines

Quoted 15 lines

Anyways.

It would seem the way to get this right is then

synchronize_rcu();
drop_in_palace(data);

Agreed?

This would then mean, however, that every time a fence drops, you have
to wait a grace period.

Or maybe stuff DriverFenceData into an RcuBox, too, and defer its
dropping.

That would work, but I think we can do better and avoid the synchronize_rcu() along these lines:

unsafe trait RcuRevocable {
    unsafe fn rcu_revoke_in_place(ptr: *mut Self); }

This trait provides a method that's like drop_in_place(), except that when you use this destructor, the value remains usable for one grace period. You could implement it for RcuBox, and for any Copy type, and for ARef<T> when T is cleaned up with rcu, and probably also other stuff.

Quoted 8 lines
Quoted 5 lines

I mean, this cannot be magic. It also boils down to executing one RCU callback per DriverFence dropping.

Is there a significant difference to stuffing DriverFenceData into an RcuBox?

Do you mean hard-coding that the user-data of a driver fence is always stored in an RcuBox?

I'm talking about this:

impl<F: Send + Sync + DriverFenceAllowedData, C: Send + Sync> DriverFenceAllocation<F, C> { /// Create a new allocation slot that can later be used to create a fully /// initialized [DriverFence] without the need to allocate.
    pub fn new(fctx: Arc<FenceCtx<F, C>>, data: F) -> Result<Self> {
        let fence_data = DriverFenceData {
            // inner remains uninitialized until a [DriverFence] takes over.
            inner: Fence {
                inner: Opaque::uninit(),
            },
            fctx,
            data,
        };

        // In order to support the C dma_fence callbacks, it is necessary for // a Fence and a DriverFence to live in the same allocation, // because the C backend passes a dma_fence, from which the driver most // likely wants to be able to access its data in DriverFence. //
        // Hence, we need the manage the memory manually. It will be freed by the // C backend automatically once the refcount within Fence drops to 0. let data = RcuBox::new(fence_data, GFP_KERNEL | __GFP_ZERO)?;

        Ok(Self { data })
    }

This way, the entire DriverFenceData will remain valid for an additional grace period. I suppose this would solve your pointer- invalid concern.

Quoted 8 lines
Quoted 3 lines

However, it appears like overkill to me because the refcounting + C backend already ensure that nothing drops too soon, and the backend frees with kfree_rcu(), so…

I agree that it doesn't sound like we want RcuBox here.

What kind of metadata are we actually planning to store in the DriverFence in practice?

Quoted 6 lines

I suppose it might be GPUVM payloads. IIRC in Tyr it's currently just empty structs as some sort of identifier. I guess copies of the actual command buffers will also reside in their associated fences.

But Boris, Daniel and Danilo probably have something smart to say about that.

In Tyr we don't need any, because we're using SW signalling: we get an interrupt, look at our HW fence seqno, and walk the set of pending fences to signal them with the ::signal() method (that's an over-simplified view of it, but functionally that's how it works).

Now, the question is more, what drivers planning to support HW signaling want attached to the DriverFence. My gut feeling is that now that we have a way to get back to the FenceCtx, and given that FenceCtx also has driver-specific data attached to it, most common use cases where the fence contains a backpointer to its timeline/creator are covered. If I look at nouveau_fence [1] (I intentionally looked at the most likely next user of this) for instance:

Quoted 8 lines
struct nouveau_fence {
	struct dma_fence base;

	struct list_head head;

	struct nouveau_channel __rcu *channel;
	unsigned long timeout;
};      

Thx for the explanation

Quoted 16 lines

- channel is basically the FenceCtx in your new design
- head is used to insert the fence in the fence context list. Not too sure how it translates in rust (insertion in an XArray instead?). Anyway, that's just stuff you need to make your fence part of the context timeline, so it's again related to how you link a fence to its context and let the context walk back pending fences when it needs to. Feels like my original FenceTimeline abstract proposal [2] could help make this thing generic, but let's reconsider this once we've got the basics sorted out, please
- timeout is probably the only thing of interest here, assuming things can be signaled out of order (if they are signaled in order, like is the case in Tyr, the timeout can just be on the FenceCtxData and represent the timeout of the first fence in the set). Anyway, that's a scalar, so no fancy ::drop() needed if that's what you're worried about

What we / Alice are worried about is multiple things.

One issue is that Alice believes that

        // SAFETY: self.data is owned by the DriverFence, but could be accessed // through some dma_fence callbacks right now. Access is being revoked // above by signalling the fence. The DriverFenceSafeToDrop trait // ensures that the data either does not need drop, or if it does it // lives in a RcuBox which will delay dropping by one grace period, hence // ensuring that all readers have disappeared.
        unsafe { drop_in_place(self.data.as_ptr()) };

Quoted 6 lines

from DriverFence::drop() invalidates the pointers that thread A accesses, even if DriverFence::data.data's drop is delayed with RCU.

Now if thread B drops DriverFence, thread A could use invalid pointers, but while still operating on non-deallocated memory (because our refcounting guards against that).

This is where I think our very permissive dma_fence callback design is hitting us in the face. There are callbacks that are called with the fence lock held (::enable_signalling(), ::signaled()), and because we forcibly signal on DriverFence::drop() (which requires locking as well) and then drop the DriverFenceData::data (I'd expect DriverFenceData::fctx to be manually dropped too, but I don't see that done in the current version), the problem you're describing doesn't exist, I think.

Quoted 5 lines

The problem of *nominally* those Rust pointers / references in DriverFenceData becoming invalid does exist.

But I tend to believe that this is a syntactic theoretical problem. All the memory is still valid.

Quoted 8 lines

But accessing an Arc<Xx> that has been dropped is invalid. You just basically rely on the implementation leaving the memory as is after it dropped the ref it had, but there's nothing preventing it from zero-ing the Arc<>, at which point this becomes a NULL deref. Also, when you drop DriverFenceData::fctx, you might very well be the last FenceCtx owner, so FenceCtx can go away at this point, meaning your in-flight dma_fence_ops::{driver,timeline}_name() callbacks might now point to memory that has been returned to the system and lead to a UAF.

Nope.
The Arc<fctx> can earliest disappear once the DriverFence dropped. The Arc<fctx> has been allocated with DriverFenceAllocation. That memory only does become re-usable once dma_fence_put() in DriverFence::drop() releases the memory.

A consuming / reading Fence also holds a refcount.

The FenceCtx on the other hand delays its drop() by an RCU grace period. So until all accessors are gone.

Where is the problem?

Quoted 8 lines
Quoted 4 lines

The FenceCtx cannot disappear because its drop is delayed with synchronize_rcu() (previously rcu_barrier()).

Okay, so that protects against the UAF I was mentioning, but fact the Arc implementation doesn't give any guarantee about what happens to the dropped Arc is still problematic.

Well yes, that's the "what does drop() really do" issue I was mentioning.

Anyways.
Can you think of a solution?

Quoted 14 lines
Quoted 11 lines

It's really just about the question whether

rcu_read_lock()
let ptr = something_valid_for_a_grace_period; drop(something_valid_for_a_grace_period); // delayed by a GP do_stuff_with_(ptr);
rcu_read_unlock()

is dangerous or not.

It's more:

Quoted 9 lines

thread A | thread B | thread C

							|				|
rcu_read_lock()						|				|
 let ptr = something_valid_for_a_grace_period;		|				|
							| rcu_defer(drop(ptr))		|
 do_stuff_with_(ptr);					|				|
rcu_read_unlock()					|				|
							|				| drop(ptr)

Yep, that's what I meant, but was too lazy to draw it properly <.<

Quoted 5 lines

And yes, as long as the drop is always deferred I don't see an issue. But IIUC, you were trying to not defer the drop unless the F on DriverFenceData needs a drop. The fact DriverFenceData::fctx is also impacted by that immediate ::drop() means you have to defer the drop() unconditionally now.

I have to defer the drop() *of the FenceCtx* unconditionally. Which is fine, because how often does that happen?

Quoted 10 lines
Quoted 4 lines

Alice doesn't like it – but I do point out that the memory cannot go away. So in C this is perfectly safe.

It might work, but it's very very fragile, to say the least. Honestly, the more I think about it, the more I believe we should defer the drop unconditionally and call it a day. Yes, it's a potential overhead, but it's, IMHO, way better than relying on some Arc implementation details that might change at some point.

That or perhaps Revocable. But since Revocable also just defers unconditionally AFAICT…

That said, is it legal to always wait a grace period?

Quoted 26 lines
Quoted 26 lines

About Rust, I don't know.

Quoted 21 lines
Quoted 21 lines

That leaves those that are not called with the lock held, and for which we rely on the RCU lock:

- ::get_{driver,timeline}_name() get back to the FenceCtx to get those names, and if the Arc<FenceCtx> inside the DriverFenceData is dropped, I guess it can't be trusted
- ::set_deadline() is problematic as well
- ::wait() and ::release() are deprecated

Quoted 4 lines

The only way I can see to make that bullet proof would be to do synchronize_rcu() before the drop_in_place() above – but that would mean that we delay each fence drop by one grace period.

synchronize_rcu() in the DriverFence::drop() path is indeed not a great idea. Not only because it adds an heavy synchronization point in a potentially hot-path, but also because you're back to a situation where DriverFence users need to be very careful about the context they are when they drop these objects, and because drop() is an implicit operation most of the time, it becomes super fragile.

Quoted 5 lines
Quoted 2 lines

We declared it illegal to drop a fence without signalling it. So it's not thaaat implicit.

What's implicit is that ::drop() is implicitly called when the object goes out of scope.

Ack.

Quoted 6 lines
Quoted 6 lines

Moreover, the current suggestion for v2 is that we force the user to obey to our safety requirements:

Quoted 9 lines
Quoted 2 lines

/// Trait to ensure that the fence implementation can safely drop the user data /// passed in [FenceCtx::new_fence_allocation()].

So, if your fence is the last owner of FenceCtx, it drops the last ref, and FenceCtx::drop() is called. In there, I guess the first thing you do in FenceCtx::drop() which waits for a grace period to expire which might force an unexpected blocking operation in the otherwise super trivial atomic-safe FenceData::drop() path.

IF the driver has already dropped its separate reference to FenceCtx, which it must have around somewhere, no?

So that could only happen if a FenceCtx gets dropped *before* the last fence. That would only happen if the driver is tearing down, typically.

But to be safe we could also demand the trait on FenceCtx::data.

Quoted 6 lines

Plus, then you have
to drop FenceCtx::data, which might or might not be SafeToDrop.

This is exactly the sort of complications I wanted to avoid initially, and this is why I still think letting the user tell when it's DriverFenceSafeToDrop is just calling for more pain.

The trait is at the very least useful for documenting what the driver must not do in its data::drop().

Quoted 20 lines
Quoted 20 lines

///
/// # Safety
///
/// You must only implement this trait for your type if the following rules are /// obeyed to:
///
/// 1. Your type either does not need [Drop] at all, OR drops only after 1 RCU ///    grace period has passed.
/// 2. If present, your [Drop] implementation performs no operations illegal in ///    atomic context, such as allocating with GFP_KERNEL.
pub unsafe trait DriverFenceSafeToDrop {}

https://gitlab.freedesktop.org/pstanner/linux-drm-work/-/blob/jobqueue-dma-fence-v2/rust/kernel/dma_buf/dma_fence.rs?ref_type=heads#L512

That way we hit several birds with one stone. We get our delayed-by-a- grace-period drop, *and* we can very explicitly document: "you must not do atomic-hostile stuff in drop()".

Quoted 6 lines
Quoted 1 line

Pretty cool, right? :]

Except you don't know. Now you have to carefully audit

- FenceCtx::drop()
- FenceCtx::data::drop()

Not our responsibility. Whoever implements the trait needs to audit that.

The alternative is to stuff the data into an RcuBox unconditionally, if it does implement a drop().

Quoted 5 lines

to guarantee that this is safe. Because with this API, there's no guarantee that the FenceCtx outlives the fences it emitted. BTW, maybe we should enforce that with lifetimes instead of having an Arc<FenceCtx> in DriverFenceData.

What. How is that not enforced?

Each DriverFence refcounts the FenceCtx. It is impossible for the FenceCtx to disappear before a DriverFence.

Quoted 21 lines
Quoted 21 lines
Quoted 7 lines
Quoted 7 lines

If we're going to force this synchronize_rcu(), I think it should be done when the FenceCtx is dropped, meaning we're back to a situation where DriverFenceData::data is also RCU-deferred with an rcu_call(), which, IIRC, was the original idea.

See the trait, yes.

Quoted 10 lines
Quoted 10 lines

Quoted 4 lines

I'm still not sure how real the problem really is, though. Because refcounting guards, and drop() doesn't actually *do* something to the pointers, or does it?

It doesn't do anything to the dma_fence part of the DriverFenceData, but it can touch the driver-specific part, and most importantly, it might leave it with stale information that are then re-interpreted by the dma_fence_ops callback that's still in-flight.

Quoted 6 lines
Quoted 1 line

Hmm no, I think this is all safe.

Well, this might be safe (baring the Arc invalidation I mentioned), but also super convoluted design that's hard to audit and maintain overtime. I'm very skeptical that this is what we want to start with just to make sure we optimize the case where no drop on fence data is needed.

It is super convoluted.

Please remember that we started out with a far simpler design, but the demand was made that we must already prepare for future support of is_signaled() callback & partner.

Without the hw_signalling callbacks, we simply had

Fence
|
| refcounts
V
DriverFence

both of which lived in their own allocations. Was super simple, well maintainable.

I think we are approaching the point where we simply have "to die one [of many possible] deaths" (a german saying)

Quoted 7 lines
Quoted 5 lines

It's really about the question whether you are allowed to access a pointer in Rust that has been dropped, even if you can guarantee that it is still valid after dropping (for a grace period).

An Arc, I don't think so.

I don't know. See further above. The Arc's memory cannot be touched by anyone, cannot be reused.

P.

#41 lore raw

Re: [PATCH 3/4] rust: Add dma_fence abstractions

Quoted 61 lines · On Wed, 03 Jun 2026 13:49:19 +0200 Philipp Stanner <[email protected]> wrote:
Quoted 52 lines
Quoted 46 lines
Quoted 37 lines
Quoted 37 lines
Quoted 36 lines
Quoted 16 lines

- channel is basically the FenceCtx in your new design
- head is used to insert the fence in the fence context list. Not too sure how it translates in rust (insertion in an XArray instead?). Anyway, that's just stuff you need to make your fence part of the context timeline, so it's again related to how you link a fence to its context and let the context walk back pending fences when it needs to. Feels like my original FenceTimeline abstract proposal [2] could help make this thing generic, but let's reconsider this once we've got the basics sorted out, please
- timeout is probably the only thing of interest here, assuming things can be signaled out of order (if they are signaled in order, like is the case in Tyr, the timeout can just be on the FenceCtxData and represent the timeout of the first fence in the set). Anyway, that's a scalar, so no fancy ::drop() needed if that's what you're worried about

What we / Alice are worried about is multiple things.

One issue is that Alice believes that

        // SAFETY: self.data is owned by the DriverFence, but could be accessed // through some dma_fence callbacks right now. Access is being revoked // above by signalling the fence. The DriverFenceSafeToDrop trait // ensures that the data either does not need drop, or if it does it // lives in a RcuBox which will delay dropping by one grace period, hence // ensuring that all readers have disappeared.
        unsafe { drop_in_place(self.data.as_ptr()) };

from DriverFence::drop() invalidates the pointers that thread A accesses, even if DriverFence::data.data's drop is delayed with RCU.

Now if thread B drops DriverFence, thread A could use invalid pointers, but while still operating on non-deallocated memory (because our refcounting guards against that).

Quoted 8 lines
Quoted 8 lines

This is where I think our very permissive dma_fence callback design is hitting us in the face. There are callbacks that are called with the fence lock held (::enable_signalling(), ::signaled()), and because we forcibly signal on DriverFence::drop() (which requires locking as well) and then drop the DriverFenceData::data (I'd expect DriverFenceData::fctx to be manually dropped too, but I don't see that done in the current version), the problem you're describing doesn't exist, I think.

Quoted 5 lines

The problem of *nominally* those Rust pointers / references in DriverFenceData becoming invalid does exist.

But I tend to believe that this is a syntactic theoretical problem. All the memory is still valid.

Quoted 8 lines

But accessing an Arc<Xx> that has been dropped is invalid. You just basically rely on the implementation leaving the memory as is after it dropped the ref it had, but there's nothing preventing it from zero-ing the Arc<>, at which point this becomes a NULL deref. Also, when you drop DriverFenceData::fctx, you might very well be the last FenceCtx owner, so FenceCtx can go away at this point, meaning your in-flight dma_fence_ops::{driver,timeline}_name() callbacks might now point to memory that has been returned to the system and lead to a UAF.

Quoted 2 lines

Nope.
The Arc<fctx> can earliest disappear once the DriverFence dropped.

Yes, and there's nothing retaining DriverFence if some dma_fence_ops are in-flight, because DriverFence is not in charge of this aspect.

Quoted 4 lines

The
Arc<fctx> has been allocated with DriverFenceAllocation. That memory only does become re-usable once dma_fence_put() in DriverFence::drop() releases the memory.

You're talking about the memory backing DriverFenceData, and I'm talking about what happens when drop_in_place(fence.data) is called. In that case, the DriverFenceData object is called. Because there's no explicit Drop implementation for DriverFenceData, the compiler will drop each field in the order they are defined:

1. inner: Fence => NOP since that type just Opaque<dma_fence>
2. fctx: Arc<FenceCtx<F, C>> => so basically calling [1]. Yes the memory behind this Arc object remains accessible/valid, but doesn't change the fact drop() has been called on this Arc, and you're basically relying on the fact Arc::drop() leaves things intact such that further accesses to this Arc instance work properly.
3. data: F => what happens depends on the type itself

So let's assume data is safe to be dropped immediately, fctx is not, or rather it might be, but it's basically relying on current implementation details, which we shouldn't do, IMO.

Quoted 2 lines

A consuming / reading Fence also holds a refcount.

It hold a ref on the Fence object, which doesn't include ftcx (memory for fctx is there, but the field is not accessible).

Quoted 5 lines

The FenceCtx on the other hand delays its drop() by an RCU grace period. So until all accessors are gone.

Where is the problem?

The problem is, you're making assumptions about the Arc implementation that shouldn't be done, so you're basically violating the layering that exists between the API exposed by the Arc module and its internal implementation, which might evolve at any point.

Quoted 10 lines
Quoted 9 lines
Quoted 5 lines

The FenceCtx cannot disappear because its drop is delayed with synchronize_rcu() (previously rcu_barrier()).

Okay, so that protects against the UAF I was mentioning, but fact the Arc implementation doesn't give any guarantee about what happens to the dropped Arc is still problematic.

Quoted 5 lines

Well yes, that's the "what does drop() really do" issue I was mentioning.

Anyways.
Can you think of a solution?

There's two options, I think:

1. We always defer the drop on DriverFenceData through an rcu_call (I guess that's RcuBox in rust)

2. We use rust lifetime specifiers to ensure FenceCtx can't outlive the DriverFence it emitted, and we define fctx as fctx: &FenceCtx in the DriverFenceData object.

Quoted 27 lines
Quoted 15 lines
Quoted 5 lines

It's really just about the question whether

Quoted 7 lines

rcu_read_lock()
let ptr = something_valid_for_a_grace_period; drop(something_valid_for_a_grace_period); // delayed by a GP do_stuff_with_(ptr);
rcu_read_unlock()

is dangerous or not.

It's more:

Quoted 9 lines

thread A | thread B | thread C

							|				|
rcu_read_lock()						|				|
 let ptr = something_valid_for_a_grace_period;		|				|
							| rcu_defer(drop(ptr))		|
 do_stuff_with_(ptr);					|				|
rcu_read_unlock()					|				|
							|				| drop(ptr)

Yep, that's what I meant, but was too lazy to draw it properly <.<

Quoted 8 lines
Quoted 5 lines

And yes, as long as the drop is always deferred I don't see an issue. But IIUC, you were trying to not defer the drop unless the F on DriverFenceData needs a drop. The fact DriverFenceData::fctx is also impacted by that immediate ::drop() means you have to defer the drop() unconditionally now.

I have to defer the drop() *of the FenceCtx* unconditionally. Which is fine, because how often does that happen?

I'm talking about the drop() of DriverFenceData here. You have to defer it if you want to keep DriverFenceData::fctx valid until all callbacks have returned.

Quoted 12 lines
Quoted 11 lines
Quoted 5 lines

Alice doesn't like it – but I do point out that the memory cannot go away. So in C this is perfectly safe.

It might work, but it's very very fragile, to say the least. Honestly, the more I think about it, the more I believe we should defer the drop unconditionally and call it a day. Yes, it's a potential overhead, but it's, IMHO, way better than relying on some Arc implementation details that might change at some point.

Quoted 4 lines

That or perhaps Revocable. But since Revocable also just defers unconditionally AFAICT…

That said, is it legal to always wait a grace period?

To clean things up? I don't see why not. The main concern was that it would be overkill if DriverFenceData::data doesn't need to drop() or is proven to be safe against immediate drop()s. What I'm seeing here is that DriverFenceData::fctx is problematic too...

Quoted 16 lines
Quoted 6 lines
Quoted 6 lines

Moreover, the current suggestion for v2 is that we force the user to obey to our safety requirements:

Quoted 9 lines
Quoted 2 lines

/// Trait to ensure that the fence implementation can safely drop the user data /// passed in [FenceCtx::new_fence_allocation()].

So, if your fence is the last owner of FenceCtx, it drops the last ref, and FenceCtx::drop() is called. In there, I guess the first thing you do in FenceCtx::drop() which waits for a grace period to expire which might force an unexpected blocking operation in the otherwise super trivial atomic-safe FenceData::drop() path.

Quoted 2 lines

IF the driver has already dropped its separate reference to FenceCtx, which it must have around somewhere, no?

But there's nothing like that enforced at the API level, so there's potentially a case where the driver drop things in invalid order and FenceCtx gets dropped through one of its driver fences' ::drop() call.

Quoted 3 lines

So that could only happen if a FenceCtx gets dropped *before* the last fence.

FenceCtx can't be dropped before the last fence it emitted, but it can be dropped as part of the drop on the last fence that remains. That is, driver drops the FenceCtx, and only then drops the fences that were coming from this context.

Quoted 1 line

That would only happen if the driver is tearing down, typically.

Yes, and that's exactly the sort of mistake I'm hoping rust can help with. I'd like drivers to not be able to drop their FenceCtx before they gave up on all the fences that were emitted by this context, because otherwise it can lead to various tricky bugs.

Quoted 2 lines

But to be safe we could also demand the trait on FenceCtx::data.

Nope, DriverFenceData::ftcx is also problematic.

Quoted 10 lines
Quoted 7 lines

Plus, then you have
to drop FenceCtx::data, which might or might not be SafeToDrop.

This is exactly the sort of complications I wanted to avoid initially, and this is why I still think letting the user tell when it's DriverFenceSafeToDrop is just calling for more pain.

The trait is at the very least useful for documenting what the driver must not do in its data::drop().

Yeah, but if you have to defer DriverFenceData for the fctx anyway, is there point forcing more complexity to users...

Quoted 23 lines
Quoted 23 lines
Quoted 22 lines

///
/// # Safety
///
/// You must only implement this trait for your type if the following rules are /// obeyed to:
///
/// 1. Your type either does not need [Drop] at all, OR drops only after 1 RCU ///    grace period has passed.
/// 2. If present, your [Drop] implementation performs no operations illegal in ///    atomic context, such as allocating with GFP_KERNEL.
pub unsafe trait DriverFenceSafeToDrop {}

https://gitlab.freedesktop.org/pstanner/linux-drm-work/-/blob/jobqueue-dma-fence-v2/rust/kernel/dma_buf/dma_fence.rs?ref_type=heads#L512

That way we hit several birds with one stone. We get our delayed-by-a- grace-period drop, *and* we can very explicitly document: "you must not do atomic-hostile stuff in drop()".

Pretty cool, right? :]

Quoted 7 lines
Quoted 4 lines

Except you don't know. Now you have to carefully audit

- FenceCtx::drop()
- FenceCtx::data::drop()

Not our responsibility. Whoever implements the trait needs to audit that.

How can you say what happens in FenceCtx::drop() is not your responsibility. Or even that DriverFenceSafeToDrop must account for both FenceCtx::data::drop() and DriverFenceData::drop() without making this a nightmare for every user and/or maintainer that's going to audit user's code.

Come on, if we can't make this at least a tiny bit user-friendly we're just going to face the same mistakes that exist on the C side.

Quoted 3 lines

The alternative is to stuff the data into an RcuBox unconditionally, if it does implement a drop().

Yes, and I'd rather have that at first, because it's easier to add complexity to optimize a case we really know we need to optimize than go for the most optimal solution and then realize it generates a lot of problems.

Quoted 7 lines
Quoted 6 lines

to guarantee that this is safe. Because with this API, there's no guarantee that the FenceCtx outlives the fences it emitted. BTW, maybe we should enforce that with lifetimes instead of having an Arc<FenceCtx> in DriverFenceData.

Quoted 7 lines

What. How is that not enforced?

Each DriverFence refcounts the FenceCtx. It is impossible for the FenceCtx to disappear before a DriverFence.

It's possible for a partially dropped DriverFenceData to be used because of dma_buf_ops callbacks.

thread A                                                      |   thread B
                                                              |
DriverFence::drop()                                           |
  DriverFenceData::drop()                                     |
    DriverFenceData::fctx::drop() <- last ref on fctx         |
      FenceCtx::drop()                                        |
                                                              | rcu_lock()
         synchronize_rcu()                                    |
                                                              |   dma_fence_ops::xxx()
                                                              |     drv_fence = DriverFenceData from dma_fence
                                                              |     fctx = drv_fence.fctx => Invalid access to a dropped Arc
                                                              |     fctx.do_xxx() => call to a FenceCtx that's being dropped

I don't think I can be clearer than that.

And yes, the memory backing the FenceCtx is still there, but doesn't change the fact the object itself is going away, which is not great to have.

As for the argument that driver should hold on its FenceCtx until all the fences this FenceCtx emitted are dropped, I agree, but this should be enforced at the API level rather than just be a written rule somewhere, and rust lifetimes allows that...

Quoted 43 lines
Quoted 22 lines
Quoted 22 lines
Quoted 8 lines
Quoted 8 lines

If we're going to force this synchronize_rcu(), I think it should be done when the FenceCtx is dropped, meaning we're back to a situation where DriverFenceData::data is also RCU-deferred with an rcu_call(), which, IIRC, was the original idea.

See the trait, yes.

Quoted 10 lines
Quoted 10 lines

Quoted 4 lines

I'm still not sure how real the problem really is, though. Because refcounting guards, and drop() doesn't actually *do* something to the pointers, or does it?

It doesn't do anything to the dma_fence part of the DriverFenceData, but it can touch the driver-specific part, and most importantly, it might leave it with stale information that are then re-interpreted by the dma_fence_ops callback that's still in-flight.

Quoted 6 lines
Quoted 1 line

Hmm no, I think this is all safe.

Well, this might be safe (baring the Arc invalidation I mentioned), but also super convoluted design that's hard to audit and maintain overtime. I'm very skeptical that this is what we want to start with just to make sure we optimize the case where no drop on fence data is needed.

It is super convoluted.

Please remember that we started out with a far simpler design, but the demand was made that we must already prepare for future support of is_signaled() callback & partner.

Without the hw_signalling callbacks, we simply had

Fence
|
| refcounts
V
DriverFence

Quoted 6 lines

both of which lived in their own allocations. Was super simple, well maintainable.

I think we are approaching the point where we simply have "to die one [of many possible] deaths" (a german saying)

I don't think so. You seem to completely dismiss the two solutions I'm suggesting:

1/ leave with all DriverFenceData::drop() being deferred with RcuBox

or

2/ define lifetimes at the FenceCtx/DriverFence level, have a simple
ref (rather than an Arc) to the context, and let the compiler check that FenceCtx always outlives the fences it emitted

Quoted 11 lines
Quoted 8 lines
Quoted 6 lines

It's really about the question whether you are allowed to access a pointer in Rust that has been dropped, even if you can guarantee that it is still valid after dropping (for a grace period).

An Arc, I don't think so.

I don't know. See further above. The Arc's memory cannot be touched by anyone, cannot be reused.

I think I gave enough explanation on that issue above, if that's still unclear, let's discuss it on Zulip or over a call.

maintainer
#12 lore raw

Re: [PATCH 2/4] rust: rcu: add RcuBox type

In reply to Philipp Stanner
Quoted 8 lines · Philipp Stanner
Quoted 8 lines
Quoted 7 lines
Quoted 2 lines

and Philipp can use the RcuKBox in this patchset. We also need to impl InPlaceInit for RcuBox, but that can be added later.

So shall we merge my series with Alice's patch, and later we add your patch and other features, or would you prefer to have the additional boxes from your patch from the get-go?

Quoted 9 lines
Quoted 2 lines

I would like to have it from the get-go mainly because of RcuBox vs RcuKBox naming. Thank you!

Fine by me. Just process-wise: how should we do it?

I could include your patch on top of Alice's. Would be a bit more consistent regarding the git-workflow if we'd squash the two patches, but then you two would have to agree on authorship.

Keeping it as a separate patch is fine by me.

Regards,
Boqun

Quoted 3 lines

All is fine by me, but I wanted to ask instead of just do A or B.

[...]

maintainer
#42 lore raw

Re: [PATCH 3/4] rust: Add dma_fence abstractions

In reply to Boris Brezillon
Quoted 2 lines · Boris Brezillon

Honestly, the more I think about it, the more I believe we should defer the drop unconditionally and call it a day.

I think deferring is fine, a hard synchronization with synchronize_rcu() in DriverFence::drop() seems very undesirable though.

Another option would be to change dma_fence_is_signaled() to take the fence lock already for signaled(), which should be fine given that drivers should only ever check a sequence number.

It would account for the fact that the same race is possible on the C side. While the memory allocation is protected by RCU, the contents are not, and it seems realistic a driver could invalidate the contents of its private data after signaling the fence while signaled() callbacks are still in flight.

Note that there are no new constraints, signaled() is already called with RCU read lock held, plus there's also dma_fence_is_signaled_locked() which also calls signaled() with the fence lock held.

With this, the problem goes away, as it would guarantee that once we reach drop_in_place() in DriverFence::drop() it is impossible that there are still callback in flight.

#43 lore raw

Re: [PATCH 3/4] rust: Add dma_fence abstractions

Quoted 16 lines · On Wed, 03 Jun 2026 17:57:16 +0200 "Danilo Krummrich" <[email protected]> wrote:
Quoted 2 lines · Boris Brezillon

Honestly, the more I think about it, the more I believe we should defer the drop unconditionally and call it a day.

I think deferring is fine, a hard synchronization with synchronize_rcu() in DriverFence::drop() seems very undesirable though.

Another option would be to change dma_fence_is_signaled() to take the fence lock already for signaled(), which should be fine given that drivers should only ever check a sequence number.

It would account for the fact that the same race is possible on the C side. While the memory allocation is protected by RCU, the contents are not, and it seems realistic a driver could invalidate the contents of its private data after signaling the fence while signaled() callbacks are still in flight.

Quoted 7 lines

Note that there are no new constraints, signaled() is already called with RCU read lock held, plus there's also dma_fence_is_signaled_locked() which also calls signaled() with the fence lock held.

With this, the problem goes away, as it would guarantee that once we reach drop_in_place() in DriverFence::drop() it is impossible that there are still callback in flight.

If we can guarantee that no dma_fence_ops we ever consider implementing in dma_fence.rs will be called after we've signaled the fence, that's even better, but it's not that clear to me that it works just for the callbacks we already implement (get_{driver,timeline}_name()). Take dma_fence_describe() for instance, with your solution the dma_fence_is_signaled() call in there would take/release the fence lock unconditionally, but it's still racy, because the fence might be signaled just after the lock is released and we would consider it valid to call ->get_{driver,timeline}_name() and use the returned strings even though the fence was effectively signaled in the meantime. We can of course fix that by forcing dma_fence_describe() to take the fence lock around the whole section where it's retrieving/using the driver/timeline strings, but I'm pretty sure this has been considered and dismissed by Christian.

I think we can't have it all, either we consider RCU deferral cheap enough that we can live with the scheduling overhead + the fact we might hold on system resources a bit longer. Or we consider DriverFence release a hot-path where resources should be returned as soon as possible, and we need to protect dma_fence::ops with a dedicated rwlock_t.

#60 lore raw

Re: [PATCH 3/4] rust: Add dma_fence abstractions

In reply to Philipp Stanner

Hi Philipp,

As I said on the cover letter, this works on Tyr (with our own JobQueue), but there are a few things to point out.

Quoted 216 lines · Philipp Stanner

C's dma_fence's are synchronisation primitives that will be needed by all Rust GPU drivers.

The dma_fence framework sets a number of rules, notably:
- fences must only be signalled once
- all fences must be signalled at some point
- fence error codes must only be set before signalling
- every pointer to a fence must be backed by a reference

All those rules are being addressed by these abstractions.

To cleanly decouple fence issuers and consumers, two types are provided:
- DriverFence: the only fence type that can be signalled and that carries driver-specific data.
- Fence: the fence type to be shared with other drivers and / or userspace. The only type callbacks can be registered on. Cannot be signalled.

Hereby, a Fence lives in the same chunk of memory as a DriverFence. Both share the refcount of the underlying C dma_fence. Since this implementation does not provide a custom dma_fence_backend_ops.release() function, the memory is freed by the dma_fence backend once the refcount drops to 0.

To create a DriverFence, the user must first allocate a DriverFenceAllocation, so that the creation of the DriverFence later on can always succeed. Otherwise, deadlocks could occur if fences need to be created in a GPU job submission path.

Synchronization is ensured by the dma_fence backend.

All DriverFence's created through this abstraction must be signalled by the creator with an error code. In case a DriverFence drops without being signalled beforehand, it is signalled with -ECANCELLED as its error and a warning is printed. This allows the Rust abstraction to very cleanly decouple fence issuer and consumer by relying on the decoupling mechanisms in the C backend, which ensures through RCU and the 'signalled' fence-flag that dma_fence_backend_ops functions cannot access the potentially unloaded driver code anymore.

Signalling fences on drop thus grants many advantages. Not signalling fences on drop would risk deadlock and does not grant real advantages:
By definition only the drivers can ensure that a fence always represents the hardware's state correctly.

This implementation models a DmaFenceCtx (fence context) object on which fences are to be created, thereby ensuring correct sequence numbering according to the timeline.

dma_fence supports a variety of callbacks. The mandatory callbacks (get_timeline_name() and get_driver_name()) are implemented in this patch. For convenience, they store those name parameters in the fence context, saving the driver from implementing these two callbacks.

Support for other callbacks (like for hardware signalling) is prepared for through the fact that both DriverFence and Fence live in the same allocation, allowing for usage of container_of from the callback to access the driver-specific data.

Synchronization for backend_ops callbacks is ensured through RCU which prevents UAF-bugs should a DriverFence drop while a Fence callback is currently operating on the associated driver data.

Add abstractions for dma_fence in Rust.

---

rust/bindings/bindings_helper.h  |   1 +
rust/helpers/dma_fence.c         |  48 ++
rust/helpers/helpers.c           |   1 +
rust/kernel/dma_buf/dma_fence.rs | 821 +++++++++++++++++++++++++++++++
rust/kernel/dma_buf/mod.rs       |  13 +
rust/kernel/lib.rs               |   1 +
6 files changed, 885 insertions(+)

create mode 100644 rust/helpers/dma_fence.c create mode 100644 rust/kernel/dma_buf/dma_fence.rs create mode 100644 rust/kernel/dma_buf/mod.rs

rust/bindings/bindings_helper.h +1 -0
index 2011645c7cfb..69daeb790f77 100644
@@ -52,6 +52,7 @@
 #include <linux/debugfs.h>
 #include <linux/device/faux.h>
 #include <linux/dma-direction.h>
+#include <linux/dma-fence.h>
 #include <linux/dma-mapping.h>
 #include <linux/dma-resv.h>
 #include <linux/errname.h>
rust/helpers/dma_fence.c new file +48 -0
new file mode 100644
index 000000000000..6244a5a61038
@@ -0,0 +1,48 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/dma-fence.h>
+
+__rust_helper void rust_helper_dma_fence_get(struct dma_fence *f)
+{
+ dma_fence_get(f);
+}
+
+__rust_helper void rust_helper_dma_fence_put(struct dma_fence *f)
+{
+ dma_fence_put(f);
+}
+
+__rust_helper bool rust_helper_dma_fence_begin_signalling(void)
+{
+ return dma_fence_begin_signalling();
+}
+
+__rust_helper void rust_helper_dma_fence_end_signalling(bool cookie)
+{
+ dma_fence_end_signalling(cookie);
+}
+
+__rust_helper bool rust_helper_dma_fence_is_signaled(struct dma_fence *f)
+{
+ return dma_fence_is_signaled(f);
+}
+
+__rust_helper bool rust_helper_dma_fence_is_signaled_locked(struct dma_fence *f)
+{
+ return dma_fence_is_signaled_locked(f);
+}
+
+__rust_helper void rust_helper_dma_fence_lock_irqsave(struct dma_fence *f, unsigned long *flags)
+{
+ dma_fence_lock_irqsave(f, *flags);
+}
+
+__rust_helper void rust_helper_dma_fence_unlock_irqrestore(struct dma_fence *f, unsigned long *flags)
+{
+ dma_fence_unlock_irqrestore(f, *flags);
+}
+
+__rust_helper void rust_helper_dma_fence_set_error(struct dma_fence *f, int error)
+{
+ dma_fence_set_error(f, error);
+}
rust/helpers/helpers.c +1 -0
index 625921e27dfb..d9114d0b3c8f 100644
@@ -57,6 +57,7 @@
 #include "cred.c"
 #include "device.c"
 #include "dma.c"
+#include "dma_fence.c"
 #include "dma-resv.c"
 #include "drm.c"
 #include "err.c"
rust/kernel/dma_buf/dma_fence.rs new file +52 -0
new file mode 100644
index 000000000000..7dc1f5c16b02
@@ -0,0 +1,821 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Copyright (C) 2025, 2026 Red Hat Inc.:
+//   - Philipp Stanner <[email protected]>
+
+//! DriverFence support.
+//!
+//! Reference: <https://docs.kernel.org/driver-api/dma-buf.html#c.dma_fence>
+//!
+//! C header: [`include/linux/dma-fence.h`](srctree/include/linux/dma-fence.h)
+
+use crate::{
+    alloc::AllocError,
+    bindings,
+    container_of,
+    error::to_result,
+    prelude::*,
+    sync::rcu::RcuBox,
+    types::ForeignOwnable,
+    types::Opaque,
+    warn_on, //
+};
+
+use pin_init::pin_init_from_closure;
+
+use core::{
+    marker::PhantomData, //
+    ops::Deref,
+    ptr,
+    ptr::{
+        drop_in_place,
+        NonNull, //
+    },
+    sync::atomic::{
+        AtomicU64,
+        Ordering, //
+    },
+};
+
+use bindings::ECANCELED;
+
+use kernel::str::CString;
+use kernel::sync::{
+    aref::{
+        ARef,
+        AlwaysRefCounted, //
+    },
+    Arc,
+    ArcBorrow, //
+};
+
+/// VTable for dma_fence backend_ops callbacks.

rust/kernel/dma_buf/dma_fence.rs line 62

Quoted 10 lines
rust/kernel/dma_buf/dma_fence.rs new file +10 -0
+//
+// Mandatory dma_fence backend_ops are implemented implicitly through
+// [`FenceCtx`]. Additional ones shall get implemented on this trait, which then
+// shall be demanded for the fence context data.
+pub trait FenceCtxOps {}
+
+/// A dma-fence context. A fence context takes care of associating related fences with each other,
+/// providing each with raising sequence numbers and a common identifier.
+#[pin_data(PinnedDrop)]
+pub struct FenceCtx<F: Send + Sync, C: Send + Sync> {

IMHO, I think we should avoid acronyms. This can be called “FenceContext” just fine.

Quoted 21 lines
rust/kernel/dma_buf/dma_fence.rs new file +21 -0
+    /// The fence context number.
+    nr: u64,
+    /// The sequence number for the next fence created.
+    seqno: AtomicU64,
+    // The name parameters live in RcuBox because they can be accessed by the
+    // dma_fence backend_ops. Those accesses are guarded by the rcu_read_lock(),
+    // so dropping them must be delayed by a grace period.
+    /// The name of the driver this FenceCtx's fences belong to.
+    driver_name: CString,
+    /// The name of the timeline this FenceCtx's fences belong to.
+    timeline_name: CString,
+    #[pin]
+    data: C,
+    fence_type: PhantomData<F>,
+}
+
+#[allow(unused_unsafe)]
+impl<F: Send + Sync + DriverFenceAllowedData, C: Send + Sync> FenceCtx<F, C> {
+    // This can later be extended as a vtable in case other parties need support
+    // for the more "exotic" callbacks.
+    const OPS: bindings::dma_fence_ops = bindings::dma_fence_ops {

rust/kernel/dma_buf/dma_fence.rs line 93

Quoted 10 lines
rust/kernel/dma_buf/dma_fence.rs new file +10 -0
+        get_driver_name: Some(Self::get_driver_name),
+        get_timeline_name: Some(Self::get_timeline_name),
+        enable_signaling: None,
+        signaled: None,
+        wait: None,
+        release: None,
+        set_deadline: None,
+    };
+
+    /// Create a new `FenceCtx`.

[FenceCtx] (or [FenceContext], if you agree with the change.

Quoted 12 lines
rust/kernel/dma_buf/dma_fence.rs new file +12 -0
+    pub fn new(
+        driver_name: CString,
+        timeline_name: CString,
+        data: impl PinInit<C>,
+    ) -> Result<Arc<Self>> {
+        let ctx = pin_init!(Self {
+            // SAFETY: `dma_fence_context_alloc()` merely works on a global atomic. Parameter `1`
+            // is the number of contexts we want to allocate.
+            nr: unsafe { bindings::dma_fence_context_alloc(1) },
+            seqno: AtomicU64::new(0),
+            driver_name,
+            timeline_name,

rust/kernel/dma_buf/dma_fence.rs line 115

Quoted 10 lines
rust/kernel/dma_buf/dma_fence.rs new file +10 -0
+            data <- data,
+            fence_type: PhantomData,
+        });
+
+        Arc::pin_init(ctx, GFP_KERNEL)
+    }
+
+    fn get_next_fence_seqno(&self) -> u64 {
+        self.seqno.fetch_add(1, Ordering::Relaxed)
+    }

I’d personally avoid using “get” prefixes in general in Rust.

Also be aware, in the future this will need to be pub(crate), i.e.:
as soon as you have syncobjs.

Quoted 140 lines
rust/kernel/dma_buf/dma_fence.rs new file +140 -0
+
+    /// Allocate the memory for a [`DriverFence`] and already store `data` inside.
+    ///
+    /// This is needed because many times, creation of a [`DriverFence`] must not
+    /// fail, and allocating might deadlock in some situations.
+    ///
+    /// The `data` you pass here must not perform any operations that are illegal
+    /// in atomic context in its [`Drop`] implementation.
+    pub fn new_fence_allocation(
+        self: ArcBorrow<'_, Self>,
+        data: F,
+    ) -> Result<DriverFenceAllocation<F, C>> {
+        let fctx = Arc::<Self>::from(self);
+
+        DriverFenceAllocation::new(fctx, data)
+    }
+
+    /// Create a new fence, consuming `data`.
+    ///
+    /// The fence will increment the refcount of the fence context associated with this
+    /// [`FenceCtx`].
+    pub fn new_fence(&self, memory: DriverFenceAllocation<F, C>) -> DriverFence<F, C> {
+        let seqno: u64 = self.get_next_fence_seqno();
+
+        // We feed the C dma_fence backend a NULL for the spinlock so that it
+        // uses per-fence locks automatically.
+        let null_ptr: *mut bindings::spinlock = ptr::null_mut();
+        let fence_ptr = memory.as_raw();
+        // SAFETY: `fence_ptr` has been created directly above. It will live
+        // at least as long as `Self`. The same applies to `&Self::OPS`.
+        unsafe { bindings::dma_fence_init(fence_ptr, &Self::OPS, null_ptr, self.nr, seqno) };
+
+        // A `DriverFenceAllocation`'s purpose is to carry allocated memory, so that
+        // `DriverFence`s can always be created without allocating. In this
+        // method, ownership over that memory is transferred to the new
+        // `DriverFence` and managed through refcounting. The C dma_fence
+        // backend will ultimately free the memory once the refcount reaches 0.
+        let ptr = KBox::into_raw(memory.data);
+        // SAFETY: `ptr` was just created validly directly above.
+        let ptr = unsafe { NonNull::new_unchecked(ptr) };
+
+        DriverFence { data: ptr }
+    }
+
+    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()
+    }
+
+    extern "C" fn get_timeline_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.timeline_name.as_char_ptr()
+    }
+
+    /// Create a [`FenceCtx`] from an associated [`bindings::dma_fence`].
+    ///
+    /// # Safety
+    ///
+    /// `ptr` must be a valid pointer to a dma_fence which resides within a [`Fence`],
+    /// which in turn resides in a [`DriverFenceData`].
+    unsafe fn from_raw_fence<'a>(ptr: *mut bindings::dma_fence) -> &'a Self {
+        let opaque_fence = Opaque::cast_from(ptr);
+
+        // SAFETY: Safe due to the function's overall safety requirements.
+        let fence_ptr = unsafe { container_of!(opaque_fence, Fence, inner) };
+
+        // DriverFenceData is repr(C) and a Fence is its first member.
+        let fence_data_ptr = fence_ptr as *mut DriverFenceData<F, C>;
+
+        // SAFETY: Safe because of the safety comment directly above.
+        let fence_data = unsafe { &*fence_data_ptr };
+
+        &fence_data.fctx
+    }
+}
+
+// FenceCtx's drop() ensures that the driver cannot unload while there are still
+// dma_fence callbacks running. This also prevents UAF problems with fctx.driver_name
+// and fctx.timeline_name.
+//
+// DriverFence data gets dropped through call_rcu() in DriverFence::drop.
+// This `rcu_barrier()` also serves to wait for their completion.
+#[pinned_drop]
+impl<F: Send + Sync, C: Send + Sync> PinnedDrop for FenceCtx<F, C> {
+    fn drop(self: Pin<&mut Self>) {
+        // SAFETY: `rcu_barrier()` is always safe to be called.
+        unsafe { bindings::rcu_barrier() };
+    }
+}
+
+/// Error type for fence callback registration.
+///
+/// Generic over `T` so that `AlreadySignaled` can return the callback to the
+/// caller, allowing it to reclaim any resources owned by the callback (e.g.,
+/// a fence handle that needs to be signaled).
+#[derive(Debug)]
+pub enum CallbackError<T = ()> {
+    /// The fence was already signaled. The callback is returned so the caller
+    /// can extract owned resources without losing them.
+    AlreadySignaled(T),
+    /// Some other error occurred during registration.
+    Other(Error),
+}
+
+impl<T> From<CallbackError<T>> for Error {
+    fn from(err: CallbackError<T>) -> Self {
+        match err {
+            CallbackError::AlreadySignaled(_) => ENOENT,
+            CallbackError::Other(e) => e,
+        }
+    }
+}
+
+impl<T> From<AllocError> for CallbackError<T> {
+    fn from(e: AllocError) -> Self {
+        CallbackError::Other(Error::from(e))
+    }
+}
+
+/// Trait for callbacks that can be registered on fences.
+///
+/// When the fence signals, the callback will be invoked.
+///
+/// # Example
+///
+/// ```rust
+/// use kernel::dma_buf::FenceCb;
+///
+/// struct MyCallback {
+///     // Your callback state here

rust/kernel/dma_buf/dma_fence.rs line 265

Quoted 10 lines
rust/kernel/dma_buf/dma_fence.rs new file +10 -0
+/// }
+///
+/// impl FenceCb for MyCallback {
+///     fn called(&mut self) {
+///         pr_info!("Fence signaled!");
+///         // Handle fence completion
+///     }
+/// }
+/// ```
+pub trait FenceCb: Send + 'static {

Same here, this can be called “FenceCallback” just fine.

rust/kernel/dma_buf/dma_fence.rs line 276

Quoted 11 lines
rust/kernel/dma_buf/dma_fence.rs new file +11 -0
+    /// Called when the fence is signaled.
+    ///
+    /// This is called from the fence signaling path, which may be in interrupt
+    /// context or with locks held, which is why `self` is only borrowed, so that
+    /// it cannot drop. Implementations must not sleep or perform
+    /// long-running operations.
+    ///
+    /// An implementation likely wants to inform itself (e.g., through a work item)
+    /// within this callback that the associated [`FenceCbRegistration`] can now be
+    /// dropped.
+    fn called(&mut self);

This is a central point. We ideally would want this to consume self, because we may want to move things out of the callback.

Consider a fence design where signal() consumes self. Now consider this:

```
impl FenceCb for MyCallback {
 fn called(&mut self) {
   // Can't move the fence out, so we have to put an Option<T> just to be able
   // to move.
   if let Some(f) = self.some_fence.take() {
     f.signal();
   }
}
```

This used to be the case when our version of the job queue used the "proxy fence" design:

```
// Callback on the hw fence
impl FenceCb for MyCallback {
 fn called(&mut self) {
   if let Some(f) = self.submit_fence.take() {
     f.signal();
   }
}
```

Although this is not the case anymore, since we phased out this design given Christian's recent work. Still, we should ideally not require Option<T> here in general just to make resource transfer possible.

Quoted 160 lines
rust/kernel/dma_buf/dma_fence.rs new file +160 -0
+}
+
+/// A callback registration on a fence.
+///
+/// When this object is dropped, the callback is automatically removed if it
+/// hasn't been called yet.
+///
+/// # Invariants
+///
+/// If `callback` is `Some`, then `cb` is registered with the fence and the
+/// callback hasn't been invoked yet. If `None`, the callback has been invoked
+/// or the fence was already signaled when we tried to register.
+#[pin_data(PinnedDrop)]
+pub struct FenceCbRegistration<T: FenceCb + 'static> {
+    #[pin]
+    cb: Opaque<bindings::dma_fence_cb>,
+    callback: T,
+    fence: ARef<Fence>,
+}
+
+impl<T: FenceCb> FenceCbRegistration<T> {
+    /// Register a callback on a fence.
+    ///
+    /// On success the callback is pinned in place and will fire when the fence
+    /// signals. On `AlreadySignaled` the callback is returned to the caller so
+    /// that owned resources can be reclaimed.
+    pub fn new<'a>(fence: &'a Fence, callback: T) -> impl PinInit<Self, CallbackError<T>> + 'a
+    where
+        T: 'a,
+    {
+        // Uses `pin_init_from_closure` instead of `try_pin_init!` so that on
+        // `-ENOENT` (already signaled) the callback can be read back from the
+        // partially-initialized slot and returned through the error.
+        //
+        // SAFETY: `pin_init_from_closure` requires:
+        // - On `Ok(())`: the slot is fully initialized and valid for `Drop`.
+        // - On `Err(_)`: the slot is clean, i.e.: no partially-initialized fields
+        //   remain, and the slot can be deallocated without dropping.
+        //
+        // We uphold this as follows:
+        // - On success: all three fields are initialized. Ok(()) is returned.
+        // - On ENOENT (already signaled): `callback` and `fence` are read back
+        //   from the slot via `ptr::read`, leaving the slot clean. `cb` was
+        //   initialized by `dma_fence_add_callback` (it calls
+        //   `INIT_LIST_HEAD(&cb->node)` even on error), but `cb` is
+        //   `Opaque<dma_fence_cb>` which has no `Drop`, so not dropping it is
+        //   fine. The callback is returned through `AlreadySignaled(T)`.
+        // - On other errors: same cleanup as ENOENT, error returned as
+        //   `Other(e)`.
+        unsafe {
+            pin_init_from_closure(move |slot: *mut Self| {
+                let slot_callback = &raw mut (*slot).callback;
+                let slot_fence = &raw mut (*slot).fence;
+                let slot_cb = &raw mut (*slot).cb;
+
+                // Write callback and fence first — must be visible before
+                // dma_fence_add_callback makes the registration live.
+                core::ptr::write(slot_callback, callback);
+                core::ptr::write(slot_fence, ARef::from(fence));
+
+                let ret = to_result(bindings::dma_fence_add_callback(
+                    fence.inner.get(),
+                    Opaque::cast_into(slot_cb),
+                    Some(Self::dma_fence_callback),
+                ));
+
+                match ret {
+                    Ok(()) => Ok(()),
+                    Err(e) => {
+                        // Read back what we wrote to leave the slot clean.
+                        let cb_back = core::ptr::read(slot_callback);
+                        let _fence_back = core::ptr::read(slot_fence);
+
+                        if e.to_errno() == ENOENT.to_errno() {
+                            Err(CallbackError::AlreadySignaled(cb_back))
+                        } else {
+                            Err(CallbackError::Other(e))
+                        }
+                    }
+                }
+            })
+        }
+    }
+
+    /// Raw dma fence callback that is called by the C code.
+    ///
+    /// # Safety
+    ///
+    /// This is only called by the dma_fence subsystem with valid pointers.
+    unsafe extern "C" fn dma_fence_callback(
+        _fence: *mut bindings::dma_fence,
+        cb: *mut bindings::dma_fence_cb,
+    ) {
+        let ptr = Opaque::cast_from(cb).cast_mut();
+
+        // SAFETY: All `cb` we can receive here have been created in such a way
+        // that they are embedded into a `FenceCbRegistration`. The backend
+        // ensures synchronisation so whoever holds the registration object
+        // cannot drop it while this code is running. See `FenceCbRegistration::drop`.
+        unsafe {
+            let reg: *mut Self = container_of!(ptr, Self, cb);
+
+            (*reg).callback.called();
+        }
+    }
+
+    /// Returns a reference to the fence this callback is registered on.
+    pub fn fence(self: Pin<&Self>) -> &Fence {
+        &self.get_ref().fence
+    }
+}
+
+#[pinned_drop]
+impl<T: FenceCb> PinnedDrop for FenceCbRegistration<T> {
+    fn drop(self: Pin<&mut Self>) {
+        // Always call dma_fence_remove_callback, even if `callback` has already
+        // been taken by `dma_fence_callback`.  This is necessary for
+        // synchronization: `dma_fence_remove_callback` acquires `fence->lock`,
+        // which ensures that any in-flight `dma_fence_signal` (which calls our
+        // callback while holding the same lock) has completed before we free
+        // the struct.
+        //
+        // Without this, Drop can race with a concurrent signal:
+        //   CPU0 (signal, lock held): take() -> signaled(fence_ref) (in progress)
+        //   CPU1 (drop): sees is_some()==false -> skips lock -> frees struct
+        //   CPU0: accesses fence_ref -> use-after-free
+        //
+        // When the callback has already fired, the signal path detached the
+        // list node via INIT_LIST_HEAD, so dma_fence_remove_callback just sees
+        // an empty node and returns false — the lock acquisition is the only
+        // thing that matters.
+        //
+        // SAFETY: The fence pointer is valid and the cb was initialized by
+        // dma_fence_add_callback during construction.
+        unsafe {
+            bindings::dma_fence_remove_callback(self.fence.as_raw(), self.cb.get());
+        }
+    }
+}
+
+// SAFETY: FenceCbRegistration can be sent between threads
+unsafe impl<T: FenceCb> Send for FenceCbRegistration<T> {}
+
+// SAFETY: &FenceCbRegistration can be shared between threads if &T can.
+unsafe impl<T: FenceCb> Sync for FenceCbRegistration<T> where T: Sync {}
+
+/// The receiving counterpart of a [`DriverFence`], designed to register callbacks
+/// on, check the signalled state etc. A [`Fence`] cannot be signalled.
+/// A [`Fence`] is always refcounted.
+pub struct Fence {
+    /// The actual dma_fence passed to C.
+    inner: Opaque<bindings::dma_fence>,
+}
+
+// SAFETY: Fences are literally designed to be shared between threads.
+unsafe impl Send for Fence {}
+// SAFETY: Fences are literally designed to be shared between threads.
+unsafe impl Sync for Fence {}
+
+impl Fence {

rust/kernel/dma_buf/dma_fence.rs line 446

Quoted 10 lines
rust/kernel/dma_buf/dma_fence.rs new file +10 -0
+    /// Check whether the fence was signalled at the moment of the function call.
+    pub fn is_signaled(&self) -> bool {
+        // SAFETY: self is by definition still valid. The backend ensures proper
+        // locking.
+        unsafe { bindings::dma_fence_is_signaled(self.as_raw()) }
+    }
+
+    fn as_raw(&self) -> *mut bindings::dma_fence {
+        self.inner.get()
+    }

Same here, this will need to be pub(crate) as soon as you have syncobjs, JFYI.

Quoted 107 lines
rust/kernel/dma_buf/dma_fence.rs new file +107 -0
+
+    /// Create a [`Fence`] from a raw C [`bindings::dma_fence`].
+    ///
+    /// # Safety
+    ///
+    /// `ptr` must point to an initialized fence that is embedded into a [`Fence`].
+    pub unsafe fn from_raw<'a>(ptr: *mut bindings::dma_fence) -> &'a Self {
+        // SAFETY: Safe as per the function's overall safety requirements.
+        unsafe { &*ptr.cast() }
+    }
+}
+
+// SAFETY: These implement the C backends refcounting methods which are proven to work correctly.
+unsafe impl AlwaysRefCounted for Fence {
+    fn inc_ref(&self) {
+        // SAFETY: `self.as_raw()` is a pointer to a valid `struct dma_fence`.
+        unsafe { bindings::dma_fence_get(self.as_raw()) }
+    }
+
+    /// # Safety
+    ///
+    /// `ptr`must be a valid pointer to a [`DriverFence`].
+    unsafe fn dec_ref(ptr: NonNull<Self>) {
+        // SAFETY: `ptr` is never a NULL pointer; and when `dec_ref()` is called
+        // the fence is by definition still valid.
+        let fence = unsafe { (*ptr.as_ptr()).inner.get() };
+
+        // SAFETY: Valid because `fence` was created validly above.
+        unsafe { bindings::dma_fence_put(fence) }
+    }
+}
+
+#[repr(C)] // Necessary to guarantee that `inner` always comes first so that we can cast.
+#[pin_data]
+struct DriverFenceData<F: Send + Sync, C: Send + Sync> {
+    #[pin]
+    /// The inner fence.
+    inner: Fence,
+    /// Pointer to access the FenceCtx. Useful for obtaining name parameters.
+    // The FenceCtx lives as long as at least all its fences, hence this is safe.
+    fctx: Arc<FenceCtx<F, C>>,
+    /// The API user's data. As required by [`DriverFenceAllowedData`], this either
+    /// does not need drop, or must live in a [`rcu::RcuBox`]. It is essential
+    /// that the data only performs operations legal in atomic context in its
+    /// [`Drop`] implementation.
+    #[pin]
+    data: F,
+}
+
+/// A trait to enforce that all data in a [`DriverFence`] either does not need
+/// drop, or lives in a [`RcuBox`].
+pub trait DriverFenceAllowedData: private::Sealed {}
+
+mod private {
+    pub trait Sealed {}
+}
+
+impl<F: Copy> DriverFenceAllowedData for F {}
+impl<F: Send> DriverFenceAllowedData for RcuBox<F> {}
+
+impl<F: Copy> private::Sealed for F {}
+impl<F: Send> private::Sealed for RcuBox<F> {}
+
+/// A synchronization primitive mainly for GPU drivers.
+///
+/// Fences are always reference counted. The typical use case is that one side registers
+/// callbacks on the fence which will perform a certain action (such as queueing work) once the
+/// other side signals the fence.
+///
+/// # Examples
+///
+/// ```
+/// use kernel::dma_buf::{DriverFence, FenceCtx, FenceCb, FenceCbRegistration};
+/// use kernel::str::CString;
+/// use kernel::sync::{
+///     aref::ARef,
+///     rcu::RcuBox, //
+/// };
+/// use core::ops::Deref;
+/// use core::fmt::Display;
+///
+/// struct CallbackData { }
+///
+/// impl FenceCb for CallbackData {
+///     fn called(&mut self) {
+///         pr_info!("DmaFence callback executed.\n");
+///     }
+/// }
+///
+/// let driver_name = CString::try_from_fmt(fmt!("dummy_driver"))?;
+/// let timeline_name = CString::try_from_fmt(fmt!("dummy_timeline"))?;
+///
+/// let fctx = FenceCtx::new(driver_name, timeline_name, ())?;
+///
+/// let fence_data = CString::try_from_fmt(fmt!("dummy_data"))?;
+/// // DriverFence::data must either not need drop, or live in an RcuBox.
+/// let fence_data = RcuBox::new(fence_data, GFP_KERNEL)?;
+///
+/// let fence_alloc = fctx.as_arc_borrow().new_fence_allocation(fence_data)?;
+/// let mut fence = fctx.new_fence(fence_alloc);
+///
+/// let cb_data = CallbackData { };
+/// let waiting_fence = ARef::from(fence.as_fence());
+/// let cb_reg = FenceCbRegistration::new(&waiting_fence, cb_data);
+/// let cb_reg = KBox::pin_init(cb_reg, GFP_KERNEL)?;
+///
+/// // DriverFence implements Deref.

rust/kernel/dma_buf/dma_fence.rs line 563

Quoted 10 lines
rust/kernel/dma_buf/dma_fence.rs new file +10 -0
+/// // FIXME: unit test claims that CString does not implement Display. Why?
+/// // pr_info!("Fence's inner data is: {}", fence.deref().deref());
+///
+/// // TODO begin_signalling
+/// fence.signal(Ok(()));
+/// assert_eq!(waiting_fence.is_signaled(), true);
+///
+/// Ok::<(), Error>(())
+/// ```
+pub struct DriverFence<F: Send + Sync, C: Send + Sync> {

C is () on Tyr, yet we have to drag this extra generic everywhere in the API. I feel this is a bit of an ergonomics regression given our version on tyr-dev, and it will get worse if we add a third generic later for the typestate.

As I said before, my concern is that this extra generic does not stay local to the fence layer. It leaks into APIs that do not actually care about it. For example, queue code and types like PreparedJob, SubmitResult, and similar types usually care about jobs plus fence behavior. They do not care about this C type enough to have it show up in the API.

With the extra generic on the fence, that context type starts showing up everywhere:

pub trait QueueOps {
type Job;
type FencePayload;
type FenceCtxData;

fn submit(

       &self,
       job: &Self::Job,
       fence: DriverFence<Self::FencePayload, Self::FenceCtxData>,

) -> Result<SubmitResult<Self::FencePayload, Self::FenceCtxData>>;
}

pub enum SubmitResult<F, C> {
Submitted,
NoResources(DriverFence<F, C>),
}

pub struct PreparedJob<J, F, C> {
job: Arc<J>,
fence: DriverFence<F, C>,
}

// etc

rust/kernel/dma_buf/dma_fence.rs line 567

Quoted 4 lines
rust/kernel/dma_buf/dma_fence.rs new file +4 -0
+    /// The actual content of the fence. Lives in a raw pointer so that its
+    /// memory can be managed independently. Valid until both the [`DriverFence`]
+    /// and all associated [`Fence`]s have disappeared.
+    data: NonNull<DriverFenceData<F, C>>,

We used to have a ManuallyDrop here, I wonder why you decided to move away from that? I'm asking because now, the lifetime is not explicit on the types, and now you have to manually implement them correctly. Which you do, but it's easier to get wrong.

rust/kernel/dma_buf/dma_fence.rs line 570

Quoted 3 lines
rust/kernel/dma_buf/dma_fence.rs new file +3 -0
+}
+
+/// A pre-prepared DMA fence, carrying the user's data and the memory it and the

Not sure what pre-prepared means here. I guess the useful fact is that dma_fence_init has not been called, hence “UninitFence” being a better name IMHO.

But...I guess this is a matter of opinion, so no big deal either way.

Quoted 62 lines
rust/kernel/dma_buf/dma_fence.rs new file +62 -0
+/// fence reside in. Only useful for creating a [`DriverFence`]. Splitting
+/// allocation and full initialization is necessary because fences cannot be
+/// allocated dynamically in some circumstances (deadlock).
+pub struct DriverFenceAllocation<F: Send + Sync, C: Send + Sync> {
+    /// The memory for the actual content of the fence.
+    /// Handed over to a [`DriverFence`], or deallocated once the
+    /// [`DriverFenceAllocation`] drops.
+    data: KBox<DriverFenceData<F, C>>,
+}
+
+impl<F: Send + Sync + DriverFenceAllowedData, C: Send + Sync> DriverFenceAllocation<F, C> {
+    /// Create a new allocation slot that can later be used to create a fully
+    /// initialized [`DriverFence`] without the need to allocate.
+    pub fn new(fctx: Arc<FenceCtx<F, C>>, data: F) -> Result<Self> {
+        let fence_data = DriverFenceData {
+            // `inner` remains uninitialized until a [`DriverFence`] takes over.
+            inner: Fence {
+                inner: Opaque::uninit(),
+            },
+            fctx,
+            data,
+        };
+
+        // In order to support the C dma_fence callbacks, it is necessary for
+        // a `Fence` and a `DriverFence` to live in the same allocation,
+        // because the C backend passes a dma_fence, from which the driver most
+        // likely wants to be able to access its `data` in `DriverFence`.
+        //
+        // Hence, we need the manage the memory manually. It will be freed by the
+        // C backend automatically once the refcount within `Fence` drops to 0.
+        let data = KBox::new(fence_data, GFP_KERNEL | __GFP_ZERO)?;
+
+        Ok(Self { data })
+    }
+
+    fn as_raw(&self) -> *mut bindings::dma_fence {
+        self.data.inner.inner.get()
+    }
+}
+
+impl<F: Send + Sync, C: Send + Sync> DriverFence<F, C> {
+    fn as_raw(&self) -> *mut bindings::dma_fence {
+        // SAFETY: Valid because `self` is valid.
+        let fence_data = unsafe { &mut *self.data.as_ptr() };
+
+        fence_data.inner.inner.get()
+    }
+
+    /// Create a [`DriverFence`] from a raw pointer to a [`bindings::dma_fence`].
+    ///
+    /// # Safety
+    ///
+    /// `ptr` must be a valid pointer to a `dma_fence` that was obtained through
+    /// a [`DriverFence`] with matching generic data for both fence and associated
+    /// [`FenceCtx`].
+    unsafe fn from_raw(ptr: *mut bindings::dma_fence) -> Self {
+        let opaque_fence = Opaque::cast_from(ptr);
+
+        // SAFETY: Safe due to the function's overall safety requirements.
+        let fence_ptr = unsafe { container_of!(opaque_fence, Fence, inner) };
+
+        // DriverFenceData is repr(C) and a Fence is its first member.

rust/kernel/dma_buf/dma_fence.rs line 642

Quoted 10 lines
rust/kernel/dma_buf/dma_fence.rs new file +10 -0
+        let fence_data_ptr = fence_ptr as *mut DriverFenceData<F, C>;
+
+        // SAFETY: `fence_data_ptr` was created validly above.
+        let data = unsafe { NonNull::new_unchecked(fence_data_ptr) };
+
+        Self { data }
+    }
+
+    /// Return the underlying [`Fence`].
+    pub fn as_fence(&self) -> &Fence {

AsRef<Fence> ?

Quoted 38 lines
rust/kernel/dma_buf/dma_fence.rs new file +38 -0
+        // SAFETY: `self` is by definition still valid, and it cannot drop until
+        // this new reference is gone.
+        unsafe { Fence::from_raw(self.as_raw()) }
+    }
+
+    /// Signal the fence. This will invoke all registered callbacks.
+    pub fn signal(self, res: Result) {
+        let fence = self.as_raw();
+        let mut fence_flags: usize = 0;
+        let flag_ptr = &raw mut fence_flags;
+
+        // SAFETY: Once a `DriverFence` is initialized, the inner `fence` is
+        // valid and initialized. It is valid until the refcount drops
+        // to 0, which can earliest happen once the `DriverFence` has been dropped.
+        unsafe {
+            bindings::dma_fence_lock_irqsave(fence, flag_ptr);
+            if !bindings::dma_fence_is_signaled_locked(fence) {
+                if let Err(err) = res {
+                    bindings::dma_fence_set_error(fence, err.to_errno());
+                }
+                bindings::dma_fence_signal_locked(fence);
+            }
+            bindings::dma_fence_unlock_irqrestore(fence, flag_ptr);
+        }
+    }
+}
+
+// SAFETY: Fences are literally designed to be shared between threads.
+unsafe impl<F: Send + Sync, C: Send + Sync> Send for DriverFence<F, C> {}
+
+impl<F: Send + Sync, C: Send + Sync> Deref for DriverFence<F, C> {
+    type Target = F;
+
+    fn deref(&self) -> &Self::Target {
+        // SAFETY: Thanks to refcounting, `data` is always valid as long as `self` is.
+        let data = unsafe { &*self.data.as_ptr() };
+
+        &data.data

rust/kernel/dma_buf/dma_fence.rs line 690

Quoted 10 lines
rust/kernel/dma_buf/dma_fence.rs new file +10 -0
+    }
+}
+
+/// A borrowed [`DriverFence`]. All you can do with it is access your user data
+/// and obtain a [`Fence`].
+pub struct DriverFenceBorrow<F: Send + Sync, C: Send + Sync> {
+    /// The actual content of the fence. Lives in a raw pointer so that its
+    /// memory can be managed independently. Valid until both the [`DriverFence`]
+    /// and all associated [`Fence`]s have disappeared.
+    data: NonNull<DriverFenceData<F, C>>,

Same here, why no ManuallyDrop? Also, why no BorrowMut? I know we don’t _need_ one, but still...

Quoted 10 lines
rust/kernel/dma_buf/dma_fence.rs new file +10 -0
+}
+
+impl<F: Send + Sync, C: Send + Sync> Deref for DriverFenceBorrow<F, C> {
+    type Target = F;
+
+    fn deref(&self) -> &Self::Target {
+        // SAFETY: Thanks to refcounting, `data` is always valid as long as `self` is.
+        let data = unsafe { &*self.data.as_ptr() };
+
+        &data.data

rust/kernel/dma_buf/dma_fence.rs line 710

Quoted 10 lines
rust/kernel/dma_buf/dma_fence.rs new file +10 -0
+    }
+}
+
+impl<F: Send + Sync, C: Send + Sync> DriverFenceBorrow<F, C> {
+    fn as_raw(&self) -> *mut bindings::dma_fence {
+        // SAFETY: Valid because `self` is valid.
+        let fence_data = unsafe { &mut *self.data.as_ptr() };
+
+        fence_data.inner.inner.get()
+    }

Well this is Borrow, not BorrowMut. I think we should return a const pointer here.

rust/kernel/dma_buf/dma_fence.rs line 646

Quoted 7 lines
rust/kernel/dma_buf/dma_fence.rs new file +7 -0
+
+    /// Return the underlying [`Fence`].
+    pub fn as_fence(&self) -> &Fence {
+        // SAFETY: `self` is by definition still valid, and it cannot drop until
+        // this new reference is gone.
+        unsafe { Fence::from_raw(self.as_raw()) }
+    }

AsRef?

Quoted 138 lines
rust/kernel/dma_buf/dma_fence.rs new file +104 -0
+
+    /// Get a [`DriverFenceBorrow`] from a raw pointer.
+    ///
+    /// # Safety
+    ///
+    /// `ptr` must point to a raw dma_fence within a [`Fence`] within a [`DriverFenceData`].
+    unsafe fn from_raw(ptr: *mut bindings::dma_fence) -> Self {
+        let opaque_fence = Opaque::cast_from(ptr);
+
+        // SAFETY: Safe due to the function's overall safety requirements.
+        let fence_ptr = unsafe { container_of!(opaque_fence, Fence, inner) };
+
+        // DriverFenceData is repr(C) and a Fence is its first member.
+        let fence_data_ptr = fence_ptr as *mut DriverFenceData<F, C>;
+
+        // SAFETY: `fence_data_ptr` was created validly above.
+        let data = unsafe { NonNull::new_unchecked(fence_data_ptr) };
+
+        Self { data }
+    }
+}
+
+// SAFETY: The Rust dma_fence abstractions are already designed around the inner
+// C `dma_fence`, which can serve safely as the identification point when being
+// owned by C. Moreover, safety is ensured by not dropping `DriverFence` and by
+// only allowing operations without side effects on the Borrowed type.
+unsafe impl<F: Send + Sync + 'static, C: Send + Sync + 'static> ForeignOwnable
+    for DriverFence<F, C>
+{
+    // `DriverFence` is merely a wrapper around a raw pointer. Thus, we can just
+    // use it directly.
+    type Borrowed<'a> = DriverFenceBorrow<F, C>;
+    type BorrowedMut<'a> = DriverFenceBorrow<F, C>;
+
+    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.
+        core::mem::forget(fence);
+
+        ptr.cast()
+    }
+
+    unsafe fn from_foreign(ptr: *mut c_void) -> Self {
+        // SAFETY: Safe because the trait implementation only invokes this with
+        // a valid `ptr`, associated to a `DriverFence` with matching generic data.
+        unsafe { Self::from_raw(ptr.cast()) }
+    }
+
+    unsafe fn borrow<'a>(ptr: *mut c_void) -> Self::Borrowed<'a> {
+        // SAFETY: The trait implementation ensures that `ptr` always resides
+        // within a [`Fence`] within a [`DriverFenceData`].
+        unsafe { DriverFenceBorrow::from_raw(ptr.cast()) }
+    }
+
+    unsafe fn borrow_mut<'a>(ptr: *mut c_void) -> Self::BorrowedMut<'a> {
+        // SAFETY: The trait implementation ensures that `ptr` always resides
+        // within a [`Fence`] within a [`DriverFenceData`].
+        unsafe { DriverFenceBorrow::from_raw(ptr.cast()) }
+    }
+}
+
+impl<F: Send + Sync, C: Send + Sync> Drop for DriverFence<F, C> {
+    fn drop(&mut self) {
+        let fence = self.as_raw();
+        let mut fence_flags: usize = 0;
+        let flag_ptr = &raw mut fence_flags;
+
+        // SAFETY: Once a `DriverFence` is initialized, the inner `fence` is
+        // valid and initialized. It is valid until the refcount drops
+        // to 0, which can earliest happen once the `DriverFence` has been dropped.
+        unsafe {
+            bindings::dma_fence_lock_irqsave(fence, flag_ptr);
+            #[allow(unused_unsafe)]
+            if warn_on!(!bindings::dma_fence_is_signaled_locked(fence)) {
+                bindings::dma_fence_set_error(fence, ECANCELED as i32);
+                bindings::dma_fence_signal_locked(fence);
+            }
+            bindings::dma_fence_unlock_irqrestore(fence, flag_ptr);
+        }
+
+        // SAFETY: `self.data` is owned by the DriverFence, but could be accessed
+        // through some dma_fence callbacks right now. Access is being revoked
+        // above by signalling the fence. The DriverFenceAllowedData trait
+        // ensures that the data either does not need drop, or if it does it
+        // lives in a RcuBox which will delay dropping by one grace period, hence
+        // ensuring that all readers have disappeared.
+        unsafe { drop_in_place(self.data.as_ptr()) };
+
+        // SAFETY: Once a `DriverFence` is initialized, the inner `fence` is
+        // valid and initialized. It is valid until the refcount drops
+        // to 0, which can earliest happen once the `DriverFence` has been dropped.
+        unsafe {
+            bindings::dma_fence_put(fence);
+        }
+
+        // The actual memory the data associated with a `DriverFence` lives in
+        // gets freed by the C dma_fence backend once the fence's refcount reaches 0.
+    }
+}
rust/kernel/dma_buf/mod.rs new file +13 -0
new file mode 100644
index 000000000000..d9da3dc57fce
@@ -0,0 +1,13 @@
+// SPDX-License-Identifier: GPL-2.0 OR MIT
+
+//! DMA-buf subsystem abstractions.
+
+pub mod dma_fence;
+
+pub use self::dma_fence::{
+    DriverFence,
+    Fence,
+    FenceCb,
+    FenceCbRegistration,
+    FenceCtx, //
+};
rust/kernel/lib.rs +1 -0
index b72b2fbe046d..a05ccaa7598c 100644
@@ -63,6 +63,7 @@
 pub mod device_id;
 pub mod devres;
 pub mod dma;
+pub mod dma_buf;
 pub mod driver;
 #[cfg(CONFIG_DRM = "y")]
 pub mod drm;
Signature · 1 line
2.54.0
#45 lore raw

Re: [PATCH 3/4] rust: Add dma_fence abstractions

In reply to Danilo Krummrich
Quoted 7 lines · On Wed, 03 Jun 2026 17:57:16 +0200 "Danilo Krummrich" <[email protected]> wrote:
Quoted 2 lines · Boris Brezillon

Honestly, the more I think about it, the more I believe we should defer the drop unconditionally and call it a day.

I think deferring is fine, a hard synchronization with synchronize_rcu() in DriverFence::drop() seems very undesirable though.

Quoted 8 lines

Another option would be to change dma_fence_is_signaled() to take the fence lock already for signaled(), which should be fine given that drivers should only ever check a sequence number.

It would account for the fact that the same race is possible on the C side. While the memory allocation is protected by RCU, the contents are not, and it seems realistic a driver could invalidate the contents of its private data after signaling the fence while signaled() callbacks are still in flight.

One more thing: I'd assume that C drivers doing such an invalidation or returning resources that might be used after a signal or that could race with signalling would implement dma_fence_ops::release() and do this invalidation in their custom release() implementation. Otherwise, yes, the whole thing is broken there as well. In practice though, there's very little stored in the <driver>_dma_fence that's not directly related to signalling, and everything signalling related should have their callback called with the fence lock held. The only exceptions I could spot are the set_deadline() and the get_xxx_name() callbacks (::wait() and ::release() being deprecated).

maintainer
#14 lore raw

Re: [PATCH 2/4] rust: rcu: add RcuBox type

In reply to Philipp Stanner
Quoted 2 lines · Philipp Stanner

From: Alice Ryhl <[email protected]>

A few minor things below:

[...]

Quoted 6 lines
rust/kernel/sync/rcu/rcu_box.rs new file +0 -0
new file mode 100644
index 000000000000..2508fdb609ec

rust/kernel/sync/rcu/rcu_box.rs line 9

Quoted 9 lines
rust/kernel/sync/rcu/rcu_box.rs new file +9 -0
@@ -0,0 +1,145 @@
+// SPDX-License-Identifier: GPL-2.0
+
+// Copyright (C) 2026 Google LLC.
+
+//! Provides the `RcuBox` type for Rust allocations that live for a grace period.
+
+use core::{ops::Deref, ptr::NonNull};
+
+use kernel::{

Let's use crate:: here since RcuBox is part of the kernel crate.

Quoted 11 lines
rust/kernel/sync/rcu/rcu_box.rs new file +11 -0
+    alloc::{self, AllocError},
+    bindings,
+    ffi::c_void,
+    prelude::*,
+    sync::rcu::{ForeignOwnableRcu, Guard},
+    types::ForeignOwnable,
+};
+
+/// A box that is freed with rcu.
+///
+/// The value must be `Send`, as rcu may drop it on another thread.

rust/kernel/sync/rcu/rcu_box.rs line 30

Quoted 10 lines
rust/kernel/sync/rcu/rcu_box.rs new file +10 -0
+///
+/// # Invariants
+///
+/// * The pointer is valid and references a pinned `RcuBoxInner<T>` allocated with `kmalloc`.
+/// * This `RcuBox` holds exclusive permissions to rcu free the allocation.
+pub struct RcuBox<T: Send>(NonNull<RcuBoxInner<T>>);
+
+struct RcuBoxInner<T> {
+    value: T,
+    rcu_head: bindings::callback_head,

Probably should reorder these fields.

rust/kernel/sync/rcu/rcu_box.rs line 42

Quoted 12 lines
rust/kernel/sync/rcu/rcu_box.rs new file +12 -0
+}
+
+// Note that `T: Sync` is required since when moving an `RcuBox<T>`, the previous owner may still
+// access `&T` for one grace period.
+//
+// SAFETY: Ownership of the `RcuBox<T>` allows for `&T` and dropping the `T`, so `T: Send + Sync`
+// implies `RcuBox<T>: Send`.
+unsafe impl<T: Send + Sync> Send for RcuBox<T> {}
+
+// SAFETY: `&RcuBox<T>` allows for no operations other than those permitted by `&T`, so `T: Sync`
+// implies `RcuBox<T>: Sync`.
+unsafe impl<T: Send + Sync> Sync for RcuBox<T> {}

@Alice, we have T: Send mostly because RcuBox itself has the `T:
Send bound? I.e. the to be Sync we actually don't need T being Send`, right?

Regards,
Boqun

[...]

#61 lore raw

Re: [PATCH 3/4] rust: Add dma_fence abstractions

In reply to Daniel Almeida
Quoted 12 lines · On Wed, 3 Jun 2026 13:41:02 -0300 Daniel Almeida <[email protected]> wrote:

rust/kernel/dma_buf/dma_fence.rs line 276

Quoted 11 lines
rust/kernel/dma_buf/dma_fence.rs new file +11 -0
+    /// Called when the fence is signaled.
+    ///
+    /// This is called from the fence signaling path, which may be in interrupt
+    /// context or with locks held, which is why `self` is only borrowed, so that
+    /// it cannot drop. Implementations must not sleep or perform
+    /// long-running operations.
+    ///
+    /// An implementation likely wants to inform itself (e.g., through a work item)
+    /// within this callback that the associated [`FenceCbRegistration`] can now be
+    /// dropped.
+    fn called(&mut self);  
Quoted 2 lines

This is a central point. We ideally would want this to consume self, because we may want to move things out of the callback.

This one comes from me. The rationale being that ::called() is called from an atomic context, and the resources attached to the callback data might require acquiring other sleeping locks to be released, and sometimes you don't even notice immediately because said resources are refcounted, and the lock is only acquired when you happen to be the last owner. Yes, those can be caught at runtime if the C side is properly annotated with might_sleep(), but that's not always the case.

If we defer the drop of the data only when the FenceCb is dropped/recycled, we're at least not constrained by this "runs in atomic context" thing.

Quoted 18 lines

Consider a fence design where signal() consumes self. Now consider this:

```
impl FenceCb for MyCallback {
 fn called(&mut self) {
   // Can't move the fence out, so we have to put an Option<T> just to be able
   // to move.
   if let Some(f) = self.some_fence.take() {
     f.signal();
   }
}
```

This used to be the case when our version of the job queue used the "proxy fence" design:

Quoted 7 lines

```
// Callback on the hw fence
impl FenceCb for MyCallback {

 fn called(&mut self) {
   if let Some(f) = self.submit_fence.take() {
     f.signal();
   }

I'm pretty sure lockdep won't like it anyway, because this is nested locking of the same lock class. For such proxies, we'll need to teach lockdep about the nesting like has been recently done on dma_fence_array & co. But I'm digressing.

Quoted 6 lines

}
```

Although this is not the case anymore, since we phased out this design given Christian's recent work. Still, we should ideally not require Option<T> here in general just to make resource transfer possible.

I see. OTOH, don't we need to make this inner data movable if we want to cancel the FenceCb before the fence is signaled anyway? And that's most certainly a case we have in the teardown path.

maintainer
#13 lore raw

Re: [PATCH 2/4] rust: rcu: add RcuBox type

In reply to Boqun Feng
Quoted 12 lines · Boqun Feng
Quoted 11 lines · Philipp Stanner
Quoted 10 lines
Quoted 7 lines
Quoted 2 lines

and Philipp can use the RcuKBox in this patchset. We also need to impl InPlaceInit for RcuBox, but that can be added later.

So shall we merge my series with Alice's patch, and later we add your patch and other features, or would you prefer to have the additional boxes from your patch from the get-go?

I would like to have it from the get-go mainly because of RcuBox vs RcuKBox naming. Thank you!

Quoted 9 lines
Quoted 6 lines

Fine by me. Just process-wise: how should we do it?

I could include your patch on top of Alice's. Would be a bit more consistent regarding the git-workflow if we'd squash the two patches, but then you two would have to agree on authorship.

Keeping it as a separate patch is fine by me.

So is squashing ;-) Whichever is easy for you.

Regards,
Boqun

Quoted 7 lines

Regards,
Boqun

Quoted 3 lines

All is fine by me, but I wanted to ask instead of just do A or B.

[...]

#44 lore raw

Re: [PATCH 3/4] rust: Add dma_fence abstractions

In reply to Boris Brezillon
Quoted 27 lines · Boris Brezillon
Quoted 16 lines · On Wed, 03 Jun 2026 17:57:16 +0200 "Danilo Krummrich" <[email protected]> wrote:
Quoted 2 lines · Boris Brezillon

Honestly, the more I think about it, the more I believe we should defer the drop unconditionally and call it a day.

I think deferring is fine, a hard synchronization with synchronize_rcu() in DriverFence::drop() seems very undesirable though.

Another option would be to change dma_fence_is_signaled() to take the fence lock already for signaled(), which should be fine given that drivers should only ever check a sequence number.

It would account for the fact that the same race is possible on the C side. While the memory allocation is protected by RCU, the contents are not, and it seems realistic a driver could invalidate the contents of its private data after signaling the fence while signaled() callbacks are still in flight.

Quoted 7 lines

Note that there are no new constraints, signaled() is already called with RCU read lock held, plus there's also dma_fence_is_signaled_locked() which also calls signaled() with the fence lock held.

With this, the problem goes away, as it would guarantee that once we reach drop_in_place() in DriverFence::drop() it is impossible that there are still callback in flight.

Quoted 14 lines

If we can guarantee that no dma_fence_ops we ever consider implementing in dma_fence.rs will be called after we've signaled the fence, that's even better, but it's not that clear to me that it works just for the callbacks we already implement (get_{driver,timeline}_name()). Take dma_fence_describe() for instance, with your solution the dma_fence_is_signaled() call in there would take/release the fence lock unconditionally, but it's still racy, because the fence might be signaled just after the lock is released and we would consider it valid to call ->get_{driver,timeline}_name() and use the returned strings even though the fence was effectively signaled in the meantime. We can of course fix that by forcing dma_fence_describe() to take the fence lock around the whole section where it's retrieving/using the driver/timeline strings, but I'm pretty sure this has been considered and dismissed by Christian.

Then I would like to hear the reasons because what Danilo suggests sounds like an incredible clean thing to do. We'd get a bullet-proof synchronisation point.

It would solve all our problems regarding UAF. Only thing left to do would be to ensure atomic-friendly dropping of data.

P.

Quoted 7 lines

I think we can't have it all, either we consider RCU deferral cheap enough that we can live with the scheduling overhead + the fact we might hold on system resources a bit longer. Or we consider DriverFence release a hot-path where resources should be returned as soon as possible, and we need to protect dma_fence::ops with a dedicated rwlock_t.

#62 lore raw

Re: [PATCH 3/4] rust: Add dma_fence abstractions

In reply to Boris Brezillon
Quoted 30 lines · Boris Brezillon
Quoted 12 lines · On Wed, 3 Jun 2026 13:41:02 -0300 Daniel Almeida <[email protected]> wrote:

rust/kernel/dma_buf/dma_fence.rs line 276

Quoted 11 lines
rust/kernel/dma_buf/dma_fence.rs new file +11 -0
+    /// Called when the fence is signaled.
+    ///
+    /// This is called from the fence signaling path, which may be in interrupt
+    /// context or with locks held, which is why `self` is only borrowed, so that
+    /// it cannot drop. Implementations must not sleep or perform
+    /// long-running operations.
+    ///
+    /// An implementation likely wants to inform itself (e.g., through a work item)
+    /// within this callback that the associated [`FenceCbRegistration`] can now be
+    /// dropped.
+    fn called(&mut self);  
Quoted 2 lines

This is a central point. We ideally would want this to consume self, because we may want to move things out of the callback.

This one comes from me. The rationale being that ::called() is called from an atomic context, and the resources attached to the callback data might require acquiring other sleeping locks to be released, and sometimes you don't even notice immediately because said resources are refcounted, and the lock is only acquired when you happen to be the last owner. Yes, those can be caught at runtime if the C side is properly annotated with might_sleep(), but that's not always the case.

Quoted 4 lines

If we defer the drop of the data only when the FenceCb is dropped/recycled, we're at least not constrained by this "runs in atomic context" thing.

This design does not solve it, because one can quite trivially get around this restriction using Option<T> as I said. If your point is “don’t run any drop() here”, then &mut self doesn’t do it.

Quoted 26 lines
Quoted 18 lines

Consider a fence design where signal() consumes self. Now consider this:

```
impl FenceCb for MyCallback {
fn called(&mut self) {
  // Can't move the fence out, so we have to put an Option<T> just to be able
  // to move.
  if let Some(f) = self.some_fence.take() {
    f.signal();
  }
}
```

This used to be the case when our version of the job queue used the "proxy fence" design:

Quoted 7 lines

```
// Callback on the hw fence
impl FenceCb for MyCallback {
fn called(&mut self) {

  if let Some(f) = self.submit_fence.take() {
    f.signal();

}

Quoted 4 lines

I'm pretty sure lockdep won't like it anyway, because this is nested locking of the same lock class. For such proxies, we'll need to teach lockdep about the nesting like has been recently done on dma_fence_array & co. But I'm digressing.

Yeah, but this is more about resource transfer in general, not this pattern specifically.

I agree that this has issues, and yes, lockdep complained back then :)

Quoted 11 lines
Quoted 7 lines

}
```

Although this is not the case anymore, since we phased out this design given Christian's recent work. Still, we should ideally not require Option<T> here in general just to make resource transfer possible.

I see. OTOH, don't we need to make this inner data movable if we want to cancel the FenceCb before the fence is signaled anyway? And that's most certainly a case we have in the teardown path.

Can you expand a bit on what you mean here?

#63 lore raw

Re: [PATCH 3/4] rust: Add dma_fence abstractions

Quoted 28 lines · On Wed, 3 Jun 2026 21:43:05 -0300 Daniel Almeida <[email protected]> wrote:
Quoted 28 lines · Boris Brezillon
Quoted 12 lines · On Wed, 3 Jun 2026 13:41:02 -0300 Daniel Almeida <[email protected]> wrote:

rust/kernel/dma_buf/dma_fence.rs line 276

Quoted 11 lines
rust/kernel/dma_buf/dma_fence.rs new file +11 -0
+    /// Called when the fence is signaled.
+    ///
+    /// This is called from the fence signaling path, which may be in interrupt
+    /// context or with locks held, which is why `self` is only borrowed, so that
+    /// it cannot drop. Implementations must not sleep or perform
+    /// long-running operations.
+    ///
+    /// An implementation likely wants to inform itself (e.g., through a work item)
+    /// within this callback that the associated [`FenceCbRegistration`] can now be
+    /// dropped.
+    fn called(&mut self);    
Quoted 2 lines

This is a central point. We ideally would want this to consume self, because we may want to move things out of the callback.

This one comes from me. The rationale being that ::called() is called from an atomic context, and the resources attached to the callback data might require acquiring other sleeping locks to be released, and sometimes you don't even notice immediately because said resources are refcounted, and the lock is only acquired when you happen to be the last owner. Yes, those can be caught at runtime if the C side is properly annotated with might_sleep(), but that's not always the case.

Quoted 8 lines
Quoted 4 lines

If we defer the drop of the data only when the FenceCb is dropped/recycled, we're at least not constrained by this "runs in atomic context" thing.

This design does not solve it, because one can quite trivially get around this restriction using Option<T> as I said. If your point is “don’t run any drop() here”, then &mut self doesn’t do it.

My bad, I thought you were talking about some Option<T> in FenceCbRegistration<T> (there was one at some point, but it's gone now), but you're talking about having an Option<X> inside the T. Yes, there's indeed nothing preventing a drop on X in that path, and it's just as bad as passing the fence back as value to the callback in that case.

Quoted 32 lines
Quoted 27 lines
Quoted 19 lines

Consider a fence design where signal() consumes self. Now consider this:

```
impl FenceCb for MyCallback {
fn called(&mut self) {
  // Can't move the fence out, so we have to put an Option<T> just to be able
  // to move.
  if let Some(f) = self.some_fence.take() {
    f.signal();
  }
}
```

This used to be the case when our version of the job queue used the "proxy fence" design:

Quoted 7 lines

```
// Callback on the hw fence
impl FenceCb for MyCallback {
fn called(&mut self) {

  if let Some(f) = self.submit_fence.take() {
    f.signal();

}

Quoted 4 lines

I'm pretty sure lockdep won't like it anyway, because this is nested locking of the same lock class. For such proxies, we'll need to teach lockdep about the nesting like has been recently done on dma_fence_array & co. But I'm digressing.

Quoted 5 lines

Yeah, but this is more about resource transfer in general, not this pattern specifically.

I agree that this has issues, and yes, lockdep complained back then :)

The thing is, there's so many aspects that could go wrong because of the context this callback is called in. Nested locking is one of them, the fact we can't sleep is another. And with rust it's even worse, because of the implicit drops that will happen when you take ownership of resources (taking sleeping locks to remove resources from a dataset for instance).

So, by passing self by value to the ::callback(), you're basically telling users "hey, BTW, don't forget to defer the drop to some workqueue if you think it's not atomic-safe". And how can users know that the thing they're about to drop can be dropped in atomic context? They basically have to audit the ::drop() of all the resources they embed in their type implementing FenceCb. Not only that, but they also have to design the thing so the deferral of this ::drop() doesn't allocate, because, obviously, allocating in atomic context is tricky/fallible. AFAIK, none of this can be spot at compile-time (I remember Gary/Danilo mentioning that we could teach the klint about some of these rules). This would leave us with runtime checks like might_sleep(), but most of the C putters (xxx_put(object)) don't have might_sleep() in the path where the decref doesn't lead to a refcnt=0 situation.

TLDR; Call this PTSD if you want, but this is the sort of bugs I struggled with on the C side, and I can predict that the exact same will happen in rust drivers if we expose the FenceCb as it is designed here and we don't have a way to check the soundness of the FenceCb implementations at compile time.

The other option (the one I've been advocating for from the start), is to not let drivers implement FenceCb (make it private), but instead have a bunch of implementations that we know are safe. Here's a list of implementations that I think would unblock most of the drivers use cases:

- wakeup a thread
- complete a completion object
- schedule a WorkItem
- schedule a kthread_worker (once we get a proper rust abstraction for that)

It doesn't mean we can't have optimized FenceCb implementations that do a lot more in the callback() path instead of deferring to a workqueue/thread, but at least those would have to be implemented in dma_fence.rs, and the dma_fence.rs maintainers can then carefully audit the code as part of the review process, which we know is not really the case when changes touch drivers code only.

FWIW, I think the FenceProxy design you were describing falls into this "must be carefully audited" bucket, and should be implemented in dma_fence.rs.

Quoted 9 lines
Quoted 9 lines
Quoted 8 lines

}
```

Although this is not the case anymore, since we phased out this design given Christian's recent work. Still, we should ideally not require Option<T> here in general just to make resource transfer possible.

Quoted 5 lines
Quoted 3 lines

I see. OTOH, don't we need to make this inner data movable if we want to cancel the FenceCb before the fence is signaled anyway? And that's most certainly a case we have in the teardown path.

Can you expand a bit on what you mean here?

Never mind, I was confusing two different iterations of the code here. I thought the Option<T> you were mentioning was in FenceCbRegistration<T>, with some explicit ::cancel() function that would return Option<T> so the user can get its resources back when it cancels the registration, and also know whether the callback was called or not. But this is all gone now, and all we can do is drop the registration, which will automatically drop the inner T.

#64 lore raw

Re: [PATCH 3/4] rust: Add dma_fence abstractions

Quoted 40 lines · Boris Brezillon
Quoted 28 lines · On Wed, 3 Jun 2026 21:43:05 -0300 Daniel Almeida <[email protected]> wrote:
Quoted 28 lines · Boris Brezillon

On Wed, 3 Jun 2026 13:41:02 -0300
Daniel Almeida <[email protected]> wrote:

Quoted 12 lines
Quoted 11 lines

+    /// Called when the fence is signaled.
+    ///
+    /// This is called from the fence signaling path, which may be in interrupt +    /// context or with locks held, which is why self is only borrowed, so that +    /// it cannot drop. Implementations must not sleep or perform
+    /// long-running operations.
+    ///
+    /// An implementation likely wants to inform itself (e.g., through a work item) +    /// within this callback that the associated [FenceCbRegistration] can now be +    /// dropped.
+    fn called(&mut self);

Quoted 2 lines

This is a central point. We ideally would want this to consume self, because we may want to move things out of the callback.

This one comes from me. The rationale being that ::called() is called from an atomic context, and the resources attached to the callback data might require acquiring other sleeping locks to be released, and sometimes you don't even notice immediately because said resources are refcounted, and the lock is only acquired when you happen to be the last owner. Yes, those can be caught at runtime if the C side is properly annotated with might_sleep(), but that's not always the case.

Quoted 8 lines
Quoted 4 lines

If we defer the drop of the data only when the FenceCb is dropped/recycled, we're at least not constrained by this "runs in atomic context" thing.

This design does not solve it, because one can quite trivially get around this restriction using Option<T> as I said. If your point is “don’t run any drop() here”, then &mut self doesn’t do it.

Quoted 5 lines

My bad, I thought you were talking about some Option<T> in FenceCbRegistration<T> (there was one at some point, but it's gone now), but you're talking about having an Option<X> inside the T. Yes, there's indeed nothing preventing a drop on X in that path, and it's just as bad as passing the fence back as value to the callback in that case.

Then maybe we should just pass it by value and require implementation of an unsafe trait on T, whose safety-requirements demand that this must be save to drop from atomic context.

Quoted 39 lines
Quoted 33 lines
Quoted 28 lines
Quoted 20 lines

Consider a fence design where signal() consumes self. Now consider this:

```
impl FenceCb for MyCallback {
fn called(&mut self) {
  // Can't move the fence out, so we have to put an Option<T> just to be able
  // to move.
  if let Some(f) = self.some_fence.take() {
    f.signal();
  }
}
```

This used to be the case when our version of the job queue used the "proxy fence" design:

Quoted 7 lines

```
// Callback on the hw fence
impl FenceCb for MyCallback {
fn called(&mut self) {
  if let Some(f) = self.submit_fence.take() {
    f.signal();
  }

Quoted 4 lines

I'm pretty sure lockdep won't like it anyway, because this is nested locking of the same lock class. For such proxies, we'll need to teach lockdep about the nesting like has been recently done on dma_fence_array & co. But I'm digressing.

Quoted 5 lines

Yeah, but this is more about resource transfer in general, not this pattern specifically.

I agree that this has issues, and yes, lockdep complained back then :)

Quoted 6 lines

The thing is, there's so many aspects that could go wrong because of the context this callback is called in. Nested locking is one of them, the fact we can't sleep is another. And with rust it's even worse, because of the implicit drops that will happen when you take ownership of resources (taking sleeping locks to remove resources from a dataset for instance).

Doesn't that have to be a problem for much of Rust infrastructure? How do other parties solve that?

Quoted 16 lines

So, by passing self by value to the ::callback(), you're basically telling users "hey, BTW, don't forget to defer the drop to some workqueue if you think it's not atomic-safe". And how can users know that the thing they're about to drop can be dropped in atomic context? They basically have to audit the ::drop() of all the resources they embed in their type implementing FenceCb. Not only that, but they also have to design the thing so the deferral of this ::drop() doesn't allocate, because, obviously, allocating in atomic context is tricky/fallible. AFAIK, none of this can be spot at compile-time (I remember Gary/Danilo mentioning that we could teach the klint about some of these rules). This would leave us with runtime checks like might_sleep(), but most of the C putters (xxx_put(object)) don't have might_sleep() in the path where the decref doesn't lead to a refcnt=0 situation.

Quoted 5 lines

TLDR; Call this PTSD if you want, but this is the sort of bugs I struggled with on the C side, and I can predict that the exact same will happen in rust drivers if we expose the FenceCb as it is designed here and we don't have a way to check the soundness of the FenceCb implementations at compile time.

My guess would be that the existence of unsafe-traits is the admission of Rust that this just cannot be guaranteed by design.

If a driver cannot know whether this or that is safe to drop, then it would have to defer it's dropping. Or would there be cases where this also doesn't work?

Quoted 13 lines

The other option (the one I've been advocating for from the start), is to not let drivers implement FenceCb (make it private), but instead have a bunch of implementations that we know are safe. Here's a list of implementations that I think would unblock most of the drivers use cases:

- wakeup a thread
- complete a completion object
- schedule a WorkItem
- schedule a kthread_worker (once we get a proper rust abstraction for that)

Quoted 6 lines

It doesn't mean we can't have optimized FenceCb implementations that do a lot more in the callback() path instead of deferring to a workqueue/thread, but at least those would have to be implemented in dma_fence.rs, and the dma_fence.rs maintainers can then carefully audit the code as part of the review process, which we know is not really the case when changes touch drivers code only.

Pragmatically speaking, if the common cases are trivial, then the drivers will get them right, because those critical primitives are already atomic-safe.

And a non-common case will have to be implemented in the driver anyways, so we'd have to allow for that.

P.

#67 lore raw

Re: [PATCH 3/4] rust: Add dma_fence abstractions

In reply to Boris Brezillon
Quoted 46 lines
Quoted 33 lines
Quoted 28 lines
Quoted 20 lines

Consider a fence design where signal() consumes self. Now consider this:

```
impl FenceCb for MyCallback {
fn called(&mut self) {
 // Can't move the fence out, so we have to put an Option<T> just to be able
 // to move.
 if let Some(f) = self.some_fence.take() {
   f.signal();
 }
}
```

This used to be the case when our version of the job queue used the "proxy fence" design:

Quoted 7 lines

```
// Callback on the hw fence
impl FenceCb for MyCallback {
fn called(&mut self) {

 if let Some(f) = self.submit_fence.take() {
   f.signal();

}

Quoted 4 lines

I'm pretty sure lockdep won't like it anyway, because this is nested locking of the same lock class. For such proxies, we'll need to teach lockdep about the nesting like has been recently done on dma_fence_array & co. But I'm digressing.

Quoted 5 lines

Yeah, but this is more about resource transfer in general, not this pattern specifically.

I agree that this has issues, and yes, lockdep complained back then :)

The thing is, there's so many aspects that could go wrong because of the context this callback is called in. Nested locking is one of them, the fact we can't sleep is another. And with rust it's even worse, because of the implicit drops that will happen when you take ownership of resources (taking sleeping locks to remove resources from a dataset for instance).

Quoted 4 lines

So, by passing self by value to the ::callback(), you're basically telling users "hey, BTW, don't forget to defer the drop to some workqueue if you think it's not atomic-safe". And how can users know that the thing they're about to drop can be dropped in atomic context?

Can’t we create a token type that signals we’re in atomic context? If we pass this token type as an argument, perhaps lockdep can check it for us?

Perhaps

enum SomeFancyName {
Atomic(AtomicToken)
NotAtomic,
}

signaled(self, atomic: AtomicToken) {
..
}

Quoted 23 lines

They basically have to audit the ::drop() of all the resources they embed in their type implementing FenceCb. Not only that, but they also have to design the thing so the deferral of this ::drop() doesn't allocate, because, obviously, allocating in atomic context is tricky/fallible. AFAIK, none of this can be spot at compile-time (I remember Gary/Danilo mentioning that we could teach the klint about some of these rules). This would leave us with runtime checks like might_sleep(), but most of the C putters (xxx_put(object)) don't have might_sleep() in the path where the decref doesn't lead to a refcnt=0 situation.

TLDR; Call this PTSD if you want, but this is the sort of bugs I struggled with on the C side, and I can predict that the exact same will happen in rust drivers if we expose the FenceCb as it is designed here and we don't have a way to check the soundness of the FenceCb implementations at compile time.

The other option (the one I've been advocating for from the start), is to not let drivers implement FenceCb (make it private), but instead have a bunch of implementations that we know are safe. Here's a list of implementations that I think would unblock most of the drivers use cases:

Quoted 5 lines

- wakeup a thread
- complete a completion object
- schedule a WorkItem
- schedule a kthread_worker (once we get a proper rust abstraction for that)

This can also work too, I guess.

Quoted 34 lines

It doesn't mean we can't have optimized FenceCb implementations that do a lot more in the callback() path instead of deferring to a workqueue/thread, but at least those would have to be implemented in dma_fence.rs, and the dma_fence.rs maintainers can then carefully audit the code as part of the review process, which we know is not really the case when changes touch drivers code only.

FWIW, I think the FenceProxy design you were describing falls into this "must be carefully audited" bucket, and should be implemented in dma_fence.rs.

Quoted 9 lines
Quoted 9 lines
Quoted 8 lines

}
```

Although this is not the case anymore, since we phased out this design given Christian's recent work. Still, we should ideally not require Option<T> here in general just to make resource transfer possible.

Quoted 5 lines
Quoted 3 lines

I see. OTOH, don't we need to make this inner data movable if we want to cancel the FenceCb before the fence is signaled anyway? And that's most certainly a case we have in the teardown path.

Can you expand a bit on what you mean here?

Never mind, I was confusing two different iterations of the code here. I thought the Option<T> you were mentioning was in FenceCbRegistration<T>, with some explicit ::cancel() function that would return Option<T> so the user can get its resources back when it cancels the registration, and also know whether the callback was called or not. But this is all gone now, and all we can do is drop the registration, which will automatically drop the inner T.

#65 lore raw

Re: [PATCH 3/4] rust: Add dma_fence abstractions

In reply to Philipp Stanner
Quoted 126 lines · Philipp Stanner
Quoted 40 lines · Boris Brezillon
Quoted 28 lines · On Wed, 3 Jun 2026 21:43:05 -0300 Daniel Almeida <[email protected]> wrote:
Quoted 28 lines · Boris Brezillon
Quoted 12 lines · On Wed, 3 Jun 2026 13:41:02 -0300 Daniel Almeida <[email protected]> wrote:

rust/kernel/dma_buf/dma_fence.rs line 276

Quoted 11 lines
rust/kernel/dma_buf/dma_fence.rs new file +11 -0
+    /// Called when the fence is signaled.
+    ///
+    /// This is called from the fence signaling path, which may be in interrupt
+    /// context or with locks held, which is why `self` is only borrowed, so that
+    /// it cannot drop. Implementations must not sleep or perform
+    /// long-running operations.
+    ///
+    /// An implementation likely wants to inform itself (e.g., through a work item)
+    /// within this callback that the associated [`FenceCbRegistration`] can now be
+    /// dropped.
+    fn called(&mut self);    
Quoted 2 lines

This is a central point. We ideally would want this to consume self, because we may want to move things out of the callback.

This one comes from me. The rationale being that ::called() is called from an atomic context, and the resources attached to the callback data might require acquiring other sleeping locks to be released, and sometimes you don't even notice immediately because said resources are refcounted, and the lock is only acquired when you happen to be the last owner. Yes, those can be caught at runtime if the C side is properly annotated with might_sleep(), but that's not always the case.

Quoted 8 lines
Quoted 4 lines

If we defer the drop of the data only when the FenceCb is dropped/recycled, we're at least not constrained by this "runs in atomic context" thing.

This design does not solve it, because one can quite trivially get around this restriction using Option<T> as I said. If your point is “don’t run any drop() here”, then &mut self doesn’t do it.

Quoted 5 lines

My bad, I thought you were talking about some Option<T> in FenceCbRegistration<T> (there was one at some point, but it's gone now), but you're talking about having an Option<X> inside the T. Yes, there's indeed nothing preventing a drop on X in that path, and it's just as bad as passing the fence back as value to the callback in that case.

Then maybe we should just pass it by value and require implementation of an unsafe trait on T, whose safety-requirements demand that this must be save to drop from atomic context.

Quoted 39 lines
Quoted 33 lines
Quoted 28 lines
Quoted 20 lines

Consider a fence design where signal() consumes self. Now consider this:

```
impl FenceCb for MyCallback {
fn called(&mut self) {
  // Can't move the fence out, so we have to put an Option<T> just to be able
  // to move.
  if let Some(f) = self.some_fence.take() {
    f.signal();
  }
}
```

This used to be the case when our version of the job queue used the "proxy fence" design:

Quoted 7 lines

```
// Callback on the hw fence
impl FenceCb for MyCallback {
fn called(&mut self) {

  if let Some(f) = self.submit_fence.take() {
    f.signal();

}

Quoted 4 lines

I'm pretty sure lockdep won't like it anyway, because this is nested locking of the same lock class. For such proxies, we'll need to teach lockdep about the nesting like has been recently done on dma_fence_array & co. But I'm digressing.

Quoted 5 lines

Yeah, but this is more about resource transfer in general, not this pattern specifically.

I agree that this has issues, and yes, lockdep complained back then :)

Quoted 6 lines

The thing is, there's so many aspects that could go wrong because of the context this callback is called in. Nested locking is one of them, the fact we can't sleep is another. And with rust it's even worse, because of the implicit drops that will happen when you take ownership of resources (taking sleeping locks to remove resources from a dataset for instance).

Doesn't that have to be a problem for much of Rust infrastructure? How do other parties solve that?

Quoted 16 lines

So, by passing self by value to the ::callback(), you're basically telling users "hey, BTW, don't forget to defer the drop to some workqueue if you think it's not atomic-safe". And how can users know that the thing they're about to drop can be dropped in atomic context? They basically have to audit the ::drop() of all the resources they embed in their type implementing FenceCb. Not only that, but they also have to design the thing so the deferral of this ::drop() doesn't allocate, because, obviously, allocating in atomic context is tricky/fallible. AFAIK, none of this can be spot at compile-time (I remember Gary/Danilo mentioning that we could teach the klint about some of these rules). This would leave us with runtime checks like might_sleep(), but most of the C putters (xxx_put(object)) don't have might_sleep() in the path where the decref doesn't lead to a refcnt=0 situation.

Quoted 5 lines

TLDR; Call this PTSD if you want, but this is the sort of bugs I struggled with on the C side, and I can predict that the exact same will happen in rust drivers if we expose the FenceCb as it is designed here and we don't have a way to check the soundness of the FenceCb implementations at compile time.

Quoted 6 lines

My guess would be that the existence of unsafe-traits is the admission of Rust that this just cannot be guaranteed by design.

If a driver cannot know whether this or that is safe to drop, then it would have to defer it's dropping. Or would there be cases where this also doesn't work?

Although I totally understand where Boris is coming from here, and I agree with him, the reality is that the current &mut self design doesn’t solve this. An unsafe trait could work as a pinky-promise by drivers, which is half-way there.

What we ideally would like to have is a bound though, something like:

T: !Drop

If I recall correctly there were people working to get support for that on Rust? I think there are two things here: !Trait, which is not supported except for !Sized IIRC, and having an auto trait that represents types that implement Drop, similar to Send and Sync.

Quoted 21 lines
Quoted 14 lines

The other option (the one I've been advocating for from the start), is to not let drivers implement FenceCb (make it private), but instead have a bunch of implementations that we know are safe. Here's a list of implementations that I think would unblock most of the drivers use cases:

- wakeup a thread
- complete a completion object
- schedule a WorkItem
- schedule a kthread_worker (once we get a proper rust abstraction for that)

Quoted 6 lines

It doesn't mean we can't have optimized FenceCb implementations that do a lot more in the callback() path instead of deferring to a workqueue/thread, but at least those would have to be implemented in dma_fence.rs, and the dma_fence.rs maintainers can then carefully audit the code as part of the review process, which we know is not really the case when changes touch drivers code only.

Quoted 3 lines

Pragmatically speaking, if the common cases are trivial, then the drivers will get them right, because those critical primitives are already atomic-safe.

No, you can still fumble trivial code, specially when it has to be cargo-culted from somewhere else.

I am not saying that having things in dma_fence . rs is the solution, although it is definitely one solution. But the argument above isn’t very strong IMHO.

Quoted 7 lines

And a non-common case will have to be implemented in the driver anyways, so we'd have to allow for that.

P.

#66 lore raw

Re: [PATCH 3/4] rust: Add dma_fence abstractions

Quoted 36 lines
Quoted 24 lines
Quoted 18 lines

So, by passing self by value to the ::callback(), you're basically telling users "hey, BTW, don't forget to defer the drop to some workqueue if you think it's not atomic-safe". And how can users know that the thing they're about to drop can be dropped in atomic context? They basically have to audit the ::drop() of all the resources they embed in their type implementing FenceCb. Not only that, but they also have to design the thing so the deferral of this ::drop() doesn't allocate, because, obviously, allocating in atomic context is tricky/fallible. AFAIK, none of this can be spot at compile-time (I remember Gary/Danilo mentioning that we could teach the klint about some of these rules). This would leave us with runtime checks like might_sleep(), but most of the C putters (xxx_put(object)) don't have might_sleep() in the path where the decref doesn't lead to a refcnt=0 situation.

Quoted 5 lines

TLDR; Call this PTSD if you want, but this is the sort of bugs I struggled with on the C side, and I can predict that the exact same will happen in rust drivers if we expose the FenceCb as it is designed here and we don't have a way to check the soundness of the FenceCb implementations at compile time.

Quoted 6 lines

My guess would be that the existence of unsafe-traits is the admission of Rust that this just cannot be guaranteed by design.

If a driver cannot know whether this or that is safe to drop, then it would have to defer it's dropping. Or would there be cases where this also doesn't work?

Although I totally understand where Boris is coming from here, and I agree with him, the reality is that the current &mut self design doesn’t solve this. An unsafe trait could work as a pinky-promise by drivers, which is half-way there.

Quoted 9 lines

What we ideally would like to have is a bound though, something like:

T: !Drop

If I recall correctly there were people working to get support for that on Rust? I think there are two things here: !Trait, which is not supported except for !Sized IIRC, and having an auto trait that represents types that implement Drop, similar to Send and Sync.

In fact, ping the pin-init people here, i.e.: Benno, Gary, etc.

What is the magic behind “MustNotImplDrop”? Perhaps we could apply that here?

— Daniel