Rust for Linux List
 help / color / mirror / Atom feed
* [PATCH 0/1] rust: revocable: Fix racing access during revocation
@ 2026-06-05  5:02 Yuan Tan
  2026-06-05  5:05 ` [PATCH 1/1] " Yuan Tan
  2026-06-05  6:09 ` [PATCH 0/1] " Boqun Feng
  0 siblings, 2 replies; 4+ messages in thread
From: Yuan Tan @ 2026-06-05  5:02 UTC (permalink / raw)
  To: ojeda, boqun, rust-for-linux; +Cc: zhiyunq, ardalan, pgovind2, dzueck, Yuan Tan

Hi Linux kernel maintainers,

We are developing a tool called FerroLens to detect potential unsound
behavior in Rust code in the Linux kernel. FerroLens reported the following
bug in rust/kernel/revocable.rs.

There is a potential race condition between the call to
revoke_internal/no_sync and try_access/try_access_with_guard on the same
object. This occurs due to the use of Ordering::Relaxed to synchronize on
is_available.

The race occurs in the following scenario:

Thread A (Revoker)
1. is_available.swap(false, Relaxed)
2. synchronize_rcu() begins

Thread B (reader)
3. executes rcu_read_lock().
4. is_available.load(Relaxed) returns true (stale value).

Thread A
5. synchronize_rcu() decides it doesn't need to wait for Thread B because
   B's critical section started after A's grace period began
6. synchronize_rcu() returns
7. drop_in_place(data)

Thread B
8. UB: Thread B accesses data via the guard.

The safety comment for revoke says there are "no more concurrent users",
but that does not prevent a new `try_access` from starting concurrently
with `revoke_internal`. And because both accesses to `is_available` are
`Relaxed`, a racing reader can still observe the old `true` value and
create a reference to `T`.

If `SYNC = true`, `synchronize_rcu()` only waits for RCU read-side critical
sections that were already in flight when the grace period started. A
reader that starts after that point is not waited for, even if it still
reads the stale `true` and obtains `&T`. Then `drop_in_place` can run while
that reader still holds the reference, which is a use-after-free.

Here is a poc kernel module which demonstrates the issue:

---
use core::{
    hint::spin_loop,
    sync::atomic::{AtomicBool, AtomicU32, Ordering},
};
use kernel::{
      bindings,
    c_str,
    error::from_err_ptr,
    prelude::*,
    revocable::{
        self, Revocable, TEST_PHASE_AFTER_DROP, TEST_PHASE_AFTER_SWAP, TEST_PHASE_AFTER_SYNC,
        TEST_PHASE_BEFORE_DROP, TEST_PHASE_BEFORE_SYNC, TEST_PHASE_READER_BEFORE_LOAD,
    },
    sync::{rcu, Arc},
};

  module! {
      type: RevocableExample,
      name: "revoke_poc",
      authors: ["Priya Govindasamy"],
      description: "Poc for potential race condition in Revocable<T>",
      license: "GPL",
  }

  struct DeviceState {
      value: u32,
  }

  struct RevocableExample {
      state: Arc<Revocable<DeviceState>>,
      race: Arc<RaceSync>,
  }

struct RaceSync {
    ready: AtomicU32,
    done: AtomicU32,
    go: AtomicBool,
    abort: AtomicBool,
    reader_may_enter: AtomicBool,
    release_reader: AtomicBool,
    reader_holding_ref: AtomicBool,
}

  struct ThreadContext {
      state: Arc<Revocable<DeviceState>>,
      race: Arc<RaceSync>,
      thread_id: u32,
  }

fn spin_until(flag: &AtomicBool) {
    while !flag.load(Ordering::Acquire) {
        spin_loop();
    }
}

  fn wait_to_start(race: &RaceSync) -> bool {
      loop {
          if race.abort.load(Ordering::Acquire) {
              return false;
          }
          if race.go.load(Ordering::Acquire) {
              return true;
          }
          spin_loop();
      }
  }

