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