* [RFC PATCH v2 1/4] rust: rcu: Add RcuBox type
2026-07-18 5:32 [RFC PATCH v2 0/4] rust: sync: Introduce Rcu*Box Boqun Feng
@ 2026-07-18 5:32 ` Boqun Feng
2026-07-18 5:48 ` sashiko-bot
2026-07-18 5:32 ` [RFC PATCH v2 2/4] rust: maple_tree: Add load_rcu() Boqun Feng
` (2 subsequent siblings)
3 siblings, 1 reply; 8+ messages in thread
From: Boqun Feng @ 2026-07-18 5:32 UTC (permalink / raw)
To: rust-for-linux, rcu
Cc: Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Daniel Almeida, Tamir Duberstein,
Alexandre Courbot, Onur Özkan, Liam R. Howlett,
Andrew Ballance, Alexander Viro, Christian Brauner, Jan Kara,
Lyude Paul, Paul E. McKenney, Frederic Weisbecker,
Neeraj Upadhyay, Joel Fernandes, Josh Triplett, Uladzislau Rezki,
Steven Rostedt, Mathieu Desnoyers, Lai Jiangshan, Zqiang,
Sumit Semwal, Christian König, Greg Kroah-Hartman,
Yury Norov (NVIDIA), Asahi Lina, Matthew Maurer, Lorenzo Stoakes,
linux-kernel, maple-tree, linux-mm, linux-fsdevel, linux-media,
dri-devel, linaro-mm-sig
From: Alice Ryhl <aliceryhl@google.com>
This adds an RcuBox container, which is like Box except that the value
is freed after waiting for one grace period (via {kvfree_,}call_rcu()).
To allow containers to rely on the RCU properties of RcuBox, an
extension of ForeignOwnable is added.
Signed-off-by: Alice Ryhl <aliceryhl@google.com>
[boqun: Make RcuBox generic over Allocator and add tests]
[boqun: Add type alias for Rcu*Box]
[boqun: Adjust the ForeignOwnable changes on `T` not being `:'static`]
Co-developed-by: Boqun Feng <boqun@kernel.org>
Signed-off-by: Boqun Feng <boqun@kernel.org>
---
rust/bindings/bindings_helper.h | 1 +
rust/kernel/sync/rcu.rs | 36 ++++-
rust/kernel/sync/rcu/rcu_box.rs | 248 ++++++++++++++++++++++++++++++++
3 files changed, 284 insertions(+), 1 deletion(-)
create mode 100644 rust/kernel/sync/rcu/rcu_box.rs
diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index 1124785e210b..3e3f1799a49f 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -81,6 +81,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>
diff --git a/rust/kernel/sync/rcu.rs b/rust/kernel/sync/rcu.rs
index 2bae76d229f0..42f6bbc83f71 100644
--- a/rust/kernel/sync/rcu.rs
+++ b/rust/kernel/sync/rcu.rs
@@ -4,7 +4,19 @@
//!
//! 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;
+pub use self::rcu_box::RcuKBox;
+pub use self::rcu_box::RcuKVBox;
+pub use self::rcu_box::RcuVBox;
/// Evidence that the RCU read side lock is held on the current thread/CPU.
///
@@ -66,3 +78,25 @@ pub fn synchronize_rcu() {
// SAFETY: `synchronize_rcu()` is always safe to be called from process context.
unsafe { bindings::synchronize_rcu() };
}
+
+/// 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>
+ where
+ Self: '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>;
+}
diff --git a/rust/kernel/sync/rcu/rcu_box.rs b/rust/kernel/sync/rcu/rcu_box.rs
new file mode 100644
index 000000000000..cb1fd422480a
--- /dev/null
+++ b/rust/kernel/sync/rcu/rcu_box.rs
@@ -0,0 +1,248 @@
+// 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::{
+ marker::PhantomData,
+ ops::Deref,
+ ptr::NonNull, //
+};
+
+use crate::{
+ alloc::{
+ self,
+ allocator::{
+ KVmalloc,
+ Kmalloc,
+ Vmalloc, //
+ },
+ AllocError,
+ Allocator, //
+ },
+ bindings,
+ ffi::c_void,
+ prelude::*,
+ 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 `A`.
+/// * This `RcuBox` holds exclusive permissions to rcu free the allocation.
+pub struct RcuBox<T: Send, A: Allocator>(NonNull<RcuBoxInner<T>>, PhantomData<A>);
+
+/// Type alias for [`RcuBox`] with a [`Kmalloc`] allocator.
+///
+/// # Examples
+///
+/// ```
+/// # use kernel::sync::rcu::{self, RcuKBox};
+/// let rb = RcuKBox::new(42, GFP_KERNEL)?;
+///
+/// assert_eq!(*rb, 42);
+/// assert_eq!(*rb.with_rcu(&rcu::read_lock()), 42);
+/// # Ok::<(), Error>(())
+/// ```
+pub type RcuKBox<T> = RcuBox<T, Kmalloc>;
+
+/// Type alias for [`RcuBox`] with a [`Vmalloc`] allocator.
+///
+/// # Examples
+///
+/// ```
+/// # use kernel::sync::rcu::{self, RcuVBox};
+/// let rb = RcuVBox::new(42, GFP_KERNEL)?;
+///
+/// assert_eq!(*rb, 42);
+/// assert_eq!(*rb.with_rcu(&rcu::read_lock()), 42);
+/// # Ok::<(), Error>(())
+/// ```
+pub type RcuVBox<T> = RcuBox<T, Vmalloc>;
+
+/// Type alias for [`RcuBox`] with a [`KVmalloc`] allocator.
+///
+/// # Examples
+///
+/// ```
+/// # use kernel::sync::rcu::{self, RcuKVBox};
+/// let rb = RcuKVBox::new(42, GFP_KERNEL)?;
+///
+/// assert_eq!(*rb, 42);
+/// assert_eq!(*rb.with_rcu(&rcu::read_lock()), 42);
+/// # Ok::<(), Error>(())
+/// ```
+pub type RcuKVBox<T> = RcuBox<T, KVmalloc>;
+
+struct RcuBoxInner<T> {
+ rcu_head: bindings::callback_head,
+ value: T,
+}
+
+// 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, 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, 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, A: Allocator> RcuBox<T, A> {
+ /// Create a new `RcuBox`.
+ pub fn new(x: T, flags: alloc::Flags) -> Result<Self, AllocError> {
+ let b = Box::<_, A>::new(
+ RcuBoxInner {
+ value: x,
+ rcu_head: Default::default(),
+ },
+ flags,
+ )?;
+
+ // INVARIANT:
+ // * The pointer contains a valid `RcuBoxInner` allocated with `A`.
+ // * We just allocated it, so we own free permissions.
+ Ok(RcuBox(NonNull::from(Box::leak(b)), PhantomData))
+ }
+
+ /// Access the value for a grace period.
+ pub fn with_rcu<'rcu>(&self, _read_guard: &'rcu Guard) -> &'rcu T {
+ // SAFETY: The `RcuBox` has not been dropped yet, so the value is valid for at least one
+ // grace period.
+ unsafe { &(*self.0.as_ptr()).value }
+ }
+}
+
+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.
+ unsafe { &(*self.0.as_ptr()).value }
+ }
+}
+
+// SAFETY:
+// * The `RcuBoxInner<T>` was allocated with `A`.
+// * `NonNull::as_ptr` returns a non-null pointer.
+unsafe impl<T: Send, A: Allocator> ForeignOwnable for RcuBox<T, A> {
+ const FOREIGN_ALIGN: usize = <Box<RcuBoxInner<T>, A> as ForeignOwnable>::FOREIGN_ALIGN;
+
+ type Borrowed<'a>
+ = &'a T
+ where
+ Self: 'a;
+ type BorrowedMut<'a>
+ = &'a T
+ where
+ Self: 'a;
+
+ fn into_foreign(self) -> *mut c_void {
+ core::mem::ManuallyDrop::new(self).0.as_ptr().cast()
+ }
+
+ unsafe fn from_foreign(ptr: *mut c_void) -> Self {
+ // 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()) }, PhantomData)
+ }
+
+ unsafe fn borrow<'a>(ptr: *mut c_void) -> &'a T
+ where
+ Self: 'a,
+ {
+ // SAFETY: Caller ensures that `'a` is short enough.
+ unsafe { &(*ptr.cast::<RcuBoxInner<T>>()).value }
+ }
+
+ unsafe fn borrow_mut<'a>(ptr: *mut c_void) -> &'a T
+ where
+ Self: 'a,
+ {
+ // SAFETY: `borrow_mut` has strictly stronger preconditions than `borrow`.
+ unsafe { Self::borrow(ptr) }
+ }
+}
+
+impl<T: Send, A: Allocator> ForeignOwnableRcu for RcuBox<T, A> {
+ type RcuBorrowed<'a>
+ = &'a T
+ where
+ Self: 'a;
+
+ unsafe fn rcu_borrow<'a>(ptr: *mut c_void) -> &'a T
+ where
+ Self: 'a,
+ {
+ // 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 }
+ }
+}
+
+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 };
+ if core::mem::needs_drop::<T>() {
+ // 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, 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()) };
+ }
+ }
+}
+
+/// Free this `RcuBoxInner<T>`.
+///
+/// # Safety
+///
+/// `head` references the `rcu_head` field of an `RcuBoxInner<T>` that has no references to it.
+/// 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 { 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(())
+ }
+}
--
2.50.1 (Apple Git-155)
^ permalink raw reply related [flat|nested] 8+ messages in thread* Re: [RFC PATCH v2 1/4] rust: rcu: Add RcuBox type
2026-07-18 5:32 ` [RFC PATCH v2 1/4] rust: rcu: Add RcuBox type Boqun Feng
@ 2026-07-18 5:48 ` sashiko-bot
0 siblings, 0 replies; 8+ messages in thread
From: sashiko-bot @ 2026-07-18 5:48 UTC (permalink / raw)
To: Boqun Feng; +Cc: media-ci, ojeda, dri-devel
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [Critical] `RcuBox` lacks a `'static` bound on `T`, which allows asynchronous Use-After-Free for types with custom Drop implementations that contain non-'static references.
- [High] `RcuBox<T, A>` bypasses the generic `Allocator`'s free method in its `Drop` implementation when `needs_drop::<T>()` is false, leading to invalid frees for custom allocators.
- [High] `RcuBox` safely uses `call_rcu()` with a monomorphized callback `drop_rcu_box`, but Rust does not expose `rcu_barrier()`, making safe module unload impossible.
--
--- Patch [1]: [RFC PATCH v2 1/4] rust: rcu: Add RcuBox type ---
commit b7ba3ecd614c74b985c54d7b0785315fa2c066c8
Author: Alice Ryhl <aliceryhl@google.com>
rust: rcu: Add RcuBox type
This adds an RcuBox container, which is like Box except that the value
is freed after waiting for one grace period (via {kvfree_,}call_rcu()).
To allow containers to rely on the RCU properties of RcuBox, an
extension of ForeignOwnable is added.
> diff --git a/rust/kernel/sync/rcu/rcu_box.rs b/rust/kernel/sync/rcu/rcu_box.rs
> new file mode 100644
> index 0000000000000..cb1fd422480a8
> --- /dev/null
> +++ b/rust/kernel/sync/rcu/rcu_box.rs
[ ... ]
> +/// 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 A.
> +/// * This RcuBox holds exclusive permissions to rcu free the allocation.
> +pub struct RcuBox<T: Send, A: Allocator>(NonNull<RcuBoxInner<T>>, PhantomData<A>);
[Severity: Critical]
Since the drop implementation can delegate the actual drop to an asynchronous
background task via call_rcu, could a type with a non-static lifetime
trigger a use-after-free if its custom drop implementation accesses a scoped
reference after it goes out of scope?
Would it be safer to require T: 'static here?
[ ... ]
> +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 };
> + if core::mem::needs_drop::<T>() {
> + // 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, A>)) };
[Severity: High]
Does this monomorphized callback require modules to use rcu_barrier() during
unload to prevent executing unloaded memory?
Since rcu_barrier() isn't exposed in the Rust bindings yet, is there a way
for loadable modules using RcuBox to safely unload without risking a regression
if the RCU grace period hasn't completed?
> + } 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()) };
[Severity: High]
If RcuBox is instantiated with a custom allocator, will hardcoding
kvfree_call_rcu cause an invalid free when needs_drop is false?
This branch appears to completely bypass the generic allocator's free method.
> + }
> + }
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260718053247.25154-1-boqun@kernel.org?part=1
^ permalink raw reply [flat|nested] 8+ messages in thread
* [RFC PATCH v2 2/4] rust: maple_tree: Add load_rcu()
2026-07-18 5:32 [RFC PATCH v2 0/4] rust: sync: Introduce Rcu*Box Boqun Feng
2026-07-18 5:32 ` [RFC PATCH v2 1/4] rust: rcu: Add RcuBox type Boqun Feng
@ 2026-07-18 5:32 ` Boqun Feng
2026-07-18 6:00 ` sashiko-bot
2026-07-18 5:32 ` [RFC PATCH v2 3/4] rust: rcu: Introduce RcuFreeBox Boqun Feng
2026-07-18 5:32 ` [NOT FOR MERGE] [RFC PATCH v2 4/4] rust: poll: use kfree_rcu() for PollCondVar Boqun Feng
3 siblings, 1 reply; 8+ messages in thread
From: Boqun Feng @ 2026-07-18 5:32 UTC (permalink / raw)
To: rust-for-linux, rcu
Cc: Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Daniel Almeida, Tamir Duberstein,
Alexandre Courbot, Onur Özkan, Liam R. Howlett,
Andrew Ballance, Alexander Viro, Christian Brauner, Jan Kara,
Lyude Paul, Paul E. McKenney, Frederic Weisbecker,
Neeraj Upadhyay, Joel Fernandes, Josh Triplett, Uladzislau Rezki,
Steven Rostedt, Mathieu Desnoyers, Lai Jiangshan, Zqiang,
Sumit Semwal, Christian König, Greg Kroah-Hartman,
Yury Norov (NVIDIA), Asahi Lina, Matthew Maurer, Lorenzo Stoakes,
linux-kernel, maple-tree, linux-mm, linux-fsdevel, linux-media,
dri-devel, linaro-mm-sig
From: Alice Ryhl <aliceryhl@google.com>
Now that we have a concept of rcu-safe containers, we may add a
load_rcu() method to MapleTree that does not take the spinlock.
Signed-off-by: Alice Ryhl <aliceryhl@google.com>
---
rust/kernel/maple_tree.rs | 52 +++++++++++++++++++++++++++++++++++++++
1 file changed, 52 insertions(+)
diff --git a/rust/kernel/maple_tree.rs b/rust/kernel/maple_tree.rs
index 265d6396a78a..1499191b8935 100644
--- a/rust/kernel/maple_tree.rs
+++ b/rust/kernel/maple_tree.rs
@@ -16,6 +16,10 @@
alloc::Flags,
error::to_result,
prelude::*,
+ sync::rcu::{
+ self,
+ ForeignOwnableRcu, //
+ },
types::{ForeignOwnable, Opaque},
};
@@ -233,6 +237,54 @@ pub fn erase(&self, index: usize) -> Option<T> {
unsafe { T::try_from_foreign(ret) }
}
+ /// Load the value at the given index with rcu.
+ ///
+ /// # Examples
+ ///
+ /// Read the value under an rcu read lock. Even if the value is removed, it remains accessible
+ /// for one rcu grace period.
+ ///
+ /// ```ignore
+ /// use kernel::{
+ /// maple_tree::MapleTree,
+ /// sync::rcu::{self, RcuBox},
+ /// };
+ ///
+ /// let tree = KBox::pin_init(MapleTree::<RcuBox<i32>>::new(), GFP_KERNEL)?;
+ ///
+ /// let ten = RcuBox::new(10, GFP_KERNEL)?;
+ /// tree.insert(100, ten, GFP_KERNEL)?;
+ ///
+ /// let rcu_read_lock = rcu::Guard::new();
+ /// let ten = tree.load_rcu(100, &rcu_read_lock);
+ /// assert_eq!(ten, Some(&10));
+ ///
+ /// // Even if the value gets removed, we may continue to access it for one rcu grace period.
+ /// tree.erase(100);
+ /// assert_eq!(ten, Some(&10));
+ /// # Ok::<_, Error>(())
+ /// ```
+ #[inline]
+ pub fn load_rcu<'rcu>(
+ &self,
+ index: usize,
+ _rcu: &'rcu rcu::Guard,
+ ) -> Option<T::RcuBorrowed<'rcu>>
+ where
+ T: ForeignOwnableRcu,
+ {
+ // SAFETY: `self.tree` contains a valid maple tree.
+ let ret = unsafe { bindings::mtree_load(self.tree.get(), index) };
+ if ret.is_null() {
+ return None;
+ }
+
+ // SAFETY: If the pointer is not null, then it references a valid instance of `T`. It is
+ // safe to borrow the instance for 'rcu because the signature of this function enforces that
+ // the borrow does not outlive an rcu grace period.
+ Some(unsafe { T::rcu_borrow(ret) })
+ }
+
/// Lock the internal spinlock.
#[inline]
pub fn lock(&self) -> MapleGuard<'_, T> {
--
2.50.1 (Apple Git-155)
^ permalink raw reply related [flat|nested] 8+ messages in thread* Re: [RFC PATCH v2 2/4] rust: maple_tree: Add load_rcu()
2026-07-18 5:32 ` [RFC PATCH v2 2/4] rust: maple_tree: Add load_rcu() Boqun Feng
@ 2026-07-18 6:00 ` sashiko-bot
0 siblings, 0 replies; 8+ messages in thread
From: sashiko-bot @ 2026-07-18 6:00 UTC (permalink / raw)
To: Boqun Feng; +Cc: ojeda, media-ci, dri-devel
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [Critical] Lockless `load_rcu()` leads to use-after-free on maple tree internal nodes because the tree is not initialized with `MT_FLAGS_USE_RCU`.
- [High] The `ForeignOwnableRcu` trait is missing the `unsafe` keyword, allowing safe code to violate RCU memory safety guarantees.
- [High] `RcuBox`'s `Drop` implementation bypasses the custom `Allocator` parameter when dropping types that do not require drop logic.
--
--- Patch [2]: [RFC PATCH v2 2/4] rust: maple_tree: Add load_rcu() ---
commit fcc71907e345f6a007c4675e0956efaca9ebf22c
Author: Alice Ryhl <aliceryhl@google.com>
rust: maple_tree: Add load_rcu()
This patch introduces a lockless load_rcu() method to the MapleTree,
assuming RCU-safe containers are used. The patch series also adds an
RcuBox type and a ForeignOwnableRcu trait to support RCU-based freeing
of box contents.
> diff --git a/rust/kernel/maple_tree.rs b/rust/kernel/maple_tree.rs
> index 265d6396a78a1..1499191b8935a 100644
> --- a/rust/kernel/maple_tree.rs
> +++ b/rust/kernel/maple_tree.rs
[ ... ]
> @@ -233,6 +237,54 @@ pub fn erase(&self, index: usize) -> Option<T> {
> unsafe { T::try_from_foreign(ret) }
> }
>
> + /// Load the value at the given index with rcu.
[ ... ]
> + #[inline]
> + pub fn load_rcu<'rcu>(
> + &self,
> + index: usize,
> + _rcu: &'rcu rcu::Guard,
> + ) -> Option<T::RcuBorrowed<'rcu>>
> + where
> + T: ForeignOwnableRcu,
> + {
> + // SAFETY: `self.tree` contains a valid maple tree.
> + let ret = unsafe { bindings::mtree_load(self.tree.get(), index) };
[Severity: Critical]
Does this code cause a use-after-free on maple tree internal nodes?
Because load_rcu() executes bindings::mtree_load() without acquiring the
tree's spinlock, the tree must be initialized with bindings::MT_FLAGS_USE_RCU
to be safe against concurrent writers.
However, MapleTree::new() initializes the tree without this flag:
rust/kernel/maple_tree.rs:MapleTree::new() {
...
tree <- Opaque::ffi_init(|slot| unsafe { bindings::mt_init_flags(slot, 0) }),
...
}
Because the tree is in non-RCU mode, writers will synchronously free internal
nodes via kfree(), causing load_rcu() to dereference freed memory during its
lockless traversal.
> + if ret.is_null() {
> + return None;
> + }
> +
> + // SAFETY: If the pointer is not null, then it references a valid instance of `T`. It is
> + // safe to borrow the instance for 'rcu because the signature of this function enforces that
> + // the borrow does not outlive an rcu grace period.
> + Some(unsafe { T::rcu_borrow(ret) })
> + }
> +
> /// Lock the internal spinlock.
> #[inline]
> pub fn lock(&self) -> MapleGuard<'_, T> {
> diff --git a/rust/kernel/sync/rcu.rs b/rust/kernel/sync/rcu.rs
> index 2bae76d229f09..42f6bbc83f71f 100644
> --- a/rust/kernel/sync/rcu.rs
> +++ b/rust/kernel/sync/rcu.rs
[ ... ]
> @@ -66,3 +78,25 @@ pub fn synchronize_rcu() {
> // SAFETY: `synchronize_rcu()` is always safe to be called from process context.
> unsafe { bindings::synchronize_rcu() };
> }
> +
> +/// Declares that a pointer type is rcu safe.
> +pub trait ForeignOwnableRcu: ForeignOwnable {
[Severity: High]
Should this trait be marked unsafe to enforce the RCU deferral contract?
The ForeignOwnableRcu trait promises that removing the node will not
invalidate the object until an RCU grace period elapses, allowing load_rcu()
to safely borrow it.
Because ForeignOwnableRcu is a safe trait, developers can implement it for
types that drop synchronously (like Box<T>, which already implements the
unsafe trait ForeignOwnable).
Doing so violates the safety assumptions of load_rcu(), causing a
use-after-free without any unsafe block in the user's code.
> + /// Type used to immutably borrow an rcu-safe value that is currently foreign-owned.
> + type RcuBorrowed<'a>
> + where
> + Self: 'a;
> +
[ ... ]
> diff --git a/rust/kernel/sync/rcu/rcu_box.rs b/rust/kernel/sync/rcu/rcu_box.rs
> new file mode 100644
> index 0000000000000..cb1fd422480a8
> --- /dev/null
> +++ b/rust/kernel/sync/rcu/rcu_box.rs
[ ... ]
> +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 };
> + if core::mem::needs_drop::<T>() {
> + // 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, 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()) };
[Severity: High]
Does this bypass the custom Allocator parameter when dropping types that do
not require drop logic?
If an RcuBox<T, A> is created with a custom allocator A (e.g., a DMA or
memory pool allocator) where T does not implement Drop, the code directly
branches to kvfree_call_rcu().
This hardcodes the assumption that the memory belongs to kmalloc or vmalloc.
When the RcuBox is freed, kvfree_call_rcu() will attempt to free memory it
does not own, resulting in allocator corruption.
> + }
> + }
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260718053247.25154-1-boqun@kernel.org?part=2
^ permalink raw reply [flat|nested] 8+ messages in thread
* [RFC PATCH v2 3/4] rust: rcu: Introduce RcuFreeBox
2026-07-18 5:32 [RFC PATCH v2 0/4] rust: sync: Introduce Rcu*Box Boqun Feng
2026-07-18 5:32 ` [RFC PATCH v2 1/4] rust: rcu: Add RcuBox type Boqun Feng
2026-07-18 5:32 ` [RFC PATCH v2 2/4] rust: maple_tree: Add load_rcu() Boqun Feng
@ 2026-07-18 5:32 ` Boqun Feng
2026-07-18 5:42 ` sashiko-bot
2026-07-18 5:32 ` [NOT FOR MERGE] [RFC PATCH v2 4/4] rust: poll: use kfree_rcu() for PollCondVar Boqun Feng
3 siblings, 1 reply; 8+ messages in thread
From: Boqun Feng @ 2026-07-18 5:32 UTC (permalink / raw)
To: rust-for-linux, rcu
Cc: Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Daniel Almeida, Tamir Duberstein,
Alexandre Courbot, Onur Özkan, Liam R. Howlett,
Andrew Ballance, Alexander Viro, Christian Brauner, Jan Kara,
Lyude Paul, Paul E. McKenney, Frederic Weisbecker,
Neeraj Upadhyay, Joel Fernandes, Josh Triplett, Uladzislau Rezki,
Steven Rostedt, Mathieu Desnoyers, Lai Jiangshan, Zqiang,
Sumit Semwal, Christian König, Greg Kroah-Hartman,
Yury Norov (NVIDIA), Asahi Lina, Matthew Maurer, Lorenzo Stoakes,
linux-kernel, maple-tree, linux-mm, linux-fsdevel, linux-media,
dri-devel, linaro-mm-sig
The current RcuBox will call the `drop()` function after a grace period
inside an RCU callback. This suffices for maintaining a RCU-protected
object:
RcuBox::drop():
call_rcu(
|..| { // <- call back after one grace period.
T::drop(); // <- call the destructor of the inner object.
}
)
However, to support a different RCU usage pattern as below we need to
extend RcuBox:
1. clean up the object, and unshare it from future RCU readers.
2. wait for an RCU grace period.
3. no other RCU readers, we can free the memory.
An `RcuFreeBox<T: RcuFreeSafe>` is introduced to provide support for
this:
RcuFreeBox::drop():
T::drop_before_gp(); // clean up and ushare.
kfree_call_rcu(..); // free it after one grace period.
Signed-off-by: Boqun Feng <boqun@kernel.org>
---
rust/kernel/sync/rcu.rs | 34 ++++++++++++++++
rust/kernel/sync/rcu/rcu_box.rs | 72 +++++++++++++++++++++++++++++++--
2 files changed, 102 insertions(+), 4 deletions(-)
diff --git a/rust/kernel/sync/rcu.rs b/rust/kernel/sync/rcu.rs
index 42f6bbc83f71..e781a5044de9 100644
--- a/rust/kernel/sync/rcu.rs
+++ b/rust/kernel/sync/rcu.rs
@@ -4,6 +4,8 @@
//!
//! C header: [`include/linux/rcupdate.h`](srctree/include/linux/rcupdate.h)
+use core::pin::Pin;
+
use crate::{
bindings,
types::{
@@ -18,6 +20,8 @@
pub use self::rcu_box::RcuKVBox;
pub use self::rcu_box::RcuVBox;
+pub use self::rcu_box::RcuFreeBox;
+
/// Evidence that the RCU read side lock is held on the current thread/CPU.
///
/// The type is explicitly not `Send` because this property is per-thread/CPU.
@@ -100,3 +104,33 @@ pub trait ForeignOwnableRcu: ForeignOwnable {
/// [`from_foreign`]: ForeignOwnable::from_foreign
unsafe fn rcu_borrow<'a>(ptr: *mut ffi::c_void) -> Self::RcuBorrowed<'a>;
}
+
+/// Declares a struct is safe to free after a grace period if all readers are guarded by RCU.
+///
+/// # Safety
+///
+/// Implementation must guarantee `drop_before_gp()` makes sure no future RCU reader will access
+/// any part of [`Self`], as a result, after `drop_before_gp()` return + one grace period, no RCU
+/// reader will be on the object, and it's safe to free it.
+///
+/// Notes for implementators: implementing this trait in general requires `Self` being a
+/// [`UnsafePinned`], i.e. a `&mut Self` is not a noalias reference if `Self` has non-trivial
+/// `drop()` function.
+pub unsafe trait RcuFreeSafe {
+ /// Clean up `Self` and make it ready to be RCU freed.
+ fn drop_before_gp(self: Pin<&mut Self>);
+}
+
+macro_rules! impl_not_drop {
+ ($($t:ty, )*) => {
+ // SAFETY: Dropping `T` has no side effect means `T` is always ready to be freed. And an
+ // empty `drop_before_gp()` suffices.
+ $(unsafe impl RcuFreeSafe for $t {
+ fn drop_before_gp(self: Pin<&mut Self>) {
+ $crate::const_assert!(!core::mem::needs_drop::<$t>());
+ }
+ })*
+ }
+}
+
+impl_not_drop! {i8,u8,i16,u16,i32,u32,isize,usize,i64,u64,}
diff --git a/rust/kernel/sync/rcu/rcu_box.rs b/rust/kernel/sync/rcu/rcu_box.rs
index cb1fd422480a..ab4e9b2a4444 100644
--- a/rust/kernel/sync/rcu/rcu_box.rs
+++ b/rust/kernel/sync/rcu/rcu_box.rs
@@ -6,6 +6,7 @@
use core::{
marker::PhantomData,
+ mem::ManuallyDrop,
ops::Deref,
ptr::NonNull, //
};
@@ -29,17 +30,18 @@
use super::{
ForeignOwnableRcu,
- Guard, //
+ Guard,
+ RcuFreeSafe, //
};
-/// A box that is freed with rcu.
+/// A box that is drop with RCU.
///
-/// The value must be `Send`, as rcu may drop it on another thread.
+/// 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 `A`.
-/// * This `RcuBox` holds exclusive permissions to rcu free the allocation.
+/// * This `RcuBox` holds exclusive permissions to RCU-free the allocation.
pub struct RcuBox<T: Send, A: Allocator>(NonNull<RcuBoxInner<T>>, PhantomData<A>);
/// Type alias for [`RcuBox`] with a [`Kmalloc`] allocator.
@@ -223,6 +225,56 @@ fn drop(&mut self) {
drop(unsafe { Box::<_, A>::from_raw(box_inner) });
}
+/// A box that is freed with RCU.
+///
+/// Currently we require `T` being `Send` because of an implementation limitation. In theory we can
+/// support `T` being `!Send`, since the RCU callback is only used to free the memory, not dropping
+/// `T`.
+pub struct RcuFreeBox<T: Send + RcuFreeSafe, A: Allocator>(RcuBox<ManuallyDrop<T>, A>);
+
+impl<T: Send + RcuFreeSafe, A: Allocator> RcuFreeBox<T, A> {
+ /// Create a new `RcuFreeBox`.
+ pub fn new(x: T, flags: alloc::Flags) -> Result<Self, AllocError> {
+ Ok(Self(RcuBox::new(ManuallyDrop::new(x), flags)?))
+ }
+}
+
+impl<T: Send + RcuFreeSafe, A: Allocator> Deref for RcuFreeBox<T, A> {
+ type Target = T;
+
+ fn deref(&self) -> &T {
+ self.0.deref()
+ }
+}
+
+impl<T: Send + RcuFreeSafe, A: Allocator> Drop for RcuFreeBox<T, A> {
+ fn drop(&mut self) {
+ // CAST: `ManuallyDrop<RcuBoxInner<T>>` is transparent to `RcuBoxInner<T>`, and `RcuBox`
+ // owns the object per type invariants.
+ let inner: *mut RcuBoxInner<T> = self.0 .0.as_ptr().cast();
+
+ // SAFETY: Per the invariants of `RcuBox`, `inner` owns the pointed object. And we are not
+ // going to move it.
+ let pin = unsafe { Pin::new_unchecked(&mut (*inner).value) };
+
+ pin.drop_before_gp();
+
+ // `needs_drop::<ManuallyDrop>()` returns `false`, hence `kvfree_call_rcu()` will be called
+ // and free the underlying data after a grace period.
+ }
+}
+
+// Note that `T: Sync` is required since when moving an `RcuFreeBox<T, A>`, the previous owner may
+// still access `&T` for one grace period.
+//
+// SAFETY: Ownership of the `RcuFreeBox<T, A>` allows for `&T` and dropping the `T`, so `T: Send +
+// Sync` implies `RcuFreeBox<T, A>: Send`.
+unsafe impl<T: Send + Sync + RcuFreeSafe, A: Allocator> Send for RcuFreeBox<T, A> {}
+
+// SAFETY: `&RcuFreeBox<T, A>` allows for no operations other than those permitted by `&T`, so `T:
+// Sync` implies `RcuFreeBox<T, A>: Sync`.
+unsafe impl<T: Send + Sync + RcuFreeSafe, A: Allocator> Sync for RcuFreeBox<T, A> {}
+
#[kunit_tests(rust_rcu_box)]
mod tests {
use super::*;
@@ -236,6 +288,12 @@ fn rcu_box_basic() -> Result {
drop(rb);
+ let rb = RcuFreeBox::<_, alloc::allocator::Kmalloc>::new(42i32, alloc::flags::GFP_KERNEL)?;
+
+ assert_eq!(*rb, 42);
+
+ drop(rb);
+
let rb = RcuBox::<_, alloc::allocator::Vmalloc>::new(42i32, alloc::flags::GFP_KERNEL)?;
assert_eq!(*rb, 42);
@@ -243,6 +301,12 @@ fn rcu_box_basic() -> Result {
drop(rb);
+ let rb = RcuFreeBox::<_, alloc::allocator::Vmalloc>::new(42i32, alloc::flags::GFP_KERNEL)?;
+
+ assert_eq!(*rb, 42);
+
+ drop(rb);
+
Ok(())
}
}
--
2.50.1 (Apple Git-155)
^ permalink raw reply related [flat|nested] 8+ messages in thread* Re: [RFC PATCH v2 3/4] rust: rcu: Introduce RcuFreeBox
2026-07-18 5:32 ` [RFC PATCH v2 3/4] rust: rcu: Introduce RcuFreeBox Boqun Feng
@ 2026-07-18 5:42 ` sashiko-bot
0 siblings, 0 replies; 8+ messages in thread
From: sashiko-bot @ 2026-07-18 5:42 UTC (permalink / raw)
To: Boqun Feng; +Cc: media-ci, ojeda, dri-devel
Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider:
New issues:
- [High] `RcuFreeBox::drop` creates an exclusive mutable reference (`&mut T`) while concurrent RCU readers may exist, causing Undefined Behavior (Mutable Aliasing).
- [High] `RcuFreeBox` permanently skips `T::drop()`, silently leaking inner resources or forcing developers into Use-After-Free traps.
- [High] Casting between `*mut RcuBoxInner<ManuallyDrop<T>>` and `*mut RcuBoxInner<T>` relies on unguaranteed memory layout, resulting in Undefined Behavior.
- [High] If memory allocation fails in `RcuFreeBox::new()`, the inner object is silently leaked.
- [Medium] `RcuFreeBox` lacks implementations for `ForeignOwnable` and `ForeignOwnableRcu`, making it completely unusable in RCU data structures.
- [Low] Missing `#[inline]` annotations on small forwarding functions in an abstraction.
Pre-existing issues:
- [High] `RcuBox::drop` unconditionally invokes `kvfree_call_rcu`, bypassing custom generic allocators and causing kernel memory corruption.
--
--- Patch [3]: [RFC PATCH v2 3/4] rust: rcu: Introduce RcuFreeBox ---
commit a85d6377ca5df1e0c96d95e2dd50fb0b8a721195
Author: Boqun Feng <boqun@kernel.org>
Subject: rust: rcu: Introduce RcuFreeBox
This commit introduces RcuFreeBox, which extends RcuBox to support an RCU
usage pattern where objects are cleaned up before a grace period and freed
afterwards. It provides methods to unshare an object from future RCU
readers and wait for a grace period before safely freeing the memory.
> diff --git a/rust/kernel/sync/rcu.rs b/rust/kernel/sync/rcu.rs
> index 42f6bbc83f71f..e781a5044de9b 100644
> --- a/rust/kernel/sync/rcu.rs
> +++ b/rust/kernel/sync/rcu.rs
[ ... ]
> @@ -100,3 +104,33 @@ pub trait ForeignOwnableRcu: ForeignOwnable {
> /// [`from_foreign`]: ForeignOwnable::from_foreign
> unsafe fn rcu_borrow<'a>(ptr: *mut ffi::c_void) -> Self::RcuBorrowed<'a>;
> }
> +
> +/// Declares a struct is safe to free after a grace period if all readers are guarded by RCU.
> +///
> +/// # Safety
> +///
> +/// Implementation must guarantee `drop_before_gp()` makes sure no future RCU reader will access
> +/// any part of [`Self`], as a result, after `drop_before_gp()` return + one grace period, no RCU
> +/// reader will be on the object, and it's safe to free it.
> +///
> +/// Notes for implementators: implementing this trait in general requires `Self` being a
> +/// [`UnsafePinned`], i.e. a `&mut Self` is not a noalias reference if `Self` has non-trivial
> +/// `drop()` function.
> +pub unsafe trait RcuFreeSafe {
> + /// Clean up `Self` and make it ready to be RCU freed.
> + fn drop_before_gp(self: Pin<&mut Self>);
[Severity: High]
Does this trait signature inherently force a mutable aliasing violation?
By requiring a mutable reference here, does this force the caller to
create an exclusive reference while concurrent RCU readers may still hold
shared references to the object?
> +}
[ ... ]
> diff --git a/rust/kernel/sync/rcu/rcu_box.rs b/rust/kernel/sync/rcu/rcu_box.rs
> index cb1fd422480a8..ab4e9b2a44448 100644
> --- a/rust/kernel/sync/rcu/rcu_box.rs
> +++ b/rust/kernel/sync/rcu/rcu_box.rs
[ ... ]
> @@ -223,6 +225,56 @@ fn drop(&mut self) {
> drop(unsafe { Box::<_, A>::from_raw(box_inner) });
> }
>
> +/// A box that is freed with RCU.
> +///
> +/// Currently we require `T` being `Send` because of an implementation limitation. In theory we can
> +/// support `T` being `!Send`, since the RCU callback is only used to free the memory, not dropping
> +/// `T`.
> +pub struct RcuFreeBox<T: Send + RcuFreeSafe, A: Allocator>(RcuBox<ManuallyDrop<T>, A>);
[Severity: Medium]
How can this struct be used in RCU data structures?
Since it lacks implementations for ForeignOwnable and ForeignOwnableRcu,
and its inner RcuBox field is private, is it structurally possible to
convert this to a foreign pointer or share it with RCU readers?
> +
> +impl<T: Send + RcuFreeSafe, A: Allocator> RcuFreeBox<T, A> {
> + /// Create a new `RcuFreeBox`.
> + pub fn new(x: T, flags: alloc::Flags) -> Result<Self, AllocError> {
[Severity: Low]
Should this small forwarding function have an inline annotation?
> + Ok(Self(RcuBox::new(ManuallyDrop::new(x), flags)?))
[Severity: High]
Does this silently leak the inner object on allocation failure?
If RcuBox::new() returns an AllocError, the ? operator will abort the
function. Because ManuallyDrop intentionally prevents the Drop
implementation of its inner value from running, does this leak the object
and any dynamic resources it holds?
> + }
> +}
> +
> +impl<T: Send + RcuFreeSafe, A: Allocator> Deref for RcuFreeBox<T, A> {
> + type Target = T;
> +
> + fn deref(&self) -> &T {
[Severity: Low]
Should this trivial deref forwarder have an inline annotation?
> + self.0.deref()
> + }
> +}
> +
> +impl<T: Send + RcuFreeSafe, A: Allocator> Drop for RcuFreeBox<T, A> {
> + fn drop(&mut self) {
[Severity: High]
This isn't a bug introduced by this patch, but does the underlying
RcuBox::drop unconditionally bypass custom allocators?
When core::mem::needs_drop::<T>() evaluates to false, RcuBox::drop
bypasses the allocator A and calls bindings::kvfree_call_rcu():
RcuBox::drop() {
...
unsafe { bindings::kvfree_call_rcu(rcu_head, self.0.as_ptr().cast()) };
...
}
If a custom allocator (such as a module-specific slab cache) is used,
will passing its pointer to kvfree result in memory corruption?
> + // CAST: `ManuallyDrop<RcuBoxInner<T>>` is transparent to `RcuBoxInner<T>`, and `RcuBox`
> + // owns the object per type invariants.
> + let inner: *mut RcuBoxInner<T> = self.0 .0.as_ptr().cast();
[Severity: High]
Does casting between these types rely on unguaranteed memory layout?
Both RcuBoxInner<ManuallyDrop<T>> and RcuBoxInner<T> use the default
repr(Rust) layout.
Since the Rust compiler provides no layout guarantees for generic
repr(Rust) structs across different type parameters, can fields be
arbitrarily reordered, making this cast undefined behavior?
> +
> + // SAFETY: Per the invariants of `RcuBox`, `inner` owns the pointed object. And we are not
> + // going to move it.
> + let pin = unsafe { Pin::new_unchecked(&mut (*inner).value) };
[Severity: High]
Does creating this exclusive mutable reference cause undefined behavior?
When RcuFreeBox::drop() executes before the RCU grace period,
pre-existing RCU readers may still be traversing the object and holding
shared references.
Does forming a mutable reference while shared references are active
violate Rust aliasing rules?
> +
> + pin.drop_before_gp();
> +
> + // `needs_drop::<ManuallyDrop>()` returns `false`, hence `kvfree_call_rcu()` will be called
> + // and free the underlying data after a grace period.
[Severity: High]
Does permanently skipping T::drop() force a memory leak or a
use-after-free?
Because ManuallyDrop evaluates needs_drop to false, kvfree_call_rcu()
will only free the outermost struct after the grace period, bypassing
T::drop().
If T owns dynamic resources like a Box or Vec, will they permanently leak?
If a developer attempts to fix the leak by manually dropping the inner
fields inside drop_before_gp(), will they trigger a use-after-free
because concurrent RCU readers can still access those fields before the
grace period ends?
> + }
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260718053247.25154-1-boqun@kernel.org?part=3
^ permalink raw reply [flat|nested] 8+ messages in thread
* [NOT FOR MERGE] [RFC PATCH v2 4/4] rust: poll: use kfree_rcu() for PollCondVar
2026-07-18 5:32 [RFC PATCH v2 0/4] rust: sync: Introduce Rcu*Box Boqun Feng
` (2 preceding siblings ...)
2026-07-18 5:32 ` [RFC PATCH v2 3/4] rust: rcu: Introduce RcuFreeBox Boqun Feng
@ 2026-07-18 5:32 ` Boqun Feng
3 siblings, 0 replies; 8+ messages in thread
From: Boqun Feng @ 2026-07-18 5:32 UTC (permalink / raw)
To: rust-for-linux, rcu
Cc: Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Daniel Almeida, Tamir Duberstein,
Alexandre Courbot, Onur Özkan, Liam R. Howlett,
Andrew Ballance, Alexander Viro, Christian Brauner, Jan Kara,
Lyude Paul, Paul E. McKenney, Frederic Weisbecker,
Neeraj Upadhyay, Joel Fernandes, Josh Triplett, Uladzislau Rezki,
Steven Rostedt, Mathieu Desnoyers, Lai Jiangshan, Zqiang,
Sumit Semwal, Christian König, Greg Kroah-Hartman,
Yury Norov (NVIDIA), Asahi Lina, Matthew Maurer, Lorenzo Stoakes,
linux-kernel, maple-tree, linux-mm, linux-fsdevel, linux-media,
dri-devel, linaro-mm-sig
From: Alice Ryhl <aliceryhl@google.com>
Rust Binder currently uses PollCondVar, but it calls synchronize_rcu()
in the destructor, which we would like to avoid. Add a variation of
PollCondVar that kfree_rcu() instead.
One could avoid the `rcu` field and allocate the rcu_head on drop using
a fallback to synchronize_rcu() on ENOMEM. However, I'd prefer to avoid
the potential for synchronize_rcu(), and Binder will only use this for a
small fraction of processes, so even if it changes which kmalloc bucket
it falls into, the extra memory is not a problem.
Signed-off-by: Alice Ryhl <aliceryhl@google.com>
[boqun: Use RcuFreeSafe]
Signed-off-by: Boqun Feng <boqun@kernel.org>
---
rust/kernel/sync/poll.rs | 26 +++++++++++++++++++++-----
1 file changed, 21 insertions(+), 5 deletions(-)
diff --git a/rust/kernel/sync/poll.rs b/rust/kernel/sync/poll.rs
index 5aa0ce9ba01b..830a4763a5c1 100644
--- a/rust/kernel/sync/poll.rs
+++ b/rust/kernel/sync/poll.rs
@@ -9,12 +9,19 @@
fs::File,
prelude::*,
sync::{
- rcu::synchronize_rcu,
+ rcu::{
+ synchronize_rcu, //
+ RcuFreeSafe,
+ },
CondVar,
LockClassKey, //
}, //
};
-use core::{marker::PhantomData, ops::Deref};
+
+use core::{
+ marker::PhantomData,
+ ops::Deref, //
+};
/// Creates a [`PollCondVar`] initialiser with the given name and a newly-created lock class.
#[macro_export]
@@ -70,6 +77,7 @@ pub fn register_wait(&self, file: &File, cv: &PollCondVar) {
///
/// [`CondVar`]: crate::sync::CondVar
#[pin_data(PinnedDrop)]
+#[repr(transparent)]
pub struct PollCondVar {
#[pin]
inner: CondVar,
@@ -97,12 +105,20 @@ fn deref(&self) -> &CondVar {
impl PinnedDrop for PollCondVar {
#[inline]
fn drop(self: Pin<&mut Self>) {
+ self.drop_before_gp();
+
+ // Wait for epoll items to be properly removed.
+ synchronize_rcu();
+ }
+}
+
+// SAFETY: __wake_up_pollfree() guarantees all the epoll items on the wait list will be gone after
+// one grace period.
+unsafe impl RcuFreeSafe for PollCondVar {
+ fn drop_before_gp(self: Pin<&mut Self>) {
// Clear anything registered using `register_wait`.
//
// SAFETY: The pointer points at a valid `wait_queue_head`.
unsafe { bindings::__wake_up_pollfree(self.inner.wait_queue_head.get()) };
-
- // Wait for epoll items to be properly removed.
- synchronize_rcu();
}
}
--
2.50.1 (Apple Git-155)
^ permalink raw reply related [flat|nested] 8+ messages in thread