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$INODE64" => {
50                let [path, buf] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
51                let result = this.stat(path, buf)?;
52                this.write_scalar(result, dest)?;
53            }
54            "lstat$INODE64" => {
55                let [path, buf] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
56                let result = this.lstat(path, buf)?;
57                this.write_scalar(result, dest)?;
58            }
59            "fstat$INODE64" => {
60                let [fd, buf] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
61                let result = this.fstat(fd, buf)?;
62                this.write_scalar(result, dest)?;
63            }
64            "opendir$INODE64" => {
65                let [name] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
66                let result = this.opendir(name)?;
67                this.write_scalar(result, dest)?;
68            }
69            "readdir_r" | "readdir_r$INODE64" => {
70                let [dirp, entry, result] =
71                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
72                let result = this.macos_readdir_r(dirp, entry, result)?;
73                this.write_scalar(result, dest)?;
74            }
75            "realpath$DARWIN_EXTSN" => {
76                let [path, resolved_path] =
77                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
78                let result = this.realpath(path, resolved_path)?;
79                this.write_scalar(result, dest)?;
80            }
81
82            // Environment related shims
83            "_NSGetEnviron" => {
84                // FIXME: This does not have a direct test (#3179).
85                let [] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
86                let environ = this.machine.env_vars.unix().environ();
87                this.write_pointer(environ, dest)?;
88            }
89
90            // Random data generation
91            "CCRandomGenerateBytes" => {
92                let [bytes, count] =
93                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
94                let bytes = this.read_pointer(bytes)?;
95                let count = this.read_target_usize(count)?;
96                let success = this.eval_libc_i32("kCCSuccess");
97                this.gen_random(bytes, count)?;
98                this.write_int(success, dest)?;
99            }
100
101            // Time related shims
102            "mach_absolute_time" => {
103                let [] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
104                let result = this.mach_absolute_time()?;
105                this.write_scalar(result, dest)?;
106            }
107
108            "mach_timebase_info" => {
109                // FIXME: This does not have a direct test (#3179).
110                let [info] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
111                let result = this.mach_timebase_info(info)?;
112                this.write_scalar(result, dest)?;
113            }
114
115            "mach_wait_until" => {
116                // FIXME: This does not have a direct test (#3179).
117                let [deadline] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
118                let result = this.mach_wait_until(deadline)?;
119                this.write_scalar(result, dest)?;
120            }
121
122            // Access to command-line arguments
123            "_NSGetArgc" => {
124                // FIXME: This does not have a direct test (#3179).
125                let [] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
126                this.write_pointer(this.machine.argc.expect("machine must be initialized"), dest)?;
127            }
128            "_NSGetArgv" => {
129                // FIXME: This does not have a direct test (#3179).
130                let [] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
131                this.write_pointer(this.machine.argv.expect("machine must be initialized"), dest)?;
132            }
133            "_NSGetExecutablePath" => {
134                // FIXME: This does not have a direct test (#3179).
135                let [buf, bufsize] =
136                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
137                this.check_no_isolation("`_NSGetExecutablePath`")?;
138
139                let buf_ptr = this.read_pointer(buf)?;
140                let bufsize = this.deref_pointer_as(bufsize, this.machine.layouts.u32)?;
141
142                // Using the host current_exe is a bit off, but consistent with Linux
143                // (where stdlib reads /proc/self/exe).
144                let path = std::env::current_exe().unwrap();
145                let (written, size_needed) = this.write_path_to_c_str(
146                    &path,
147                    buf_ptr,
148                    this.read_scalar(&bufsize)?.to_u32()?.into(),
149                )?;
150
151                if written {
152                    this.write_null(dest)?;
153                } else {
154                    this.write_scalar(Scalar::from_u32(size_needed.try_into().unwrap()), &bufsize)?;
155                    this.write_int(-1, dest)?;
156                }
157            }
158
159            // Thread-local storage
160            "_tlv_atexit" => {
161                let [dtor, data] =
162                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
163                let dtor = this.read_pointer(dtor)?;
164                let dtor = this.get_ptr_fn(dtor)?.as_instance()?;
165                let data = this.read_scalar(data)?;
166                let active_thread = this.active_thread();
167                this.machine.tls.add_macos_thread_dtor(
168                    active_thread,
169                    dtor,
170                    data,
171                    this.machine.current_user_relevant_span(),
172                )?;
173            }
174
175            // Querying system information
176            "pthread_get_stackaddr_np" => {
177                // FIXME: This does not have a direct test (#3179).
178                let [thread] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
179                this.read_target_usize(thread)?;
180                let stack_addr = Scalar::from_uint(this.machine.stack_addr, this.pointer_size());
181                this.write_scalar(stack_addr, dest)?;
182            }
183            "pthread_get_stacksize_np" => {
184                // FIXME: This does not have a direct test (#3179).
185                let [thread] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
186                this.read_target_usize(thread)?;
187                let stack_size = Scalar::from_uint(this.machine.stack_size, this.pointer_size());
188                this.write_scalar(stack_size, dest)?;
189            }
190
191            // Threading
192            "pthread_setname_np" => {
193                let [name] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
194
195                // The real implementation has logic in two places:
196                // * in userland at https://github.com/apple-oss-distributions/libpthread/blob/c032e0b076700a0a47db75528a282b8d3a06531a/src/pthread.c#L1178-L1200,
197                // * in kernel at https://github.com/apple-oss-distributions/xnu/blob/8d741a5de7ff4191bf97d57b9f54c2f6d4a15585/bsd/kern/proc_info.c#L3218-L3227.
198                //
199                // The function in libc calls the kernel to validate
200                // the security policies and the input. If all of the requirements
201                // are met, then the name is set and 0 is returned. Otherwise, if
202                // the specified name is lomnger than MAXTHREADNAMESIZE, then
203                // ENAMETOOLONG is returned.
204                let thread = this.pthread_self()?;
205                let res = match this.pthread_setname_np(
206                    thread,
207                    this.read_scalar(name)?,
208                    this.eval_libc("MAXTHREADNAMESIZE").to_target_usize(this)?,
209                    /* truncate */ false,
210                )? {
211                    ThreadNameResult::Ok => Scalar::from_u32(0),
212                    ThreadNameResult::NameTooLong => this.eval_libc("ENAMETOOLONG"),
213                    ThreadNameResult::ThreadNotFound => unreachable!(),
214                };
215                // Contrary to the manpage, `pthread_setname_np` on macOS still
216                // returns an integer indicating success.
217                this.write_scalar(res, dest)?;
218            }
219            "pthread_getname_np" => {
220                let [thread, name, len] =
221                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
222
223                // The function's behavior isn't portable between platforms.
224                // In case of macOS, a truncated name (due to a too small buffer)
225                // does not lead to an error.
226                //
227                // For details, see the implementation at
228                // https://github.com/apple-oss-distributions/libpthread/blob/c032e0b076700a0a47db75528a282b8d3a06531a/src/pthread.c#L1160-L1175.
229                // The key part is the strlcpy, which truncates the resulting value,
230                // but always null terminates (except for zero sized buffers).
231                let res = match this.pthread_getname_np(
232                    this.read_scalar(thread)?,
233                    this.read_scalar(name)?,
234                    this.read_scalar(len)?,
235                    /* truncate */ true,
236                )? {
237                    ThreadNameResult::Ok => Scalar::from_u32(0),
238                    // `NameTooLong` is possible when the buffer is zero sized,
239                    ThreadNameResult::NameTooLong => Scalar::from_u32(0),
240                    ThreadNameResult::ThreadNotFound => this.eval_libc("ESRCH"),
241                };
242                this.write_scalar(res, dest)?;
243            }
244            "pthread_threadid_np" => {
245                let [thread, tid_ptr] =
246                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
247                let res = this.apple_pthread_threadip_np(thread, tid_ptr)?;
248                this.write_scalar(res, dest)?;
249            }
250
251            // Synchronization primitives
252            "os_sync_wait_on_address" => {
253                let [addr_op, value_op, size_op, flags_op] =
254                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
255                this.os_sync_wait_on_address(
256                    addr_op,
257                    value_op,
258                    size_op,
259                    flags_op,
260                    MacOsFutexTimeout::None,
261                    dest,
262                )?;
263            }
264            "os_sync_wait_on_address_with_deadline" => {
265                let [addr_op, value_op, size_op, flags_op, clock_op, timeout_op] =
266                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
267                this.os_sync_wait_on_address(
268                    addr_op,
269                    value_op,
270                    size_op,
271                    flags_op,
272                    MacOsFutexTimeout::Absolute { clock_op, timeout_op },
273                    dest,
274                )?;
275            }
276            "os_sync_wait_on_address_with_timeout" => {
277                let [addr_op, value_op, size_op, flags_op, clock_op, timeout_op] =
278                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
279                this.os_sync_wait_on_address(
280                    addr_op,
281                    value_op,
282                    size_op,
283                    flags_op,
284                    MacOsFutexTimeout::Relative { clock_op, timeout_op },
285                    dest,
286                )?;
287            }
288            "os_sync_wake_by_address_any" => {
289                let [addr_op, size_op, flags_op] =
290                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
291                this.os_sync_wake_by_address(
292                    addr_op, size_op, flags_op, /* all */ false, dest,
293                )?;
294            }
295            "os_sync_wake_by_address_all" => {
296                let [addr_op, size_op, flags_op] =
297                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
298                this.os_sync_wake_by_address(
299                    addr_op, size_op, flags_op, /* all */ true, dest,
300                )?;
301            }
302            "os_unfair_lock_lock" => {
303                let [lock_op] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
304                this.os_unfair_lock_lock(lock_op)?;
305            }
306            "os_unfair_lock_trylock" => {
307                let [lock_op] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
308                this.os_unfair_lock_trylock(lock_op, dest)?;
309            }
310            "os_unfair_lock_unlock" => {
311                let [lock_op] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
312                this.os_unfair_lock_unlock(lock_op)?;
313            }
314            "os_unfair_lock_assert_owner" => {
315                let [lock_op] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
316                this.os_unfair_lock_assert_owner(lock_op)?;
317            }
318            "os_unfair_lock_assert_not_owner" => {
319                let [lock_op] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
320                this.os_unfair_lock_assert_not_owner(lock_op)?;
321            }
322
323            "pthread_cond_timedwait_relative_np" => {
324                let [cond, mutex, reltime] =
325                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
326                this.pthread_cond_timedwait(
327                    cond, mutex, reltime, dest, /* macos_relative_np */ true,
328                )?;
329            }
330
331            _ => return interp_ok(EmulateItemResult::NotSupported),
332        };
333
334        interp_ok(EmulateItemResult::NeedsReturn)
335    }
336}