-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Potential UB due to data race between regular thread and interrupt handler #5
Comments
Thank you so much for chiming in and thanks for clarifying! Soundness is definitely a must. I was hoping that I could avoid using atomic memory access, but now I see that it's unavoidable. I'll yank |
That does not have the same effect as using a compiler fence, in my experience (rust-osdev/x86_64#436). Would one need to remove Side note: While this crate does not aim to rely on interrupts being disabled for critical sections, it seems like all of rust-embedded does, while also having |
Note that you only need to keep the calls to the
Yes |
Hmmm. I am a bit confused. It sounds to me like you are suggesting the following: // Disable interrupts, start critical section
asm!("cli", options(nomem, nostack));
// Prevent future writes to be moved before this point.
compiler_fence(Ordering::Acquire);
let borrow = REF_CELL.borrow_mut();
// Prevent earlier writes to be moved beyond this point
compiler_fence(Ordering::Release);
// Enable interrupts, end critical section
asm!("sti", options(nomem, nostack));
// use `borrow` As far as I understand, nothing prevents the compiler to move It would be great to have a critical section that is usable with non-atomics like this. That would make everything much easier. |
Ah yeah we probably need something that actually resemble a lock release -- such as an asm block without But if you remove the |
I have made sure that every interrupts status change is recognized as potentially acquiring/releasing a lock and yanked all previous releases of I have also opened a PR that makes Thank you so much for your insights in this discussion. :) |
I'm glad this has been helpful. :)
That is assuming there is no cross-lang LTO... but I assume this bottoms out in a syscall anyway? |
Yes, there is an underlying system call: sigprocmask(2) - Linux manual page
(Cross-lang) LTO cannot perform this kind of optimization on our assembly blocks for bare metal? |
Assembly blocks are guaranteed to not be inspected by the compiler. |
Sorry to comment on a closed PR, but I had a potential solution.
Would using an atomic fence fix the problem? The acquire load in spin mutexes prevents the compiler from reordering even non-atomic accesses across the load (https://github.com/mvdnes/spin-rs/blob/2cc9ee6ccc45d38b913149465ae99553dce59602/src/mutex/spin.rs#L236). |
Thanks for getting in touch. :) Atomic fences themselves still require atomic operations somewhere that they can synchronize with. In the case of spin mutexes, the compiler may not move the code out of the if statement because taking a reference to inside the mutex depends on the result of the atomic operation. This crate currently manages to avoid reimplementing A solution with atomics is also interesting to explore, though. I've opened mkroening/interrupt-mutex#1 for creating |
See mkroening/interrupt-ref-cell#5 (comment). Signed-off-by: Klimenty Tsoutsman <klim@tsoutsman.com>
See mkroening/interrupt-ref-cell#5 (comment). Also adds the `preserves_flags` option to `x86_64` assembly as the interrupt flag [doesn't need to be preserved][rust-ref]. [rust-ref]: https://doc.rust-lang.org/reference/inline-assembly.html#rules-for-inline-assembly Signed-off-by: Klimenty Tsoutsman <klim@tsoutsman.com>
The docs say
That's not quite true. A compiler fence plus using atomic accesses is sufficient. Non-atomic accesses still cause data races between a thread and its interrupt handler, even if you add all the fences in the world. This is because the interrupt handler could otherwise observe a state that's half-way through executing a Rust operation, which is not a state that can even be described in the Rust Abstract Machine. Also, compiler optimizations assume that non-atomic accesses cannot be observed and hence be arbitrarily reordered when there is no synchronization.
This crate needs to ensure interrupts are disabled on any racy read or write to shared data (in particular, the RefCell borrow tracking flags). In that case disabling the interrupts becomes the critical section (this also requires appropriate acquire/release fences, but I assume interrupt disabling/enabling is anyway done with inline assembly so the fences can be "implicit" in the inline asm block). It is concerning that the docs say interrupt disabling is best-effort, since that seems to imply that soundness is best-effort. If that's truly the case it definitely needs to be stated in stronger terms in the docs.
The text was updated successfully, but these errors were encountered: