From: Priya Bala Govindasamy <pgovind2@uci.edu>
To: peterz@infradead.org, mingo@redhat.com, will@kernel.org,
boqun@kernel.org, gary@garyguo.net, aliceryhl@google.com,
ojeda@kernel.org
Cc: longman@redhat.com, lyude@redhat.com,
daniel.almeida@collabora.com, work@onurozkan.dev,
bjorn3_gh@protonmail.com, lossin@kernel.org,
a.hindborg@kernel.org, tmgross@umich.edu, dakr@kernel.org,
tamird@kernel.org, acourbot@nvidia.com,
rust-for-linux@vger.kernel.org, ardalan@uci.edu,
zhiyunq@cs.ucr.edu, dzueck@uci.edu, pgovind2@uci.edu
Subject: [PATCH 0/1] rust: sync: Make `GlobalLockedBy::as_ref` unavailable if T is not Sync
Date: Fri, 16 Oct 2026 17:18:55 +0000 [thread overview]
Message-ID: <cover.1791594262.git.pgovind2@uci.edu> (raw)
Dear Linux kernel maintainers,
We are developing a tool called FerroLens to detect potential
unsound behavior in Rust code in the Linux kernel. The tool
internally uses an LLM to detect bugs.
We then perform manual analysis to verify these reports.
FerroLens reported the following bug in rust/kernel/sync/lock/global.rs:
The `GlobalLockedBy::as_ref` method allows `&T` to be obtained from
mutliple threads even if `T` is not Sync.
This happens because `GlobalLockedBy<T, B>` is Sync if
`LockedBy<T, B::item>` is Sync. `LockedBy<T, B>` is Sync if `T` is Send.
Since there is no requirement that `T` also implements Sync, it is
possible for multiple threads to simultaneously obtain references to a
`GlobalLockedBy<T, B>` and then acquire `&T` through
`GlobalLockedBy::as_ref`. This can lead to undefined behavior if `T` does
not implement Sync.
Note that a reference to `GlobalGuard<B>` is required as an argument to
`GlobalLockedBy::as_ref`. Therefore, this issue requires that
`GlobalGuard<B>` is `Sync` which happens if `B::item` is `Sync`
(for example, ()).
Here is a PoC kernel module demonstrating the issue.
use core::cell::Cell;
use kernel::{
bindings, c_str,
error::from_err_ptr,
prelude::*,
sync::{Completion, GlobalGuard, GlobalLockedBy},
};
use pin_init::stack_pin_init;
module! {
type: LockedByPoc,
name: "locked_by_poc",
authors: ["Priya Govindasamy"],
description: "PoC for GlobalLockedBy::as_ref on a non-Sync value",
license: "GPL",
}
kernel::sync::global_lock! {
// SAFETY: Initialized exactly once in `LockedByPoc::init`, before use.
unsafe(uninit) static POC_LOCK: Mutex<()> = ();
}
struct LockedByPoc;
// `Cell<u32>` is `Send` but not `Sync`, so this type is also `Send` but not `Sync`.
struct SendButNotSync {
value: Cell<u32>,
}
fn assert_sync<T: Sync>() {}
#[pin_data]
struct ThreadContext<'a> {
protected: &'a GlobalLockedBy<SendButNotSync, POC_LOCK>,
guard: &'a GlobalGuard<POC_LOCK>,
thread_id: u32,
#[pin]
done: Completion,
}
unsafe extern "C" fn thread_main(data: *mut kernel::ffi::c_void) -> i32 {
// SAFETY: `data` points to a pinned `ThreadContext` in `init`. `init` waits for this thread
// to run and then joins it before dropping the context or either borrowed reference.
let ctx = unsafe { &*data.cast::<ThreadContext<'_>>() };
// This is accepted despite `SendButNotSync: !Sync`.
let value = ctx.protected.as_ref(ctx.guard);
pr_info!(
"thread {} acquired &SendButNotSync through GlobalLockedBy::as_ref (value={})\n",
ctx.thread_id,
value.value.get()
);
ctx.done.complete_all();
0
}
fn spawn_thread(ctx: &ThreadContext<'_>) -> Result<*mut bindings::task_struct> {
let task = match from_err_ptr(unsafe {
bindings::kthread_create_on_node(
Some(thread_main),
(ctx as *const ThreadContext<'_>).cast_mut().cast(),
bindings::NUMA_NO_NODE,
c_str!("locked_by_thread/%u").as_char_ptr(),
ctx.thread_id,
)
}) {
Ok(task) => task,
Err(err) => return Err(err),
};
// SAFETY: `task` is the sleeping kthread returned by `kthread_create_on_node`.
unsafe { bindings::wake_up_process(task) };
Ok(task)
}
impl kernel::Module for LockedByPoc {
fn init(_module: &'static ThisModule) -> Result<Self> {
// SAFETY: Module initialization executes once for this static global lock.
unsafe { POC_LOCK.init() };
// `SendButNotSync` is not `Sync`, but this assertion currently compiles because
// `GlobalLockedBy` only requires its protected value to be `Send` to implement `Sync`.
assert_sync::<GlobalLockedBy<SendButNotSync, POC_LOCK>>();
pr_info!("GlobalLockedBy<SendButNotSync, POC_LOCK> is Sync\n");
assert_sync::<GlobalGuard<POC_LOCK>>();
pr_info!("GlobalGuard<POC_LOCK> is Sync\n");
let protected = GlobalLockedBy::<SendButNotSync, POC_LOCK>::new(SendButNotSync {
value: Cell::new(0),
});
let guard = POC_LOCK.lock();
let protected_ref = &protected;
let guard_ref = &guard;
stack_pin_init!(let thread_a = pin_init!(ThreadContext {
protected: protected_ref,
guard: guard_ref,
thread_id: 1,
done <- Completion::new(),
}));
stack_pin_init!(let thread_b = pin_init!(ThreadContext {
protected: protected_ref,
guard: guard_ref,
thread_id: 2,
done <- Completion::new(),
}));
let task_a = spawn_thread(thread_a.as_ref().get_ref())?;
let task_b = match spawn_thread(thread_b.as_ref().get_ref()) {
Ok(task) => task,
Err(err) => {
thread_a.done.wait_for_completion();
// SAFETY: The callback has already run; this joins it before its context dies.
unsafe { bindings::kthread_stop(task_a) };
return Err(err);
}
};
// Each callback prints before signalling. Wait for both callbacks to run, then join
// their kthreads before the stack contexts or their borrowed values are dropped.
thread_a.done.wait_for_completion();
thread_b.done.wait_for_completion();
unsafe {
bindings::kthread_stop(task_a);
bindings::kthread_stop(task_b);
}
Ok(Self)
}
}
impl Drop for LockedByPoc {
fn drop(&mut self) {
pr_info!("locked_by_poc unloaded\n");
}
}
Output:
[91306.702534] locked_by_poc: GlobalLockedBy<SendButNotSync, POC_LOCK> is Sync
[91306.702563] locked_by_poc: GlobalGuard<POC_LOCK> is Sync
[91306.706793] locked_by_poc: thread 1 acquired &SendButNotSync through GlobalLockedBy::as_ref (value=0)
[91306.707231] locked_by_poc: thread 2 acquired &SendButNotSync through GlobalLockedBy::as_ref (value=0)
Priya Bala Govindasamy (1):
rust: sync: Make `GlobalLockedBy::as_ref` unavailable if T is not Sync
rust/kernel/sync/lock/global.rs | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
--
2.34.1
next reply other threads:[~2026-10-16 17:19 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-16 17:18 Priya Bala Govindasamy [this message]
2026-10-16 17:18 ` [PATCH 1/1] rust: sync: Make `GlobalLockedBy::as_ref` unavailable if T is not Sync Priya Bala Govindasamy
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=cover.1791594262.git.pgovind2@uci.edu \
--to=pgovind2@uci.edu \
--cc=a.hindborg@kernel.org \
--cc=acourbot@nvidia.com \
--cc=aliceryhl@google.com \
--cc=ardalan@uci.edu \
--cc=bjorn3_gh@protonmail.com \
--cc=boqun@kernel.org \
--cc=dakr@kernel.org \
--cc=daniel.almeida@collabora.com \
--cc=dzueck@uci.edu \
--cc=gary@garyguo.net \
--cc=longman@redhat.com \
--cc=lossin@kernel.org \
--cc=lyude@redhat.com \
--cc=mingo@redhat.com \
--cc=ojeda@kernel.org \
--cc=peterz@infradead.org \
--cc=rust-for-linux@vger.kernel.org \
--cc=tamird@kernel.org \
--cc=tmgross@umich.edu \
--cc=will@kernel.org \
--cc=work@onurozkan.dev \
--cc=zhiyunq@cs.ucr.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