From: Marco Elver <elver@google.com>
To: elver@google.com, Boqun Feng <boqun.feng@gmail.com>,
Miguel Ojeda <ojeda@kernel.org>, Gary Guo <gary@garyguo.net>
Cc: "Alexander Potapenko" <glider@google.com>,
kasan-dev@googlegroups.com, linux-kernel@vger.kernel.org,
"Jonathan Corbet" <corbet@lwn.net>,
"Shuah Khan" <skhan@linuxfoundation.org>,
"Randy Dunlap" <rdunlap@infradead.org>,
"Andrew Morton" <akpm@linux-foundation.org>,
"Boqun Feng" <boqun@kernel.org>,
"Björn Roy Baron" <bjorn3_gh@protonmail.com>,
"Benno Lossin" <lossin@kernel.org>,
"Andreas Hindborg" <a.hindborg@kernel.org>,
"Alice Ryhl" <aliceryhl@google.com>,
"Trevor Gross" <tmgross@umich.edu>,
"Danilo Krummrich" <dakr@kernel.org>,
"Daniel Almeida" <daniel.almeida@collabora.com>,
"Tamir Duberstein" <tamird@kernel.org>,
"Alexandre Courbot" <acourbot@nvidia.com>,
"Onur Özkan" <work@onurozkan.dev>,
"Nathan Chancellor" <nathan@kernel.org>,
"Nicolas Schier" <nsc@kernel.org>,
"Nick Desaulniers" <ndesaulniers@google.com>,
"Bill Wendling" <morbo@google.com>,
"Justin Stitt" <justinstitt@google.com>,
"Asahi Lina" <lina+kernel@asahilina.net>,
"Yury Norov" <ynorov@nvidia.com>,
"Eliot Courtney" <ecourtney@nvidia.com>,
"Joel Fernandes" <joelagnelf@nvidia.com>,
workflows@vger.kernel.org, linux-doc@vger.kernel.org,
rust-for-linux@vger.kernel.org, linux-kbuild@vger.kernel.org,
llvm@lists.linux.dev
Subject: [PATCH 2/2] rust: kcsan: add KUnit test for data race detection
Date: Mon, 12 Oct 2026 13:39:15 +0000 [thread overview]
Message-ID: <20261012134159.912183-2-elver@google.com> (raw)
In-Reply-To: <20261012134159.912183-1-elver@google.com>
Add a minimal KUnit test that checks that KCSAN detects data races in
Rust code: two threads concurrently increment a shared counter without
synchronization, and a probe on the console tracepoint waits for the
KCSAN report (similar to kcsan_test.c):
| BUG: KCSAN: data-race in _RNvNtNtCskctMpSfuwL9_6kernel10kcsan_test5tests8racy_inc / _RNvNtNtCskctMpSfuwL9_6kernel10kcsan_test5tests8racy_inc
|
| read-write to 0xffffffffb751ae28 of 8 bytes by task 116 on cpu 5:
| _RNvNtNtCskctMpSfuwL9_6kernel10kcsan_test5tests8racy_inc+0x1c/0x30
| _RNvNtNtCskctMpSfuwL9_6kernel10kcsan_test5tests28kunit_rust_wrapper_data_race+0x28c/0x850
| kunit_try_run_case+0xdf/0x2a0
| kunit_generic_run_threadfn_adapter+0x48/0x80
| kthread+0x234/0x280
| ret_from_fork+0xf8/0x280
| ret_from_fork_asm+0x1a/0x30
|
| read-write to 0xffffffffb751ae28 of 8 bytes by task 12 on cpu 3:
| _RNvNtNtCskctMpSfuwL9_6kernel10kcsan_test5tests8racy_inc+0x1c/0x30
| _RNvXsb_NtCskctMpSfuwL9_6kernel9workqueueINtNtCs1G77UTwpz6o_4core3pin3PinINtNtNtB7_5alloc4kbox3BoxINtB5_11ClosureWorkNCNvNtNtB7_10kcsan_test5tests9data_races_0ENtNtB1d_9allocator7KmallocEEINtB5_15WorkItemPointerKy0_E3runB7_+0x55/0x120
| process_scheduled_works+0x4cd/0x970
| worker_thread+0x4de/0x6e0
| kthread+0x234/0x280
| ret_from_fork+0xf8/0x280
| ret_from_fork_asm+0x1a/0x30
|
| value changed: 0x000000000000022b -> 0x00000000000005a2
| [...]
The test is intentionally kept minimal; comprehensive tests of KCSAN's
runtime behaviour are in kernel/kcsan/kcsan_test.c.
Assisted-by: Antigravity
Signed-off-by: Marco Elver <elver@google.com>
---
MAINTAINERS | 1 +
rust/bindings/bindings_helper.h | 1 +
rust/kernel/Kconfig.test | 11 +++
rust/kernel/kcsan_test.rs | 120 ++++++++++++++++++++++++++++++++
rust/kernel/lib.rs | 2 +
5 files changed, 135 insertions(+)
create mode 100644 rust/kernel/kcsan_test.rs
diff --git a/MAINTAINERS b/MAINTAINERS
index cc3cae2e378b..3fbbfbb31f5d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14089,6 +14089,7 @@ F: Documentation/dev-tools/kcsan.rst
F: include/linux/kcsan*.h
F: kernel/kcsan/
F: lib/Kconfig.kcsan
+F: rust/kernel/kcsan_test.rs
F: scripts/Makefile.kcsan
KDUMP
diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index 4b31aa7f432f..b06d5142d6e1 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -97,6 +97,7 @@
#include <linux/xarray.h>
#include <net/genetlink.h>
#include <net/netlink.h>
+#include <trace/events/printk.h>
#include <trace/events/rust_sample.h>
/*
diff --git a/rust/kernel/Kconfig.test b/rust/kernel/Kconfig.test
index e6a5c7a795f0..caf98a23de76 100644
--- a/rust/kernel/Kconfig.test
+++ b/rust/kernel/Kconfig.test
@@ -83,4 +83,15 @@ config RUST_BITFIELD_KUNIT_TEST
If unsure, say N.
+config RUST_KCSAN_KUNIT_TEST
+ bool "KUnit test for KCSAN instrumentation of Rust code" if !KUNIT_ALL_TESTS
+ depends on KCSAN && HAVE_KCSAN_RUSTC && TRACEPOINTS
+ default KUNIT_ALL_TESTS
+ help
+ This option enables a KUnit test checking that KCSAN detects data
+ races in Rust code. The test needs at least 2 CPUs to reliably
+ observe a data race.
+
+ If unsure, say N.
+
endif
diff --git a/rust/kernel/kcsan_test.rs b/rust/kernel/kcsan_test.rs
new file mode 100644
index 000000000000..5ddc9090cd6e
--- /dev/null
+++ b/rust/kernel/kcsan_test.rs
@@ -0,0 +1,120 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! KUnit test checking that KCSAN detects data races in Rust code.
+
+#[macros::kunit_tests(rust_kcsan)]
+mod tests {
+ use crate::{
+ bindings,
+ error::to_result,
+ ffi::{c_char, c_void},
+ prelude::*,
+ sync::{
+ atomic::{Atomic, Relaxed},
+ Arc, Completion,
+ },
+ time::{Delta, Instant, Monotonic},
+ types::ScopeGuard,
+ workqueue,
+ };
+ use core::{cell::UnsafeCell, ptr, slice};
+
+ struct Racy(UnsafeCell<u64>);
+
+ // SAFETY: Not actually safe: unsynchronized concurrent access is the point of this test.
+ unsafe impl Sync for Racy {}
+
+ static RACY: Racy = Racy(UnsafeCell::new(0));
+
+ /// Set by [`probe_console`] once KCSAN reported a data race in [`racy_inc`].
+ static OBSERVED: Atomic<bool> = Atomic::new(false);
+
+ #[inline(never)]
+ fn racy_inc() {
+ let p = RACY.0.get();
+ // SAFETY: `p` is valid; the data race is intentional. `wrapping_add()` keeps the load and
+ // store in one basic block, so that the access is instrumented as a compound access (plain
+ // writes may be assumed atomic with `CONFIG_KCSAN_ASSUME_PLAIN_WRITES_ATOMIC=y`).
+ unsafe { *p = (*p).wrapping_add(1); }
+ }
+
+ fn contains(haystack: &[u8], needle: &[u8]) -> bool {
+ haystack.windows(needle.len()).any(|w| w == needle)
+ }
+
+ /// Probe for the `console` tracepoint.
+ ///
+ /// # Safety
+ ///
+ /// `buf` must be non-null and valid for reads of `len` bytes.
+ unsafe extern "C" fn probe_console(_data: *mut c_void, buf: *const c_char, len: usize) {
+ // SAFETY: By the safety requirements of this function.
+ let line = unsafe { slice::from_raw_parts(buf.cast::<u8>(), len) };
+
+ if contains(line, b"BUG: KCSAN: data-race in ") && contains(line, b"racy_inc") {
+ OBSERVED.store(true, Relaxed);
+ }
+ }
+
+ #[pin_data]
+ struct Ctx {
+ stop: Atomic<bool>,
+ #[pin]
+ done: Completion,
+ }
+
+ #[test]
+ fn data_race() -> Result {
+ // Do not pass on a report from a previous run of this suite.
+ OBSERVED.store(false, Relaxed);
+
+ let ctx = Arc::pin_init(
+ pin_init!(Ctx {
+ stop: Atomic::new(false),
+ done <- Completion::new(),
+ }),
+ GFP_KERNEL,
+ )?;
+
+ // SAFETY: `probe_console` has the signature expected by the `console` tracepoint. Being
+ // part of the kernel image, it and `OBSERVED` outlive the registration, so there is no
+ // need to synchronize with in-flight probes after unregistering.
+ to_result(unsafe {
+ bindings::tracepoint_probe_register(
+ &raw mut bindings::__tracepoint_console,
+ probe_console as *mut c_void,
+ ptr::null_mut(),
+ )
+ })?;
+ let _unregister = ScopeGuard::new(|| {
+ // SAFETY: The probe was registered above.
+ unsafe {
+ bindings::tracepoint_probe_unregister(
+ &raw mut bindings::__tracepoint_console,
+ probe_console as *mut c_void,
+ ptr::null_mut(),
+ );
+ }
+ });
+
+ let worker = ctx.clone();
+ workqueue::system_unbound().try_spawn(GFP_KERNEL, move || {
+ while !worker.stop.load(Relaxed) {
+ racy_inc();
+ }
+ worker.done.complete_all();
+ })?;
+
+ let start = Instant::<Monotonic>::now();
+ while !OBSERVED.load(Relaxed) && start.elapsed() < Delta::from_secs(10) {
+ racy_inc();
+ }
+
+ ctx.stop.store(true, Relaxed);
+ ctx.done.wait_for_completion();
+ drop(_unregister);
+
+ assert!(OBSERVED.load(Relaxed));
+ Ok(())
+ }
+}
diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index 4d5c96ddc49c..009efda9e7a4 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -94,6 +94,8 @@
pub mod iov;
pub mod irq;
pub mod jump_label;
+#[cfg(CONFIG_RUST_KCSAN_KUNIT_TEST)]
+mod kcsan_test;
#[cfg(CONFIG_KUNIT)]
pub mod kunit;
pub mod list;
--
2.56.0.385.gd3acb90ef8-goog
next prev parent reply other threads:[~2026-10-12 13:42 UTC|newest]
Thread overview: 15+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-12 13:39 [PATCH 1/2] kbuild: rust: enable KCSAN support Marco Elver
2026-10-12 13:39 ` Marco Elver [this message]
2026-10-12 15:03 ` [PATCH 2/2] rust: kcsan: add KUnit test for data race detection Alexander Potapenko
2026-10-12 15:15 ` Marco Elver
2026-10-12 15:21 ` Miguel Ojeda
2026-10-13 9:38 ` Alexander Potapenko
2026-10-13 10:09 ` Marco Elver
2026-10-13 10:32 ` Miguel Ojeda
2026-10-13 11:16 ` Marco Elver
2026-10-13 12:40 ` Alice Ryhl
2026-10-12 14:17 ` [PATCH 1/2] kbuild: rust: enable KCSAN support Miguel Ojeda
2026-10-12 14:37 ` Marco Elver
2026-10-12 14:52 ` Boqun Feng
2026-10-13 9:36 ` Alexander Potapenko
2026-10-13 9:51 ` Marco Elver
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=20261012134159.912183-2-elver@google.com \
--to=elver@google.com \
--cc=a.hindborg@kernel.org \
--cc=acourbot@nvidia.com \
--cc=akpm@linux-foundation.org \
--cc=aliceryhl@google.com \
--cc=bjorn3_gh@protonmail.com \
--cc=boqun.feng@gmail.com \
--cc=boqun@kernel.org \
--cc=corbet@lwn.net \
--cc=dakr@kernel.org \
--cc=daniel.almeida@collabora.com \
--cc=ecourtney@nvidia.com \
--cc=gary@garyguo.net \
--cc=glider@google.com \
--cc=joelagnelf@nvidia.com \
--cc=justinstitt@google.com \
--cc=kasan-dev@googlegroups.com \
--cc=lina+kernel@asahilina.net \
--cc=linux-doc@vger.kernel.org \
--cc=linux-kbuild@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=llvm@lists.linux.dev \
--cc=lossin@kernel.org \
--cc=morbo@google.com \
--cc=nathan@kernel.org \
--cc=ndesaulniers@google.com \
--cc=nsc@kernel.org \
--cc=ojeda@kernel.org \
--cc=rdunlap@infradead.org \
--cc=rust-for-linux@vger.kernel.org \
--cc=skhan@linuxfoundation.org \
--cc=tamird@kernel.org \
--cc=tmgross@umich.edu \
--cc=work@onurozkan.dev \
--cc=workflows@vger.kernel.org \
--cc=ynorov@nvidia.com \
/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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.