fn read_state_value(state: &Revocable<DeviceState>, race: &RaceSync) -> Option<u32> {
    spin_until(&race.reader_may_enter);
    pr_info!("reader: coordinator released reader to enter RCU critical section\n");

    let guard = rcu::read_lock();
    pr_info!("reader: entered RCU critical section after revoke started\n");
    pr_info!("reader: about to call try_access_with_guard after revoke started\n");

    let state = state.try_access_with_guard(&guard)?;
    race.reader_holding_ref.store(true, Ordering::Release);
    pr_info!("reader: holding reference while revoke continues\n");
    spin_until(&race.release_reader);
    let value = state.value;
    race.reader_holding_ref.store(false, Ordering::Release);
    Some(value)
}

  unsafe extern "C" fn read_value_thread(data: *mut kernel::ffi::c_void) -> i32 {
      // SAFETY: `data` comes from `Arc::into_raw` in `spawn_read_thread`, and each thread takes
      // ownership of exactly one reference.
      let ctx = unsafe { Arc::from_raw(data.cast::<ThreadContext>()) };

      ctx.race.ready.fetch_add(1, Ordering::AcqRel);
      if !wait_to_start(&ctx.race) {
          ctx.race.done.fetch_add(1, Ordering::AcqRel);
          return 0;
      }

      match read_state_value(&ctx.state, &ctx.race) {
          Some(v) => pr_info!("REVOKE_BUG: thread {} read value = {}\n", ctx.thread_id, v),
          None => pr_info!("thread {} saw revoked state\n", ctx.thread_id),
      }

      ctx.race.done.fetch_add(1, Ordering::AcqRel);
      0
  }

  unsafe extern "C" fn revoke_state_thread(data: *mut kernel::ffi::c_void) -> i32 {
      // SAFETY: `data` comes from `Arc::into_raw` in `spawn_revoke_thread`, and each thread takes
      // ownership of exactly one reference.
      let ctx = unsafe { Arc::from_raw(data.cast::<ThreadContext>()) };

      ctx.race.ready.fetch_add(1, Ordering::AcqRel);
      if !wait_to_start(&ctx.race) {
          ctx.race.done.fetch_add(1, Ordering::AcqRel);
          return 0;
      }

    pr_info!("revoker: about to call revoke\n");
    ctx.state.revoke();
    pr_info!("thread {} revoked state\n", ctx.thread_id);

      ctx.race.done.fetch_add(1, Ordering::AcqRel);
      0
  }

  fn spawn_read_thread(
      state: Arc<Revocable<DeviceState>>,
      race: Arc<RaceSync>,
      thread_id: u32,
  ) -> Result {
      let ctx = Arc::new(ThreadContext { state, race, thread_id }, GFP_KERNEL)?;
      let data = Arc::into_raw(ctx) as *mut kernel::ffi::c_void;

      let task = match from_err_ptr(unsafe {
          bindings::kthread_create_on_node(
              Some(read_value_thread),
              data,
              bindings::NUMA_NO_NODE,
              c_str!("revocable_reader/%u").as_char_ptr(),
              thread_id,
          )
      }) {
          Ok(task) => task,
          Err(err) => {
              // SAFETY: Thread creation failed, so the raw reference was not handed off.
              unsafe { drop(Arc::from_raw(data.cast::<ThreadContext>())) };
              return Err(err);
          }
      };

      // SAFETY: `task` is a valid sleeping kthread returned by `kthread_create_on_node`.
      unsafe { bindings::wake_up_process(task) };
      Ok(())
  }

  fn spawn_revoke_thread(
      state: Arc<Revocable<DeviceState>>,
      race: Arc<RaceSync>,
      thread_id: u32,
  ) -> Result {
      let ctx = Arc::new(ThreadContext { state, race, thread_id }, GFP_KERNEL)?;
      let data = Arc::into_raw(ctx) as *mut kernel::ffi::c_void;

      let task = match from_err_ptr(unsafe {
          bindings::kthread_create_on_node(
              Some(revoke_state_thread),
              data,
              bindings::NUMA_NO_NODE,
              c_str!("revocable_revoke/%u").as_char_ptr(),
              thread_id,
          )
      }) {
          Ok(task) => task,
          Err(err) => {
              // SAFETY: Thread creation failed, so the raw reference was not handed off.
              unsafe { drop(Arc::from_raw(data.cast::<ThreadContext>())) };
              return Err(err);
          }
      };

      // SAFETY: `task` is a valid sleeping kthread returned by `kthread_create_on_node`.
      unsafe { bindings::wake_up_process(task) };
      Ok(())
  }

 impl kernel::Module for RevocableExample {
      fn init(_module: &'static ThisModule) -> Result<Self> {
          let state = Arc::pin_init(Revocable::new(DeviceState { value: 123 }), GFP_KERNEL)?;
          let race = Arc::new(
              RaceSync {
                  ready: AtomicU32::new(0),
                  done: AtomicU32::new(0),
                  go: AtomicBool::new(false),
                  abort: AtomicBool::new(false),
                  reader_may_enter: AtomicBool::new(false),
                  release_reader: AtomicBool::new(false),
                  reader_holding_ref: AtomicBool::new(false),
              },
              GFP_KERNEL,
          )?;

          let m = Self { state, race };
          revocable::test_hooks_reset();

          spawn_revoke_thread(m.state.clone(), m.race.clone(), 1)?;
          if let Err(err) = spawn_read_thread(m.state.clone(), m.race.clone(), 2) {
              m.race.abort.store(true, Ordering::Release);
              revocable::test_hooks_disable();
              while m.race.done.load(Ordering::Acquire) != m.race.ready.load(Ordering::Acquire) {
                  spin_loop();
              }
              return Err(err);
          }

          while m.race.ready.load(Ordering::Acquire) != 2 {
              spin_loop();
          }
          m.race.go.store(true, Ordering::Release);
          pr_info!("main: released reader and revoker together\n");

          revocable::test_hooks_wait_for(TEST_PHASE_AFTER_SWAP);
          pr_info!("main: observed revoke_internal after swap(false)\n");
          revocable::test_hooks_allow_through(TEST_PHASE_AFTER_SWAP);

          revocable::test_hooks_wait_for(TEST_PHASE_BEFORE_SYNC);
          pr_info!("main: releasing revoker into synchronize_rcu\n");
          revocable::test_hooks_allow_through(TEST_PHASE_BEFORE_SYNC);
          m.race.reader_may_enter.store(true, Ordering::Release);

          revocable::test_hooks_wait_for(TEST_PHASE_READER_BEFORE_LOAD);
          pr_info!("main: reader reached try_access_with_guard before load\n");
          revocable::test_hooks_allow_through(TEST_PHASE_READER_BEFORE_LOAD);

          revocable::test_hooks_wait_for(TEST_PHASE_AFTER_SYNC);
          pr_info!("main: revoker completed synchronize_rcu\n");
          if m.race.reader_holding_ref.load(Ordering::Acquire) {
              pr_info!(
                  "BUG_WINDOW: reader still holds reference after synchronize_rcu completed\n"
              );
          }
          revocable::test_hooks_allow_through(TEST_PHASE_AFTER_SYNC);

          revocable::test_hooks_wait_for(TEST_PHASE_BEFORE_DROP);
          pr_info!("main: revoker reached the drop_in_place boundary\n");
          if m.race.reader_holding_ref.load(Ordering::Acquire) {
              pr_info!(
                  "BUG_WINDOW: reader still holds reference immediately before drop_in_place\n"
              );
          }
          m.race.release_reader.store(true, Ordering::Release);
          revocable::test_hooks_allow_through(TEST_PHASE_BEFORE_DROP);
          revocable::test_hooks_allow_through(TEST_PHASE_AFTER_DROP);

          while m.race.done.load(Ordering::Acquire) != 2 {
              spin_loop();
          }
          revocable::test_hooks_disable();

          Ok(m)
      }
  }

  impl Drop for RevocableExample {
      fn drop(&mut self) {
          // Safe to call more than once; only the first revoke does the work.
          self.state.revoke();
      }
  }


