Atomicity Violation in dlsym DlSym::get(&self)
https://gitlab.redox-os.org/redox-os/mio/blob/master/src/sys/unix/dlsym.rs#L19-46
pub fn get(&self) -> Option<&F> {
assert_eq!(mem::size_of::<F>(), mem::size_of::<usize>());
unsafe {
if self.addr.load(Ordering::SeqCst) == 0 { // 1
self.addr.store(fetch(self.name), Ordering::SeqCst); // 2, 3
}
if self.addr.load(Ordering::SeqCst) == 1 { // 4
None
} else {
mem::transmute::<&AtomicUsize, Option<&F>>(&self.addr)
}
}
}
This bug is an atomicity violation. addr is an AtomicUsize, which can be converted to a function reference in function get(). In the buggy code,
- function get() first check if addr is 0.
- If not then call fetch()
- and store the returned value (1 or other) to addr.
- Then it checks if addr is 1. If not, convert the addr into a function reference.
Because the struct is Sync (all its fields are Sync) and the function takes immutable self (&self) as parameter, the function can be called by two threads simultaneously and the following execution may occur:
T1 | T2 |
---|---|
check addr.load() == 0 | |
tmp1 = fetch(name) | |
check addr.load() == 0 | |
tmp2 = fetch(name) (suppose tmp2 == 1) | |
addr.store(tmp2) | |
addr.store(tmp1) (suppose tmp1 != 1) | |
check addr.load() != 1 | |
convert addr (currently tmp1) to a function reference and returned |
But originally it should return None because tmp2 == 1