miri/shims/unix/linux_like/sync.rs
1use crate::concurrency::sync::FutexRef;
2use crate::meta_obj::GetMetaError;
3use crate::shims::sig::Varargs;
4use crate::*;
5
6struct LinuxFutex {
7 futex: FutexRef,
8}
9
10impl MetaObj for LinuxFutex {
11 fn opaque(&self) -> bool {
12 false
13 }
14}
15
16/// Implementation of the SYS_futex syscall.
17/// `args` is the arguments *including* the syscall number.
18pub fn futex<'tcx>(
19 ecx: &mut MiriInterpCx<'tcx>,
20 varargs: Varargs<'tcx, '_>,
21 dest: &MPlaceTy<'tcx>,
22) -> InterpResult<'tcx> {
23 let ([addr, op, val], varargs) =
24 ecx.check_varargs(shim_varargs![*u32, i32, u32], varargs, "syscall(SYS_futex, ...)")?;
25
26 // See <https://man7.org/linux/man-pages/man2/futex.2.html> for docs.
27 // The first three arguments (after the syscall number itself) are the same to all futex operations:
28 // (uint32_t *addr, int op, uint32_t val).
29 // We checked above that these definitely exist.
30 let obj = ecx.deref_pointer(&addr)?;
31 let op = ecx.read_scalar(&op)?.to_i32()?;
32 let val = ecx.read_scalar(&val)?.to_u32()?;
33
34 let futex_private = ecx.eval_libc_i32("FUTEX_PRIVATE_FLAG");
35 let futex_wait = ecx.eval_libc_i32("FUTEX_WAIT");
36 let futex_wait_bitset = ecx.eval_libc_i32("FUTEX_WAIT_BITSET");
37 let futex_wake = ecx.eval_libc_i32("FUTEX_WAKE");
38 let futex_wake_bitset = ecx.eval_libc_i32("FUTEX_WAKE_BITSET");
39 let futex_realtime = ecx.eval_libc_i32("FUTEX_CLOCK_REALTIME");
40
41 // FUTEX_PRIVATE enables an optimization that stops it from working across processes.
42 // Miri doesn't support that anyway, so we ignore that flag.
43 match op & !futex_private {
44 // FUTEX_WAIT: (int *addr, int op = FUTEX_WAIT, int val, const timespec *timeout)
45 // Blocks the thread if *addr still equals val. Wakes up when FUTEX_WAKE is called on the same address,
46 // or *timeout expires. `timeout == null` for an infinite timeout.
47 //
48 // FUTEX_WAIT_BITSET: (int *addr, int op = FUTEX_WAIT_BITSET, int val, const timespec *timeout, int *_ignored, unsigned int bitset)
49 // This is identical to FUTEX_WAIT, except:
50 // - The timeout is absolute rather than relative.
51 // - You can specify the bitset to selecting what WAKE operations to respond to.
52 op if op & !futex_realtime == futex_wait || op & !futex_realtime == futex_wait_bitset => {
53 let wait_bitset = op & !futex_realtime == futex_wait_bitset;
54
55 let (timeout, bitset) = if wait_bitset {
56 let ([timeout, uaddr2, bitset], _) = ecx.check_varargs(
57 shim_varargs![*libc::timespec, *u32, u32],
58 varargs,
59 "syscall(SYS_futex, ...)",
60 )?;
61 let uaddr2 = ecx.read_pointer(&uaddr2)?;
62 if !ecx.ptr_is_null(uaddr2)? {
63 throw_ub_format!("`uaddr2` pointer must be null for `FUTEX_WAIT_BITSET`");
64 }
65 (timeout, ecx.read_scalar(&bitset)?.to_u32()?)
66 } else {
67 let ([timeout], _) = ecx.check_varargs(
68 shim_varargs![*libc::timespec],
69 varargs,
70 "syscall(SYS_futex, ...)",
71 )?;
72 (timeout, u32::MAX)
73 };
74
75 if bitset == 0 {
76 return ecx.set_errno_and_return_neg1(LibcError("EINVAL"), dest);
77 }
78
79 let timeout = ecx.deref_pointer(&timeout)?;
80 let deadline = if ecx.ptr_is_null(timeout.ptr())? {
81 None
82 } else {
83 let Some(duration) = ecx.read_timespec(&timeout)? else {
84 return ecx.set_errno_and_return_neg1(LibcError("EINVAL"), dest);
85 };
86 let timeout_clock = if op & futex_realtime == futex_realtime {
87 ecx.check_no_isolation(
88 "`futex` syscall with `op=FUTEX_WAIT` and non-null timeout with `FUTEX_CLOCK_REALTIME`",
89 )?;
90 TimeoutClock::RealTime
91 } else {
92 TimeoutClock::Monotonic
93 };
94 let timeout_style = if wait_bitset {
95 // FUTEX_WAIT_BITSET uses an absolute timestamp.
96 TimeoutStyle::Absolute
97 } else {
98 // FUTEX_WAIT uses a relative timestamp.
99 TimeoutStyle::Relative
100 };
101 Some(ecx.machine.timeout(timeout_clock, timeout_style, duration))
102 };
103 // There may be a concurrent thread changing the value of addr
104 // and then invoking the FUTEX_WAKE syscall. It is critical that the
105 // effects of this and the other thread are correctly observed,
106 // otherwise we will deadlock.
107 //
108 // There are two scenarios to consider, depending on whether WAIT or WAKE goes first:
109 // 1. If we (FUTEX_WAIT) execute first, we'll push ourselves into the waiters queue and
110 // go to sleep. They (FUTEX_WAKE) will see us in the queue and wake us up. It doesn't
111 // matter how the addr write is ordered.
112 // 2. If they (FUTEX_WAKE) execute first, that means the addr write is also before us
113 // (FUTEX_WAIT). It is crucial that we observe addr's new value. If we see an
114 // outdated value that happens to equal the expected val, then we'll put ourselves to
115 // sleep with no one to wake us up, so we end up with a deadlock. This is prevented
116 // by having a SeqCst fence inside FUTEX_WAKE syscall, and another SeqCst fence here
117 // in FUTEX_WAIT. The atomic read on addr after the SeqCst fence is guaranteed not to
118 // see any value older than the addr write immediately before calling FUTEX_WAKE.
119 // We'll see futex_val != val and return without sleeping.
120 //
121 // Note that the fences do not create any happens-before relationship.
122 // The read sees the write immediately before the fence not because
123 // one happens after the other, but is instead due to a guarantee unique
124 // to SeqCst fences that restricts what an atomic read placed AFTER the
125 // fence can see. The read still has to be atomic, otherwise it's a data
126 // race. This guarantee cannot be achieved with acquire-release fences
127 // since they only talk about reads placed BEFORE a fence - and places
128 // no restrictions on what the read itself can see, only that there is
129 // a happens-before between the fences IF the read happens to see the
130 // right value. This is useless to us, since we need the read itself
131 // to see an up-to-date value.
132 //
133 // The above case distinction is valid since both FUTEX_WAIT and FUTEX_WAKE
134 // contain a SeqCst fence, therefore inducing a total order between the operations.
135 // It is also critical that the fence, the atomic load, and the comparison in FUTEX_WAIT
136 // altogether happen atomically. If the other thread's fence in FUTEX_WAKE
137 // gets interleaved after our fence, then we lose the guarantee on the
138 // atomic load being up-to-date; if the other thread's write on addr and FUTEX_WAKE
139 // call are interleaved after the load but before the comparison, then we get a TOCTOU
140 // race condition, and go to sleep thinking the other thread will wake us up,
141 // even though they have already finished.
142 //
143 // Thankfully, preemptions cannot happen inside a Miri shim, so we do not need to
144 // do anything special to guarantee fence-load-comparison atomicity.
145 ecx.atomic_fence(AtomicFenceOrd::SeqCst)?;
146 // Read an `i32` through the pointer, regardless of any wrapper types.
147 // It's not uncommon for `addr` to be passed as another type than `*mut i32`, such as `*const AtomicI32`.
148 // We do an acquire read -- it only seems reasonable that if we observe a value here, we
149 // actually establish an ordering with that value.
150 let futex_val = ecx.read_scalar_atomic(&obj, AtomicReadOrd::Acquire)?.to_u32()?;
151 if val == futex_val {
152 // The value still matches, so we block the thread and make it wait for FUTEX_WAKE.
153
154 let futex_ref = ecx
155 .get_or_insert_meta(&obj, |_| {
156 interp_ok(LinuxFutex { futex: Default::default() })
157 })?
158 .futex
159 .clone();
160
161 let dest = dest.clone();
162 ecx.futex_wait(
163 futex_ref,
164 bitset,
165 deadline,
166 callback!(
167 @capture<'tcx> {
168 dest: MPlaceTy<'tcx>,
169 }
170 |ecx, unblock: UnblockKind| match unblock {
171 UnblockKind::Ready => {
172 ecx.write_int(0, &dest)
173 }
174 UnblockKind::TimedOut => {
175 ecx.set_errno_and_return_neg1(LibcError("ETIMEDOUT"), &dest)
176 }
177 }
178 ),
179 );
180 } else {
181 // The futex value doesn't match the expected value, so we return failure
182 // right away without sleeping: -1 and errno set to EAGAIN.
183 return ecx.set_errno_and_return_neg1(LibcError("EAGAIN"), dest);
184 }
185 }
186 // FUTEX_WAKE: (int *addr, int op = FUTEX_WAKE, int val)
187 // Wakes at most `val` threads waiting on the futex at `addr`.
188 // Returns the amount of threads woken up.
189 // Does not access the futex value at *addr.
190 // FUTEX_WAKE_BITSET: (int *addr, int op = FUTEX_WAKE, int val, const timespect *_unused, int *_unused, unsigned int bitset)
191 // Same as FUTEX_WAKE, but allows you to specify a bitset to select which threads to wake up.
192 op if op == futex_wake || op == futex_wake_bitset => {
193 let bitset = if op == futex_wake_bitset {
194 let ([timeout, uaddr2, bitset], _) = ecx.check_varargs(
195 shim_varargs![*libc::timespec, *u32, u32],
196 varargs,
197 "syscall(SYS_futex, ...)",
198 )?;
199 let timeout = ecx.read_pointer(&timeout)?;
200 if !ecx.ptr_is_null(timeout)? {
201 throw_ub_format!("`timeout` pointer must be null for `FUTEX_WAKE_BITSET`");
202 }
203 let uaddr2 = ecx.read_pointer(&uaddr2)?;
204 if !ecx.ptr_is_null(uaddr2)? {
205 throw_ub_format!("`uaddr2` pointer must be null for `FUTEX_WAKE_BITSET`");
206 }
207 ecx.read_scalar(&bitset)?.to_u32()?
208 } else {
209 u32::MAX
210 };
211 if bitset == 0 {
212 return ecx.set_errno_and_return_neg1(LibcError("EINVAL"), dest);
213 }
214
215 // Together with the SeqCst fence in futex_wait, this makes sure that futex_wait will
216 // see the latest value on addr which could be changed by our caller before doing the
217 // syscall. We must do this even if we don't find a futex below in case we are the ones
218 // to go first, and FUTEX_WAIT has not yet had a chance to create a `FutexRef`.
219 ecx.atomic_fence(AtomicFenceOrd::SeqCst)?;
220
221 let futex = match ecx.try_get_meta::<LinuxFutex>(obj.ptr()) {
222 Ok(f) => f,
223 Err(GetMetaError::NoMem) => {
224 // No AllocId, or no live allocation at that AllocId.
225 // Return an error code. (That seems nicer than silently doing something non-intuitive.)
226 // This means that if an address gets reused by a new allocation,
227 // we'll use an independent futex queue for this... that seems acceptable.
228 return ecx.set_errno_and_return_neg1(LibcError("EFAULT"), dest);
229 }
230 Err(GetMetaError::NoObj) => {
231 // Nobody waiting here, nothing to do.
232 ecx.write_int(0, dest)?;
233 return interp_ok(());
234 }
235 };
236 let woken = ecx.futex_wake(&futex.futex.clone(), bitset, val.try_into().unwrap())?;
237 ecx.write_scalar(Scalar::from_target_isize(woken.try_into().unwrap(), ecx), dest)?;
238 }
239 op => throw_unsup_format!("Miri does not support `futex` syscall with op={}", op),
240 }
241
242 interp_ok(())
243}