]> git.lizzy.rs Git - rust.git/blob - src/tools/miri/src/shims/unix/linux/sync.rs
31461e4c9fd384d23e2efd761a993108f665a1c4
[rust.git] / src / tools / miri / src / shims / unix / linux / sync.rs
1 use std::time::SystemTime;
2
3 use crate::concurrency::thread::{MachineCallback, Time};
4 use crate::*;
5
6 /// Implementation of the SYS_futex syscall.
7 /// `args` is the arguments *after* the syscall number.
8 pub fn futex<'tcx>(
9     this: &mut MiriInterpCx<'_, 'tcx>,
10     args: &[OpTy<'tcx, Provenance>],
11     dest: &PlaceTy<'tcx, Provenance>,
12 ) -> InterpResult<'tcx> {
13     // The amount of arguments used depends on the type of futex operation.
14     // The full futex syscall takes six arguments (excluding the syscall
15     // number), which is also the maximum amount of arguments a linux syscall
16     // can take on most architectures.
17     // However, not all futex operations use all six arguments. The unused ones
18     // may or may not be left out from the `syscall()` call.
19     // Therefore we don't use `check_arg_count` here, but only check for the
20     // number of arguments to fall within a range.
21     if args.len() < 3 {
22         throw_ub_format!(
23             "incorrect number of arguments for `futex` syscall: got {}, expected at least 3",
24             args.len()
25         );
26     }
27
28     // The first three arguments (after the syscall number itself) are the same to all futex operations:
29     //     (int *addr, int op, int val).
30     // We checked above that these definitely exist.
31     let addr = this.read_pointer(&args[0])?;
32     let op = this.read_scalar(&args[1])?.to_i32()?;
33     let val = this.read_scalar(&args[2])?.to_i32()?;
34
35     let thread = this.get_active_thread();
36     // This is a vararg function so we have to bring our own type for this pointer.
37     let addr = MPlaceTy::from_aligned_ptr(addr, this.machine.layouts.i32);
38     let addr_usize = addr.ptr.addr().bytes();
39
40     let futex_private = this.eval_libc_i32("FUTEX_PRIVATE_FLAG")?;
41     let futex_wait = this.eval_libc_i32("FUTEX_WAIT")?;
42     let futex_wait_bitset = this.eval_libc_i32("FUTEX_WAIT_BITSET")?;
43     let futex_wake = this.eval_libc_i32("FUTEX_WAKE")?;
44     let futex_wake_bitset = this.eval_libc_i32("FUTEX_WAKE_BITSET")?;
45     let futex_realtime = this.eval_libc_i32("FUTEX_CLOCK_REALTIME")?;
46
47     // FUTEX_PRIVATE enables an optimization that stops it from working across processes.
48     // Miri doesn't support that anyway, so we ignore that flag.
49     match op & !futex_private {
50         // FUTEX_WAIT: (int *addr, int op = FUTEX_WAIT, int val, const timespec *timeout)
51         // Blocks the thread if *addr still equals val. Wakes up when FUTEX_WAKE is called on the same address,
52         // or *timeout expires. `timeout == null` for an infinite timeout.
53         //
54         // FUTEX_WAIT_BITSET: (int *addr, int op = FUTEX_WAIT_BITSET, int val, const timespec *timeout, int *_ignored, unsigned int bitset)
55         // This is identical to FUTEX_WAIT, except:
56         //  - The timeout is absolute rather than relative.
57         //  - You can specify the bitset to selecting what WAKE operations to respond to.
58         op if op & !futex_realtime == futex_wait || op & !futex_realtime == futex_wait_bitset => {
59             let wait_bitset = op & !futex_realtime == futex_wait_bitset;
60
61             let bitset = if wait_bitset {
62                 if args.len() < 6 {
63                     throw_ub_format!(
64                         "incorrect number of arguments for `futex` syscall with `op=FUTEX_WAIT_BITSET`: got {}, expected at least 6",
65                         args.len()
66                     );
67                 }
68                 let _timeout = this.read_pointer(&args[3])?;
69                 let _uaddr2 = this.read_pointer(&args[4])?;
70                 this.read_scalar(&args[5])?.to_u32()?
71             } else {
72                 if args.len() < 4 {
73                     throw_ub_format!(
74                         "incorrect number of arguments for `futex` syscall with `op=FUTEX_WAIT`: got {}, expected at least 4",
75                         args.len()
76                     );
77                 }
78                 u32::MAX
79             };
80
81             if bitset == 0 {
82                 let einval = this.eval_libc("EINVAL")?;
83                 this.set_last_error(einval)?;
84                 this.write_scalar(Scalar::from_machine_isize(-1, this), dest)?;
85                 return Ok(());
86             }
87
88             // `deref_operand` but not actually dereferencing the ptr yet (it might be NULL!).
89             let timeout = this.ref_to_mplace(&this.read_immediate(&args[3])?)?;
90             let timeout_time = if this.ptr_is_null(timeout.ptr)? {
91                 None
92             } else {
93                 let realtime = op & futex_realtime == futex_realtime;
94                 if realtime {
95                     this.check_no_isolation(
96                         "`futex` syscall with `op=FUTEX_WAIT` and non-null timeout with `FUTEX_CLOCK_REALTIME`",
97                     )?;
98                 }
99                 let duration = match this.read_timespec(&timeout)? {
100                     Some(duration) => duration,
101                     None => {
102                         let einval = this.eval_libc("EINVAL")?;
103                         this.set_last_error(einval)?;
104                         this.write_scalar(Scalar::from_machine_isize(-1, this), dest)?;
105                         return Ok(());
106                     }
107                 };
108                 Some(if wait_bitset {
109                     // FUTEX_WAIT_BITSET uses an absolute timestamp.
110                     if realtime {
111                         Time::RealTime(SystemTime::UNIX_EPOCH.checked_add(duration).unwrap())
112                     } else {
113                         Time::Monotonic(this.machine.clock.anchor().checked_add(duration).unwrap())
114                     }
115                 } else {
116                     // FUTEX_WAIT uses a relative timestamp.
117                     if realtime {
118                         Time::RealTime(SystemTime::now().checked_add(duration).unwrap())
119                     } else {
120                         Time::Monotonic(this.machine.clock.now().checked_add(duration).unwrap())
121                     }
122                 })
123             };
124             // There may be a concurrent thread changing the value of addr
125             // and then invoking the FUTEX_WAKE syscall. It is critical that the
126             // effects of this and the other thread are correctly observed,
127             // otherwise we will deadlock.
128             //
129             // There are two scenarios to consider:
130             // 1. If we (FUTEX_WAIT) execute first, we'll push ourselves into
131             //    the waiters queue and go to sleep. They (addr write & FUTEX_WAKE)
132             //    will see us in the queue and wake us up.
133             // 2. If they (addr write & FUTEX_WAKE) execute first, we must observe
134             //    addr's new value. If we see an outdated value that happens to equal
135             //    the expected val, then we'll put ourselves to sleep with no one to wake us
136             //    up, so we end up with a deadlock. This is prevented by having a SeqCst
137             //    fence inside FUTEX_WAKE syscall, and another SeqCst fence
138             //    below, the atomic read on addr after the SeqCst fence is guaranteed
139             //    not to see any value older than the addr write immediately before
140             //    calling FUTEX_WAKE. We'll see futex_val != val and return without
141             //    sleeping.
142             //
143             //    Note that the fences do not create any happens-before relationship.
144             //    The read sees the write immediately before the fence not because
145             //    one happens after the other, but is instead due to a guarantee unique
146             //    to SeqCst fences that restricts what an atomic read placed AFTER the
147             //    fence can see. The read still has to be atomic, otherwise it's a data
148             //    race. This guarantee cannot be achieved with acquire-release fences
149             //    since they only talk about reads placed BEFORE a fence - and places
150             //    no restrictions on what the read itself can see, only that there is
151             //    a happens-before between the fences IF the read happens to see the
152             //    right value. This is useless to us, since we need the read itself
153             //    to see an up-to-date value.
154             //
155             // The above case distinction is valid since both FUTEX_WAIT and FUTEX_WAKE
156             // contain a SeqCst fence, therefore inducting a total order between the operations.
157             // It is also critical that the fence, the atomic load, and the comparison in FUTEX_WAIT
158             // altogether happen atomically. If the other thread's fence in FUTEX_WAKE
159             // gets interleaved after our fence, then we lose the guarantee on the
160             // atomic load being up-to-date; if the other thread's write on addr and FUTEX_WAKE
161             // call are interleaved after the load but before the comparison, then we get a TOCTOU
162             // race condition, and go to sleep thinking the other thread will wake us up,
163             // even though they have already finished.
164             //
165             // Thankfully, preemptions cannot happen inside a Miri shim, so we do not need to
166             // do anything special to guarantee fence-load-comparison atomicity.
167             this.atomic_fence(AtomicFenceOrd::SeqCst)?;
168             // Read an `i32` through the pointer, regardless of any wrapper types.
169             // It's not uncommon for `addr` to be passed as another type than `*mut i32`, such as `*const AtomicI32`.
170             let futex_val = this.read_scalar_atomic(&addr, AtomicReadOrd::Relaxed)?.to_i32()?;
171             if val == futex_val {
172                 // The value still matches, so we block the thread make it wait for FUTEX_WAKE.
173                 this.block_thread(thread);
174                 this.futex_wait(addr_usize, thread, bitset);
175                 // Succesfully waking up from FUTEX_WAIT always returns zero.
176                 this.write_scalar(Scalar::from_machine_isize(0, this), dest)?;
177                 // Register a timeout callback if a timeout was specified.
178                 // This callback will override the return value when the timeout triggers.
179                 if let Some(timeout_time) = timeout_time {
180                     struct Callback<'tcx> {
181                         thread: ThreadId,
182                         addr_usize: u64,
183                         dest: PlaceTy<'tcx, Provenance>,
184                     }
185
186                     impl<'tcx> VisitTags for Callback<'tcx> {
187                         fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
188                             let Callback { thread: _, addr_usize: _, dest } = self;
189                             dest.visit_tags(visit);
190                         }
191                     }
192
193                     impl<'mir, 'tcx: 'mir> MachineCallback<'mir, 'tcx> for Callback<'tcx> {
194                         fn call(&self, this: &mut MiriInterpCx<'mir, 'tcx>) -> InterpResult<'tcx> {
195                             this.unblock_thread(self.thread);
196                             this.futex_remove_waiter(self.addr_usize, self.thread);
197                             let etimedout = this.eval_libc("ETIMEDOUT")?;
198                             this.set_last_error(etimedout)?;
199                             this.write_scalar(Scalar::from_machine_isize(-1, this), &self.dest)?;
200
201                             Ok(())
202                         }
203                     }
204
205                     this.register_timeout_callback(
206                         thread,
207                         timeout_time,
208                         Box::new(Callback { thread, addr_usize, dest: dest.clone() }),
209                     );
210                 }
211             } else {
212                 // The futex value doesn't match the expected value, so we return failure
213                 // right away without sleeping: -1 and errno set to EAGAIN.
214                 let eagain = this.eval_libc("EAGAIN")?;
215                 this.set_last_error(eagain)?;
216                 this.write_scalar(Scalar::from_machine_isize(-1, this), dest)?;
217             }
218         }
219         // FUTEX_WAKE: (int *addr, int op = FUTEX_WAKE, int val)
220         // Wakes at most `val` threads waiting on the futex at `addr`.
221         // Returns the amount of threads woken up.
222         // Does not access the futex value at *addr.
223         // FUTEX_WAKE_BITSET: (int *addr, int op = FUTEX_WAKE, int val, const timespect *_unused, int *_unused, unsigned int bitset)
224         // Same as FUTEX_WAKE, but allows you to specify a bitset to select which threads to wake up.
225         op if op == futex_wake || op == futex_wake_bitset => {
226             let bitset = if op == futex_wake_bitset {
227                 if args.len() < 6 {
228                     throw_ub_format!(
229                         "incorrect number of arguments for `futex` syscall with `op=FUTEX_WAKE_BITSET`: got {}, expected at least 6",
230                         args.len()
231                     );
232                 }
233                 let _timeout = this.read_pointer(&args[3])?;
234                 let _uaddr2 = this.read_pointer(&args[4])?;
235                 this.read_scalar(&args[5])?.to_u32()?
236             } else {
237                 u32::MAX
238             };
239             if bitset == 0 {
240                 let einval = this.eval_libc("EINVAL")?;
241                 this.set_last_error(einval)?;
242                 this.write_scalar(Scalar::from_machine_isize(-1, this), dest)?;
243                 return Ok(());
244             }
245             // Together with the SeqCst fence in futex_wait, this makes sure that futex_wait
246             // will see the latest value on addr which could be changed by our caller
247             // before doing the syscall.
248             this.atomic_fence(AtomicFenceOrd::SeqCst)?;
249             let mut n = 0;
250             #[allow(clippy::integer_arithmetic)]
251             for _ in 0..val {
252                 if let Some(thread) = this.futex_wake(addr_usize, bitset) {
253                     this.unblock_thread(thread);
254                     this.unregister_timeout_callback_if_exists(thread);
255                     n += 1;
256                 } else {
257                     break;
258                 }
259             }
260             this.write_scalar(Scalar::from_machine_isize(n, this), dest)?;
261         }
262         op => throw_unsup_format!("Miri does not support `futex` syscall with op={}", op),
263     }
264
265     Ok(())
266 }