Makefile:

obj-m += revoke_poc.o

KDIR := /lib/modules/$(shell uname -r)/build

PWD := $(shell pwd)

all:
	$(MAKE) -C $(KDIR) M=$(PWD) LLVM=1 modules

clean:
	$(MAKE) -C $(KDIR) M=$(PWD) clean


Note that this bug could not be triggered on x86 architecture because it uses a Total Store Order (TSO) model.

Yuan Tan (1):
  rust: revocable: Fix racing access during revocation

 rust/kernel/revocable.rs | 56 ++++++++++++++++++++++++++--------------
 1 file changed, 37 insertions(+), 19 deletions(-)

-- 
2.43.2


^ permalink raw reply	[flat|nested] 4+ messages in thread

* [PATCH 1/1] rust: revocable: Fix racing access during revocation
  2026-06-05  5:02 [PATCH 0/1] rust: revocable: Fix racing access during revocation Yuan Tan
@ 2026-06-05  5:05 ` Yuan Tan
  2026-06-05  6:09 ` [PATCH 0/1] " Boqun Feng
  1 sibling, 0 replies; 4+ messages in thread
From: Yuan Tan @ 2026-06-05  5:05 UTC (permalink / raw)
  To: ojeda, boqun, rust-for-linux; +Cc: zhiyunq, ardalan, pgovind2, dzueck

