From: "Onur Özkan" <work@onurozkan.dev>
To: Gary Guo <gary@garyguo.net>
Cc: rcu@vger.kernel.org, rust-for-linux@vger.kernel.org,
linux-kernel@vger.kernel.org, ojeda@kernel.org, boqun@kernel.org,
bjorn3_gh@protonmail.com, lossin@kernel.org,
a.hindborg@kernel.org, aliceryhl@google.com, tmgross@umich.edu,
dakr@kernel.org, peterz@infradead.org, fujita.tomonori@gmail.com,
tamird@kernel.org, jiangshanlai@gmail.com, paulmck@kernel.org,
josh@joshtriplett.org, rostedt@goodmis.org,
mathieu.desnoyers@efficios.com
Subject: Re: [PATCH v2 2/3] rust: sync: add SRCU abstraction
Date: Sun, 3 May 2026 06:39:40 +0300 [thread overview]
Message-ID: <20260503034008.36917-1-work@onurozkan.dev> (raw)
In-Reply-To: <DI8DUMY6GAM6.24VYS1478B7L8@garyguo.net>
On Sat, 02 May 2026 18:55:56 +0100
Gary Guo <gary@garyguo.net> wrote:
> On Sat May 2, 2026 at 5:27 PM BST, Onur Özkan wrote:
> > Add a Rust abstraction for sleepable RCU.
> >
> > Add a Rust abstraction for sleepable RCU (SRCU), backed by C srcu_struct.
> > Provide FFI helpers and a safe wrapper with a guard-based API for read-side
> > critical sections.
> >
> > Signed-off-by: Onur Özkan <work@onurozkan.dev>
> > ---
> > rust/kernel/sync.rs | 2 +
> > rust/kernel/sync/srcu.rs | 152 +++++++++++++++++++++++++++++++++++++++
> > 2 files changed, 154 insertions(+)
> > create mode 100644 rust/kernel/sync/srcu.rs
> >
> > diff --git a/rust/kernel/sync.rs b/rust/kernel/sync.rs
> > index 993dbf2caa0e..0d6a5f1300c3 100644
> > --- a/rust/kernel/sync.rs
> > +++ b/rust/kernel/sync.rs
> > @@ -21,6 +21,7 @@
> > pub mod rcu;
> > mod refcount;
> > mod set_once;
> > +pub mod srcu;
> >
> > pub use arc::{Arc, ArcBorrow, UniqueArc};
> > pub use completion::Completion;
> > @@ -31,6 +32,7 @@
> > pub use locked_by::LockedBy;
> > pub use refcount::Refcount;
> > pub use set_once::SetOnce;
> > +pub use srcu::Srcu;
> >
> > /// Represents a lockdep class.
> > ///
> > diff --git a/rust/kernel/sync/srcu.rs b/rust/kernel/sync/srcu.rs
> > new file mode 100644
> > index 000000000000..7bd713e96375
> > --- /dev/null
> > +++ b/rust/kernel/sync/srcu.rs
> > @@ -0,0 +1,152 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +
> > +//! Sleepable read-copy update (SRCU) support.
> > +//!
> > +//! C header: [`include/linux/srcu.h`](srctree/include/linux/srcu.h)
> > +
> > +use crate::{
> > + bindings,
> > + error::to_result,
> > + prelude::*,
> > + sync::LockClassKey,
> > + types::{
> > + NotThreadSafe,
> > + Opaque, //
> > + },
> > +};
> > +
> > +use pin_init::pin_data;
> > +
> > +/// Creates an [`Srcu`] initialiser with the given name and a newly-created lock class.
> > +#[macro_export]
>
> #[doc(hidden)]
>
> Given this is a new macro, let's not encourage user from using it from crate
> root.
>
> > +macro_rules! new_srcu {
> > + ($($name:literal)?) => {
> > + $crate::sync::Srcu::new($crate::optional_name!($($name)?), $crate::static_lock_class!())
> > + };
> > +}
> > +pub use new_srcu;
> > +
> > +/// Sleepable read-copy update primitive.
> > +///
> > +/// SRCU readers may sleep while holding the read-side guard.
> > +///
> > +/// The destructor may sleep.
> > +///
> > +/// # Invariants
> > +///
> > +/// This represents a valid `struct srcu_struct` initialized by the C SRCU API
> > +/// and it remains pinned and valid until the pinned destructor runs.
> > +#[repr(transparent)]
> > +#[pin_data(PinnedDrop)]
> > +pub struct Srcu {
> > + #[pin]
> > + inner: Opaque<bindings::srcu_struct>,
> > +}
> > +
> > +impl Srcu {
> > + /// Creates a new SRCU instance.
> > + #[inline]
> > + pub fn new(name: &'static CStr, key: Pin<&'static LockClassKey>) -> impl PinInit<Self, Error> {
> > + try_pin_init!(Self {
> > + inner <- Opaque::try_ffi_init(|ptr: *mut bindings::srcu_struct| {
> > + // SAFETY: `ptr` points to valid uninitialised memory for a `srcu_struct`.
> > + to_result(unsafe {
> > + bindings::init_srcu_struct_with_key(ptr, name.as_char_ptr(), key.as_ptr())
> > + })
> > + }),
> > + })
> > + }
> > +
> > + /// Enters an SRCU read-side critical section.
> > + ///
> > + /// # Safety
> > + ///
> > + /// The returned [`Guard`] must not be leaked. Leaking it with [`core::mem::forget`]
> > + /// leaves the SRCU read-side critical section active.
>
> I generally would prefer if we could use guard-like API instead of forcing a
> callback.
Me too and developers can still do that. I think the safety contract here is
very simple to handle. It's essentially this:
// SAFETY: Guard is not leaked.
let _guard = unsafe { x.read_lock() };
To me it's very simple and straightforward for both the developer and the
reviewer. It doesn't add any overhead to the implementation and it ensures
that the developer (and later the reviewer) is aware of the potential issue.
Of course, there's also the safe option if the developer is happy with
closure-based API:
x.with_read_lock(|_guard| {
...
});
So it allows you to use the guard-based approach directly with the requirement
of a safety comment and also provides a safe API for developers who don't want
to deal with that. I am not sure if you fall into the third category, which is
"I don't like writing safety comments and I don't like the closure-based
approach" :)
>
> I suppose the only reason that this is unsafe is the "just leak it" condition
> when cleaning up SRCU struct, which skips cleaning up delayed work, which could
> call into `process_srcu`, which accesses `srcu_struct`. This however is *not*
> leaked, because it's controlled by the user. Only the auxillary data allocated
> by SRCU is leaked. So UAF is going to happen.
>
> So in some aspect, the leak caused by `srcu_readers_active(ssp)` can cause more
> damage compared to just continuing cleanup despite active users? I think this
> could be changed in one of these ways:
> * Have SRCU allocate all memory instead, and user-side would just have a
> `struct srcu_struct*`; then leaking would be safe. This is probably a bit
> difficult to change because it affects many users.
We could do the same on the Rust side only. Basically instead of embedding
srcu_struct in Rust srcu, allocate it separately and store its pointer. Then,
if cleanup hits the active reader case, we could leak that allocation so any
remaining srcu work does not hit UAF. I was aware of this option but I would
prefer to avoid it because it adds an extra allocation for every Rust srcu.
> * Continue to flush delayed work and stop the timer, and then leak before the
> actual kfree happens.
Can you say more? I didn't understand this particular solution.
> * Trigger a `BUG` when the leak condition is hit for Rust users.
We need an atomic counter to detect the leak and I thought that would be too
much overhead for this abstraction. Basically each lock and drop will call an
atomic operation so.
> * Declare the `WARN_ON` to be a sufficient protection and say this can be
> considered safe. Kinda similar to the strategy we take to the
> sleep-inside-atomic context issue.
I think this is a rather weak precaution.
>
> > + #[inline]
> > + pub unsafe fn read_lock(&self) -> Guard<'_> {
> > + // SAFETY: By the type invariants, `self` contains a valid `struct srcu_struct`.
> > + let idx = unsafe { bindings::srcu_read_lock(self.inner.get()) };
> > +
> > + // INVARIANT: `idx` was returned by `srcu_read_lock()` for this `Srcu`.
> > + Guard {
> > + srcu: self,
> > + idx,
> > + _not_send: NotThreadSafe,
> > + }
> > + }
> > +
> > + /// Runs `f` in an SRCU read-side critical section.
> > + #[inline]
> > + pub fn with_read_lock<T>(&self, f: impl FnOnce(&Guard<'_>) -> T) -> T {
> > + // SAFETY: The guard is owned within this function and is not leaked.
> > + let guard = unsafe { self.read_lock() };
> > +
> > + f(&guard)
> > + }
> > +
> > + /// Waits until all pre-existing SRCU readers have completed.
> > + #[inline]
> > + pub fn synchronize(&self) {
> > + // SAFETY: By the type invariants, `self` contains a valid `struct srcu_struct`.
> > + unsafe { bindings::synchronize_srcu(self.inner.get()) };
> > + }
> > +
> > + /// Waits until all pre-existing SRCU readers have completed, expedited.
> > + ///
> > + /// This requests a lower-latency grace period than [`Srcu::synchronize`] typically
> > + /// at the cost of higher system-wide overhead. Prefer [`Srcu::synchronize`] by default
> > + /// and use this variant only when reducing reset or teardown latency is more important
> > + /// than the extra cost.
> > + #[inline]
> > + pub fn synchronize_expedited(&self) {
> > + // SAFETY: By the type invariants, `self` contains a valid `struct srcu_struct`.
> > + unsafe { bindings::synchronize_srcu_expedited(self.inner.get()) };
> > + }
> > +}
> > +
> > +#[pinned_drop]
> > +impl PinnedDrop for Srcu {
> > + fn drop(self: Pin<&mut Self>) {
> > + let ptr = self.inner.get();
> > +
>
> This needs a comment on why this is invoked. Something like:
>
> // Ensure all SRCU callbacks have been finished before freeing.
>
> Best,
> Gary
>
> > + // SAFETY: By the type invariants, `self` contains a valid and pinned `struct srcu_struct`.
> > + unsafe { bindings::srcu_barrier(ptr) };
> > + // SAFETY: Same as above.
> > + unsafe { bindings::cleanup_srcu_struct(ptr) };
> > + }
> > +}
> > +
> > +// SAFETY: `srcu_struct` may be shared and used across threads.
> > +unsafe impl Send for Srcu {}
> > +// SAFETY: `srcu_struct` may be shared and used concurrently.
> > +unsafe impl Sync for Srcu {}
next prev parent reply other threads:[~2026-05-03 3:40 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-05-02 16:27 [PATCH v2 0/3] rust: add SRCU abstraction Onur Özkan
2026-05-02 16:27 ` [PATCH v2 1/3] rust: helpers: add SRCU helpers Onur Özkan
2026-05-02 16:27 ` [PATCH v2 2/3] rust: sync: add SRCU abstraction Onur Özkan
2026-05-02 17:55 ` Gary Guo
2026-05-03 3:39 ` Onur Özkan [this message]
2026-05-03 19:25 ` Gary Guo
2026-05-02 16:27 ` [PATCH v2 3/3] MAINTAINERS: add Rust SRCU files to SRCU entry Onur Özkan
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260503034008.36917-1-work@onurozkan.dev \
--to=work@onurozkan.dev \
--cc=a.hindborg@kernel.org \
--cc=aliceryhl@google.com \
--cc=bjorn3_gh@protonmail.com \
--cc=boqun@kernel.org \
--cc=dakr@kernel.org \
--cc=fujita.tomonori@gmail.com \
--cc=gary@garyguo.net \
--cc=jiangshanlai@gmail.com \
--cc=josh@joshtriplett.org \
--cc=linux-kernel@vger.kernel.org \
--cc=lossin@kernel.org \
--cc=mathieu.desnoyers@efficios.com \
--cc=ojeda@kernel.org \
--cc=paulmck@kernel.org \
--cc=peterz@infradead.org \
--cc=rcu@vger.kernel.org \
--cc=rostedt@goodmis.org \
--cc=rust-for-linux@vger.kernel.org \
--cc=tamird@kernel.org \
--cc=tmgross@umich.edu \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox