Skip to main content

miri/shims/unix/macos/
foreign_items.rs

1use rustc_abi::CanonAbi;
2use rustc_middle::ty::Ty;
3use rustc_span::Symbol;
4use rustc_target::callconv::FnAbi;
5
6use super::sync::{EvalContextExt as _, MacOsFutexTimeout};
7use crate::shims::unix::*;
8use crate::*;
9
10pub fn is_dyn_sym(name: &str) -> bool {
11    match name {
12        // These only became available with macOS 11.0, so std looks them up dynamically.
13        "os_sync_wait_on_address"
14        | "os_sync_wait_on_address_with_deadline"
15        | "os_sync_wait_on_address_with_timeout"
16        | "os_sync_wake_by_address_any"
17        | "os_sync_wake_by_address_all" => true,
18        _ => false,
19    }
20}
21
22impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
23pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
24    fn emulate_foreign_item_inner(
25        &mut self,
26        link_name: Symbol,
27        abi: &FnAbi<'tcx, Ty<'tcx>>,
28        args: &[OpTy<'tcx>],
29        dest: &MPlaceTy<'tcx>,
30    ) -> InterpResult<'tcx, EmulateItemResult> {
31        let this = self.eval_context_mut();
32
33        // See `fn emulate_foreign_item_inner` in `shims/foreign_items.rs` for the general pattern.
34
35        match link_name.as_str() {
36            // errno
37            "__error" => {
38                let [] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
39                let errno_place = this.last_error_place()?;
40                this.write_scalar(errno_place.to_ref(this).to_scalar(), dest)?;
41            }
42
43            // File related shims
44            "close$NOCANCEL" => {
45                let [result] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
46                let result = this.close(result)?;
47                this.write_scalar(result, dest)?;
48            }
49            "stat" | "stat$INODE64" => {
50                // FIXME: This does not have a direct test (#3179).
51                let [path, buf] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
52                let result = this.stat(path, buf)?;
53                this.write_scalar(result, dest)?;
54            }
55            "lstat" | "lstat$INODE64" => {
56                // FIXME: This does not have a direct test (#3179).
57                let [path, buf] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
58                let result = this.lstat(path, buf)?;
59                this.write_scalar(result, dest)?;
60            }
61            "fstat$INODE64" => {
62                let [fd, buf] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
63                let result = this.fstat(fd, buf)?;
64                this.write_scalar(result, dest)?;
65            }
66            "opendir$INODE64" => {
67                let [name] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
68                let result = this.opendir(name)?;
69                this.write_scalar(result, dest)?;
70            }
71            "readdir_r" | "readdir_r$INODE64" => {
72                let [dirp, entry, result] =
73                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
74                let result = this.macos_readdir_r(dirp, entry, result)?;
75                this.write_scalar(result, dest)?;
76            }
77            "realpath$DARWIN_EXTSN" => {
78                let [path, resolved_path] =
79                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
80                let result = this.realpath(path, resolved_path)?;
81                this.write_scalar(result, dest)?;
82            }
83            "ioctl" => {
84                let ([fd_num, cmd], varargs) =
85                    this.check_shim_sig_variadic_lenient(abi, CanonAbi::C, link_name, args)?;
86                let result = this.ioctl(fd_num, cmd, varargs)?;
87                this.write_scalar(result, dest)?;
88            }
89
90            // Environment related shims
91            "_NSGetEnviron" => {
92                // FIXME: This does not have a direct test (#3179).
93                let [] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
94                let environ = this.machine.env_vars.unix().environ();
95                this.write_pointer(environ, dest)?;
96            }
97
98            // Random data generation
99            "CCRandomGenerateBytes" => {
100                let [bytes, count] =
101                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
102                let bytes = this.read_pointer(bytes)?;
103                let count = this.read_target_usize(count)?;
104                let success = this.eval_libc_i32("kCCSuccess");
105                this.gen_random(bytes, count)?;
106                this.write_int(success, dest)?;
107            }
108
109            // Time related shims
110            "mach_absolute_time" => {
111                let [] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
112                let result = this.mach_absolute_time()?;
113                this.write_scalar(result, dest)?;
114            }
115
116            "mach_timebase_info" => {
117                // FIXME: This does not have a direct test (#3179).
118                let [info] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
119                let result = this.mach_timebase_info(info)?;
120                this.write_scalar(result, dest)?;
121            }
122
123            "mach_wait_until" => {
124                // FIXME: This does not have a direct test (#3179).
125                let [deadline] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
126                let result = this.mach_wait_until(deadline)?;
127                this.write_scalar(result, dest)?;
128            }
129
130            // Access to command-line arguments
131            "_NSGetArgc" => {
132                // FIXME: This does not have a direct test (#3179).
133                let [] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
134                this.write_pointer(this.machine.argc.expect("machine must be initialized"), dest)?;
135            }
136            "_NSGetArgv" => {
137                // FIXME: This does not have a direct test (#3179).
138                let [] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
139                this.write_pointer(this.machine.argv.expect("machine must be initialized"), dest)?;
140            }
141            "_NSGetExecutablePath" => {
142                // FIXME: This does not have a direct test (#3179).
143                let [buf, bufsize] =
144                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
145                this.check_no_isolation("`_NSGetExecutablePath`")?;
146
147                let buf_ptr = this.read_pointer(buf)?;
148                let bufsize = this.deref_pointer_as(bufsize, this.machine.layouts.u32)?;
149
150                // Using the host current_exe is a bit off, but consistent with Linux
151                // (where stdlib reads /proc/self/exe).
152                let path = std::env::current_exe().unwrap();
153                let (written, size_needed) = this.write_path_to_c_str(
154                    &path,
155                    buf_ptr,
156                    this.read_scalar(&bufsize)?.to_u32()?.into(),
157                )?;
158
159                if written {
160                    this.write_null(dest)?;
161                } else {
162                    this.write_scalar(Scalar::from_u32(size_needed.try_into().unwrap()), &bufsize)?;
163                    this.write_int(-1, dest)?;
164                }
165            }
166
167            // Thread-local storage
168            "_tlv_atexit" => {
169                let [dtor, data] =
170                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
171                let dtor = this.read_pointer(dtor)?;
172                let dtor = this.get_ptr_fn(dtor)?.as_instance()?;
173                let data = this.read_scalar(data)?;
174                let active_thread = this.active_thread();
175                this.machine.tls.add_macos_thread_dtor(
176                    active_thread,
177                    dtor,
178                    data,
179                    this.machine.current_user_relevant_span(),
180                )?;
181            }
182
183            // Querying system information
184            "pthread_get_stackaddr_np" => {
185                // FIXME: This does not have a direct test (#3179).
186                let [thread] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
187                this.read_target_usize(thread)?;
188                let stack_addr = Scalar::from_uint(this.machine.stack_addr, this.pointer_size());
189                this.write_scalar(stack_addr, dest)?;
190            }
191            "pthread_get_stacksize_np" => {
192                // FIXME: This does not have a direct test (#3179).
193                let [thread] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
194                this.read_target_usize(thread)?;
195                let stack_size = Scalar::from_uint(this.machine.stack_size, this.pointer_size());
196                this.write_scalar(stack_size, dest)?;
197            }
198
199            // Threading
200            "pthread_setname_np" => {
201                let [name] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
202
203                // The real implementation has logic in two places:
204                // * in userland at https://github.com/apple-oss-distributions/libpthread/blob/c032e0b076700a0a47db75528a282b8d3a06531a/src/pthread.c#L1178-L1200,
205                // * in kernel at https://github.com/apple-oss-distributions/xnu/blob/8d741a5de7ff4191bf97d57b9f54c2f6d4a15585/bsd/kern/proc_info.c#L3218-L3227.
206                //
207                // The function in libc calls the kernel to validate
208                // the security policies and the input. If all of the requirements
209                // are met, then the name is set and 0 is returned. Otherwise, if
210                // the specified name is lomnger than MAXTHREADNAMESIZE, then
211                // ENAMETOOLONG is returned.
212                let thread = this.pthread_self()?;
213                let res = match this.pthread_setname_np(
214                    thread,
215                    this.read_scalar(name)?,
216                    this.eval_libc("MAXTHREADNAMESIZE").to_target_usize(this)?,
217                    /* truncate */ false,
218                )? {
219                    ThreadNameResult::Ok => Scalar::from_u32(0),
220                    ThreadNameResult::NameTooLong => this.eval_libc("ENAMETOOLONG"),
221                    ThreadNameResult::ThreadNotFound => unreachable!(),
222                };
223                // Contrary to the manpage, `pthread_setname_np` on macOS still
224                // returns an integer indicating success.
225                this.write_scalar(res, dest)?;
226            }
227            "pthread_getname_np" => {
228                let [thread, name, len] =
229                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
230
231                // The function's behavior isn't portable between platforms.
232                // In case of macOS, a truncated name (due to a too small buffer)
233                // does not lead to an error.
234                //
235                // For details, see the implementation at
236                // https://github.com/apple-oss-distributions/libpthread/blob/c032e0b076700a0a47db75528a282b8d3a06531a/src/pthread.c#L1160-L1175.
237                // The key part is the strlcpy, which truncates the resulting value,
238                // but always null terminates (except for zero sized buffers).
239                let res = match this.pthread_getname_np(
240                    this.read_scalar(thread)?,
241                    this.read_scalar(name)?,
242                    this.read_scalar(len)?,
243                    /* truncate */ true,
244                )? {
245                    ThreadNameResult::Ok => Scalar::from_u32(0),
246                    // `NameTooLong` is possible when the buffer is zero sized,
247                    ThreadNameResult::NameTooLong => Scalar::from_u32(0),
248                    ThreadNameResult::ThreadNotFound => this.eval_libc("ESRCH"),
249                };
250                this.write_scalar(res, dest)?;
251            }
252            "pthread_threadid_np" => {
253                let [thread, tid_ptr] =
254                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
255                let res = this.apple_pthread_threadip_np(thread, tid_ptr)?;
256                this.write_scalar(res, dest)?;
257            }
258
259            // Synchronization primitives
260            "os_sync_wait_on_address" => {
261                let [addr_op, value_op, size_op, flags_op] =
262                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
263                this.os_sync_wait_on_address(
264                    addr_op,
265                    value_op,
266                    size_op,
267                    flags_op,
268                    MacOsFutexTimeout::None,
269                    dest,
270                )?;
271            }
272            "os_sync_wait_on_address_with_deadline" => {
273                let [addr_op, value_op, size_op, flags_op, clock_op, timeout_op] =
274                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
275                this.os_sync_wait_on_address(
276                    addr_op,
277                    value_op,
278                    size_op,
279                    flags_op,
280                    MacOsFutexTimeout::Absolute { clock_op, timeout_op },
281                    dest,
282                )?;
283            }
284            "os_sync_wait_on_address_with_timeout" => {
285                let [addr_op, value_op, size_op, flags_op, clock_op, timeout_op] =
286                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
287                this.os_sync_wait_on_address(
288                    addr_op,
289                    value_op,
290                    size_op,
291                    flags_op,
292                    MacOsFutexTimeout::Relative { clock_op, timeout_op },
293                    dest,
294                )?;
295            }
296            "os_sync_wake_by_address_any" => {
297                let [addr_op, size_op, flags_op] =
298                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
299                this.os_sync_wake_by_address(
300                    addr_op, size_op, flags_op, /* all */ false, dest,
301                )?;
302            }
303            "os_sync_wake_by_address_all" => {
304                let [addr_op, size_op, flags_op] =
305                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
306                this.os_sync_wake_by_address(
307                    addr_op, size_op, flags_op, /* all */ true, dest,
308                )?;
309            }
310            "os_unfair_lock_lock" => {
311                let [lock_op] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
312                this.os_unfair_lock_lock(lock_op)?;
313            }
314            "os_unfair_lock_trylock" => {
315                let [lock_op] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
316                this.os_unfair_lock_trylock(lock_op, dest)?;
317            }
318            "os_unfair_lock_unlock" => {
319                let [lock_op] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
320                this.os_unfair_lock_unlock(lock_op)?;
321            }
322            "os_unfair_lock_assert_owner" => {
323                let [lock_op] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
324                this.os_unfair_lock_assert_owner(lock_op)?;
325            }
326            "os_unfair_lock_assert_not_owner" => {
327                let [lock_op] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
328                this.os_unfair_lock_assert_not_owner(lock_op)?;
329            }
330
331            "pthread_cond_timedwait_relative_np" => {
332                let [cond, mutex, reltime] =
333                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
334                this.pthread_cond_timedwait(
335                    cond, mutex, reltime, dest, /* macos_relative_np */ true,
336                )?;
337            }
338
339            _ => return interp_ok(EmulateItemResult::NotSupported),
340        };
341
342        interp_ok(EmulateItemResult::NeedsReturn)
343    }
344
345    fn ioctl(
346        &mut self,
347        fd_num: &OpTy<'tcx>,
348        cmd: &OpTy<'tcx>,
349        _varargs: &[OpTy<'tcx>],
350    ) -> InterpResult<'tcx, Scalar> {
351        let this = self.eval_context_mut();
352
353        let fioclex = this.eval_libc_u64("FIOCLEX");
354
355        let fd_num = this.read_scalar(fd_num)?.to_i32()?;
356        let cmd = this.read_scalar(cmd)?.to_u64()?;
357
358        if cmd == fioclex {
359            // Since we don't support `exec`, this is a NOP. However, we want to
360            // return EBADF if the FD is invalid.
361            if this.machine.fds.is_fd_num(fd_num) {
362                interp_ok(Scalar::from_i32(0))
363            } else {
364                this.set_last_error_and_return_i32(LibcError("EBADF"))
365            }
366        } else {
367            throw_unsup_format!("ioctl: unsupported command {cmd:#x}");
368        }
369    }
370}