Revocable readers currently check availability with a relaxed load after
entering an RCU read-side critical section, while revocation clears the
flag with a relaxed swap before waiting for a grace period and dropping
the wrapped value.

A reader racing with revocation can therefore observe the old available
value and create a reference after the revoker's grace period has
started. Such a reader is not necessarily waited for by synchronize_rcu(),
so the wrapped value can be dropped while the reference is still live.

This also means the Sync implementation is not justified by the stated
bounds alone: sharing Revocable<T> across threads permits safe readers to
race with safe revocation and hit the same use-after-free.

Use the kernel's LKMM atomic flag for the availability state and make
readers claim access with cmpxchg() instead of a load. This orders a
successful access against the revoker's xchg(): either the reader claims
access before revocation and is covered by the RCU grace period, or
revocation wins and the reader fails.

revoke_nosync() intentionally skips the RCU grace period, so it still
requires external exclusion. Tighten its safety requirements to state
that no new try_access() or try_access_with_guard() calls may race with
revocation.

Fixes: 0494d9c82b0c ("rust: add `Revocable` type")
Cc: stable@vger.kernel.org
Reported-by: Priya Bala Govindasamy <pgovind2@uci.edu>
Reported-by: Dylan Zueck <dzueck@uci.edu>
Signed-off-by: Yuan Tan <ytan089@ucr.edu>
---
 rust/kernel/revocable.rs | 56 ++++++++++++++++++++++++++--------------
 1 file changed, 37 insertions(+), 19 deletions(-)

diff --git a/rust/kernel/revocable.rs b/rust/kernel/revocable.rs
index 0f4ae673256d..0b56c1259404 100644
--- a/rust/kernel/revocable.rs
+++ b/rust/kernel/revocable.rs
@@ -7,13 +7,19 @@
 
 use pin_init::Wrapper;
 
