Rust and UB
Rust and UB
Posted Aug 14, 2024 22:34 UTC (Wed) by pbonzini (subscriber, #60935)In reply to: Rust and UB by josh
Parent article: Standards for use of unsafe Rust in the kernel
Consider any kind of synchronization primitive (such as futexes, or a ring buffer) that span kernel and userspace, you cannot for example ensure that the other side accesses data with something that resembles an atomic read/write. In theory that would be a data race and undefined behavior in the Rust code. In practice you make some, more or less reasonable, assumptions on what the compiler and processor do, and assume that this bounds the kind of cross-process-induced undefined behavior that can actually happen.
For example, you may assume that the compiler won't perform optimizations that assume that it can see all possible accesses to AtomicXYZ (it clearly does not, since some accesses happen outside Linux). So if you write code that validates indices read from atomic references, the compiler won't try to infer that these bounds checks are dead.
You may also have to assume that in the case of data races involving integer atomics (as opposed to pointers), the undefined behavior is limited to seeing data that neither side has ever written, for example leaving a mix of the old and the new value in memory. This is beyond what Rust guarantees, but you can make more or less handwavy arguments that this is the same as if a malicious userspace wrote random data without causing data races. The latter case is not UB and constrains the kind of optimization that the compiler can perform, so that in the end input validation (see previous point) will catch the invalid data before reaching the unsafe-safe boundary.
You may also have to make assumptions on what volatile reads and writes really are (possibly including reads and writes from asm! blocks), and the behavior you get when you access volatile memory in ways that would technically be data races according to the Rust memory model.
The above is true of two processes, or of a VMM and a virtual machine guest, or of a device on the same memory bus as Linux. But it's even true of Rust and C code within Linux, because the C code isn't using atomic load and store primitives, and therefore you cannot really escape thinking through this. (In practice it's not going to be a problem, but it shows that the "clean slate" approach doesn't work 100%).
But at least, this kind of reasoning only needs to be applied to unsafe code that uses *mut pointers or UnsafeCell.
