Please provide a short (approximately 100 word) summary of the following web Content, written in the voice of the original author. If there is anything controversial please highlight the controversy. If there is something surprising, unique, or clever, please highlight that as well. Content: Title: Klint: Compile-Time Detection of Atomic Context Violations for Kernel Rust Code Site: www.memorysafety.org Gary Guo is helping our efforts to bring Rust into the Linux kernel by building a tool called klint . We asked him to provide his perspective on the work in this blog post. Thank you for your partnership and contributions, Gary! For the last couple of months, I have been working on a static analysis tool called klint that is able to detect, in compile time, coding errors related to atomic contexts in Rust kernel code. In this blog post, I'll talk about what is atomic context, what can happen if they are misused, how it is related to Rust and why it is important to detect these errors. Atomic Contexts Generally, a piece of Linux kernel code runs in one of two contexts, atomic context or task context (we are not going to discuss the raw atomic context in this blog post). Code running in task contexts is allowed to sleep, e.g. rescheduling or acquiring a mutex. Code running in atomic contexts, on the other hand, is not allowed to sleep. One obvious example of atomic context is interrupt handlers. Apart from interrupts, the kernel also moves from task context into atomic context if a spinlock is acquired, or when the code is inside an RCU critical section. Sleeping inside an atomic context is bad -- if you acquire a spinlock and then go to sleep, and another piece of code tries to acquire the same spinlock, it is very likely that the system will be locked up. spin_lock ( & lock ); ... mutex_lock ( & mutex ); // BAD ... spin_unlock ( & lock ); These kinds of mistakes are easy to make and hard to debug, especially when the sleepable call is deeply nested. To debug this, kernel C code has might_sleep() annotations all around the place (e.g. inside the mutex_lock function). If you have DEBUG_ATOMIC_SLEEP config enabled, then the kernel will track the preemption count. This counter is incremented whenever you enter an atomic section (e.g. by acquiring a spinlock) and decremented on exit. If the counter is non-zero, then it means that the kernel is inside an atomic context -- calling might_sleep() in this case will produce a warning to aid debugging. Memory Safety Aspects of Atomic Context The Rust for Linux project tries hard to ensure that it can provide safe abstractions of the kernel C API and empower drivers to be written in safe Rust code. We already have a list of synchronisation primitives implemented, and this includes spinlocks and mutexes. Therefore, the concept of atomic context is as relevant in Rust code as in C code. You might ask, how is memory safety related here? If you are familiar with Rust, there's a chance that you are aware of what "memory safety" in Rust means. Safe code in Rust should not be able to cause use-after-free or data races, but causing a deadlock is memory safe. If a Rust kernel driver sleeps while inside an atomic context, it might cause a deadlock, which is bad and should be avoided, but it should be memory safe regardless, right? This would be true if spinlocks were the only source of atomic contexts. However, the kernel very widely employs RCU (read-copy-update). Details of RCU can be found in the kernel documentation , but in a nutshell, RCU is a synchronisation mechanism to provide efficient read access to shared data structures. It allows multiple readers to access shared data structures without locking. A data structure accessible from an RCU read-side critical section will stay alive and will not be deallocated until all read-side critical sections that may access it have been completed. In the kernel, an RCU read-side critical section starts with rcu_read_lock() and ends with rcu_read_unlock() . To drop a data structure unpublished from RCU, one would do synchronize_rcu() before dropping it: /* CPU 0 */ /* CPU 1 */ rcu_read_lock (); ptr = rcu_dereference ( v ); old_ptr = rcu_dereference ( v ); /* use ptr */ rcu_assign_pointer ( v , new_ptr ); synchronize_rcu (); /* waiting for RCU read to finish */ rcu_read_unlock (); /* synchronize_rcu() returns */ /* destruct and free old_ptr */ If you look at the implementation detail of rcu_read_lock() , however, you will see that it compiles down to a single compiler barrier — asm volatile("":::"memory") , if all the debugging facilities are off. Yes, there are absolutely no instructions generated for rcu_read_lock() and rcu_read_unlock() ! Linux kernel plays a trick here -- it implements synchronize_rcu() in a way such that it returns after all CPU cores experience context switches at least once. The kernel considers an RCU read-side critical section to be an atomic context, so no code inside it may sleep and thus cause a context switch. With this reasoning, if all CPU cores have gone through context switches, then all live read critical sections must have been completed. The soundness of synchronize_rcu() relies on the fact that code cannot sleep inside RCU read-side critical sections! If such sleep indeed happens, it can cause synchronize_rcu() to return early and thus cause memory to be freed before rcu_read_unlock() , leading to use-after-free. TL;DR: How RCU is implemented in the Linux kernel lifts sleep in atomic context from "it's bad because it might cause deadlock" to "it's bad because it can cause use-after-free". RCU Abstractions in Rust Rust code, unlike C, usually does not use separate lock and unlock calls for synchronisation primitives -- instead, RAII is used, and lock primitives are typically implemented by having a lock function that returns a Guard , and unlocking happens when the Guard is dropped. For example, RCU read-side critical section could be implemented like this: struct RcuReadGuard { _not_send : PhantomData <* mut () > , } pub fn rcu_read_lock () -> RcuReadGuard { rcu_read_lock (); RcuReadGuard { _not_send : PhantomData } } impl Drop for RcuReadGuard { fn drop ( & mut self ) { rcu_read_unlock (); } } // Usage { let guard = rcu_read_lock (); /* Code inside RCU read-side critical section here */ // `guard` is dropped automatically when it goes out of scope, // or can be dropped manually by `drop(guard)`. } If we disregard the memory safety issues discussed above just for a second, Rust lifetimes can model RCU fairly well: struct RcuProtectedBox < T > { write_mutex : Mutex < () > , ptr : UnsafeCell <* const T > , } impl < T > RcuProtectedBox < T > { fn read < 'a > ( & 'a self , guard : & 'a RcuReadGuard ) -> & 'a T { // SAFETY: We can deref because `guard` ensures we are protected by RCU read lock let ptr = unsafe { rcu_dereference ! ( * self . ptr . get ()) }; // SAFETY: The lifetime is the shorter of `self` and `guard`, so it can only be used until RCU read unlock. unsafe { &* ptr } } fn write ( & self , p : Box < T > ) -> Box < T > { let g = self . write_mutex . lock (); let old_ptr ; // SAFETY: We can deref and assign because we are the only writer. unsafe { old_ptr = rcu_dereference ! ( * self . ptr . get ()); rcu_assign_pointer ! ( * self . ptr . get (), Box :: into_raw ( p )); } drop ( g ); synchronize_rcu (); // SAFETY: We now have exclusive ownership of this pointer as `synchronize_rcu` ensures that all reader that can read this pointer has ended. unsafe { Box :: from_raw ( old_ptr ) } } } Note that in read , the returned lifetime 'a is tied to both self and RcuReadGuard . That is, the RcuReadGuard must outlive the returned reference -- leaving RCU read-side critical section by dropping RcuReadGuard will ensure that references obtained through the read method will no longer be readable. However, such an abstraction is not sound, due to the sleep-in-atomic-context issue that we have described above. fn foo ( b : & RcuProtectedBox < Foo > ) { fn bar ( b : & RcuProtectedBox < Foo > ) { let guard = rcu_read_lock (); let p = b . read ( & guard ); let old = b . write ( Box :: new ( Foo { ... })); sleep (); // `synchronize_rcu()` returns drop ( old ); // Rust allows us to use `p` here // but it is already freed! } There were discussions about how we can provide abstractions of RCU in a sound way in the past two years. One approach is to make all RCU abstractions unsafe -- this is bad from a usability point of view, and