miri/shims/unix/macos/
foreign_items.rs

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