-use crate::{bindings, prelude::*, sync::rcu, types::Opaque};
-use core::{
-    marker::PhantomData,
-    ops::Deref,
-    ptr::drop_in_place,
-    sync::atomic::{AtomicBool, Ordering},
+use crate::{
+    bindings,
+    prelude::*,
+    sync::{
+        atomic::{
+            ordering,
+            AtomicFlag, //
+        },
+        rcu,
+    },
+    types::Opaque,
 };
+use core::{marker::PhantomData, ops::Deref, ptr::drop_in_place};
 
 /// An object that can become inaccessible at runtime.
 ///
@@ -65,7 +71,7 @@
 /// ```
 #[pin_data(PinnedDrop)]
 pub struct Revocable<T> {
-    is_available: AtomicBool,
+    is_available: AtomicFlag,
     #[pin]
     data: Opaque<T>,
 }
@@ -84,7 +90,7 @@ impl<T> Revocable<T> {
     /// Creates a new revocable instance of the given data.
     pub fn new<E>(data: impl PinInit<T, E>) -> impl PinInit<Self, E> {
         try_pin_init!(Self {
-            is_available: AtomicBool::new(true),
+            is_available: AtomicFlag::new(true),
             data <- Opaque::pin_init(data),
         }? E)
     }
@@ -98,9 +104,9 @@ pub fn new<E>(data: impl PinInit<T, E>) -> impl PinInit<Self, E> {
     /// because another CPU may be waiting to complete the revocation of this object.
     pub fn try_access(&self) -> Option<RevocableGuard<'_, T>> {
         let guard = rcu::read_lock();
-        if self.is_available.load(Ordering::Relaxed) {
-            // Since `self.is_available` is true, data is initialised and has to remain valid
-            // because the RCU read side lock prevents it from being dropped.
+        if self.try_claim() {
+            // Since `try_claim` succeeded while the RCU read side lock is held, data is
+            // initialised and synchronized revocation cannot drop it before the guard is dropped.
             Some(RevocableGuard::new(self.data.get(), guard))
         } else {
             None
@@ -116,9 +122,9 @@ pub fn try_access(&self) -> Option<RevocableGuard<'_, T>> {
     /// allowed to sleep because another CPU may be waiting to complete the revocation of this
     /// object.
     pub fn try_access_with_guard<'a>(&'a self, _guard: &'a rcu::Guard) -> Option<&'a T> {
-        if self.is_available.load(Ordering::Relaxed) {
-            // SAFETY: Since `self.is_available` is true, data is initialised and has to remain
-            // valid because the RCU read side lock prevents it from being dropped.
+        if self.try_claim() {
+            // SAFETY: Since `try_claim` succeeded while the RCU read side lock is held, data is
+            // initialised and synchronized revocation cannot drop it before `_guard` is dropped.
             Some(unsafe { &*self.data.get() })
         } else {
             None
@@ -153,11 +159,21 @@ pub unsafe fn access(&self) -> &T {
         unsafe { &*self.data.get() }
     }
 
+    fn try_claim(&self) -> bool {
+        // Use cmpxchg rather than load so a racing access cannot succeed with the old value after
+        // revocation's xchg has changed the flag to false.
+        self.is_available
+            .cmpxchg(true, true, ordering::Acquire)
+            .is_ok()
+    }
+
     /// # Safety
     ///
-    /// Callers must ensure that there are no more concurrent users of the revocable object.
+    /// If `SYNC` is `false`, callers must ensure that there are no more concurrent users of the
+    /// revocable object, and that no new calls to [`Self::try_access`] or
+    /// [`Self::try_access_with_guard`] can race with revocation.
     unsafe fn revoke_internal<const SYNC: bool>(&self) -> bool {
-        let revoke = self.is_available.swap(false, Ordering::Relaxed);
+        let revoke = self.is_available.xchg(false, ordering::Full);
 
         if revoke {
             if SYNC {
@@ -165,8 +181,8 @@ unsafe fn revoke_internal<const SYNC: bool>(&self) -> bool {
                 unsafe { bindings::synchronize_rcu() };
             }
 
-            // SAFETY: We know `self.data` is valid because only one CPU can succeed the
-            // `compare_exchange` above that takes `is_available` from `true` to `false`.
+            // SAFETY: We know `self.data` is valid because only one CPU can observe
+            // `is_available` as `true` in the `xchg` above.
             unsafe { drop_in_place(self.data.get()) };
         }
 
@@ -183,7 +199,9 @@ unsafe fn revoke_internal<const SYNC: bool>(&self) -> bool {
     ///
     /// # Safety
     ///
-    /// Callers must ensure that there are no more concurrent users of the revocable object.
+    /// Callers must ensure that there are no more concurrent users of the revocable object, and
+    /// that no new calls to [`Self::try_access`] or [`Self::try_access_with_guard`] can race with
+    /// revocation.
     pub unsafe fn revoke_nosync(&self) -> bool {
         // SAFETY: By the safety requirement of this function, the caller ensures that nobody is
         // accessing the data anymore and hence we don't have to wait for the grace period to
-- 
2.43.2


^ permalink raw reply related	[flat|nested] 4+ messages in thread

* Re: [PATCH 0/1] rust: revocable: Fix racing access during revocation
  2026-06-05  5:02 [PATCH 0/1] rust: revocable: Fix racing access during revocation Yuan Tan
  2026-06-05  5:05 ` [PATCH 1/1] " Yuan Tan
@ 2026-06-05  6:09 ` Boqun Feng
  2026-06-05  6:34   ` Gary Guo
  1 sibling, 1 reply; 4+ messages in thread
From: Boqun Feng @ 2026-06-05  6:09 UTC (permalink / raw)
  To: Yuan Tan; +Cc: ojeda, rust-for-linux, zhiyunq, ardalan, pgovind2, dzueck

On Thu, Jun 04, 2026 at 10:02:46PM -0700, Yuan Tan wrote:
> Hi Linux kernel maintainers,
> 
> We are developing a tool called FerroLens to detect potential unsound
> behavior in Rust code in the Linux kernel. FerroLens reported the following
> bug in rust/kernel/revocable.rs.
> 
> There is a potential race condition between the call to
> revoke_internal/no_sync and try_access/try_access_with_guard on the same
> object. This occurs due to the use of Ordering::Relaxed to synchronize on
> is_available.
> 
> The race occurs in the following scenario:
> 
> Thread A (Revoker)
> 1. is_available.swap(false, Relaxed)
> 2. synchronize_rcu() begins
> 
> Thread B (reader)
> 3. executes rcu_read_lock().
> 4. is_available.load(Relaxed) returns true (stale value).
> 
> Thread A
> 5. synchronize_rcu() decides it doesn't need to wait for Thread B because
>    B's critical section started after A's grace period began

This part is impossible because:

	https://docs.kernel.org/RCU/Design/Requirements/Requirements.html#grace-period-guarantee

Does your tool understand Linux kernel memory model?

Regards,
Boqun

> 6. synchronize_rcu() returns
> 7. drop_in_place(data)
> 
> Thread B
> 8. UB: Thread B accesses data via the guard.
> 
> The safety comment for revoke says there are "no more concurrent users",
> but that does not prevent a new `try_access` from starting concurrently
> with `revoke_internal`. And because both accesses to `is_available` are
> `Relaxed`, a racing reader can still observe the old `true` value and
> create a reference to `T`.
> 
> If `SYNC = true`, `synchronize_rcu()` only waits for RCU read-side critical
> sections that were already in flight when the grace period started. A
> reader that starts after that point is not waited for, even if it still
> reads the stale `true` and obtains `&T`. Then `drop_in_place` can run while
> that reader still holds the reference, which is a use-after-free.
> 
[...]

^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [PATCH 0/1] rust: revocable: Fix racing access during revocation
  2026-06-05  6:09 ` [PATCH 0/1] " Boqun Feng
@ 2026-06-05  6:34   ` Gary Guo
  0 siblings, 0 replies; 4+ messages in thread
From: Gary Guo @ 2026-06-05  6:34 UTC (permalink / raw)
  To: Boqun Feng, Yuan Tan
  Cc: ojeda, rust-for-linux, zhiyunq, ardalan, pgovind2, dzueck

On Fri Jun 5, 2026 at 7:09 AM BST, Boqun Feng wrote:
> On Thu, Jun 04, 2026 at 10:02:46PM -0700, Yuan Tan wrote:
>> Hi Linux kernel maintainers,
>> 
>> We are developing a tool called FerroLens to detect potential unsound
>> behavior in Rust code in the Linux kernel. FerroLens reported the following
>> bug in rust/kernel/revocable.rs.
>> 
>> There is a potential race condition between the call to
>> revoke_internal/no_sync and try_access/try_access_with_guard on the same
>> object. This occurs due to the use of Ordering::Relaxed to synchronize on
>> is_available.
>> 
>> The race occurs in the following scenario:
>> 
>> Thread A (Revoker)
>> 1. is_available.swap(false, Relaxed)
>> 2. synchronize_rcu() begins
>> 
>> Thread B (reader)
>> 3. executes rcu_read_lock().
>> 4. is_available.load(Relaxed) returns true (stale value).
>> 
>> Thread A
>> 5. synchronize_rcu() decides it doesn't need to wait for Thread B because
>>    B's critical section started after A's grace period began
>
> This part is impossible because:
>
> 	https://docs.kernel.org/RCU/Design/Requirements/Requirements.html#grace-period-guarantee
>
> Does your tool understand Linux kernel memory model?

Here's a correctness proof with LKMM litmus test (it's basically just that
guarantee, which I am somewhat surprised that doesn't have an in-tree litmus
test for).

$ cat litmus-tests/revocable.litmus
C revocable

(*
 * Result: Never
 *)

{
is_available=1;
data_valid=1;
}

P0(int *is_available, int *data_valid) // Revoker
{
        WRITE_ONCE(*is_available, 0);
        synchronize_rcu();
        WRITE_ONCE(*data_valid, 0);
}

P1(int *is_available, int *data_valid) // Reader
{
        int r0;
        int r1;

        rcu_read_lock();
        r0 = READ_ONCE(*is_available);
        r1 = READ_ONCE(*data_valid);
        rcu_read_unlock();
}

exists (1:r0=1 /\ 1:r1=0) (* Bad outcome. *)

$ herd7 -conf linux-kernel.cfg litmus-tests/revocable.litmus
Test revocable Allowed
States 3
1:r0=0; 1:r1=0;
1:r0=0; 1:r1=1;
1:r0=1; 1:r1=1;
No
Witnesses
Positive: 0 Negative: 3
Condition exists (1:r0=1 /\ 1:r1=0)
Observation revocable Never 0 3
Time revocable 0.01
Hash=645c0c4bc27ad56fdeb295e68326f479

Best,
Gary

^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2026-06-05  6:35 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-05  5:02 [PATCH 0/1] rust: revocable: Fix racing access during revocation Yuan Tan
2026-06-05  5:05 ` [PATCH 1/1] " Yuan Tan
2026-06-05  6:09 ` [PATCH 0/1] " Boqun Feng
2026-06-05  6:34   ` Gary Guo

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox