Skip to main content

miri/shims/unix/linux/
foreign_items.rs

1use rustc_middle::ty::Ty;
2use rustc_span::Symbol;
3use rustc_target::callconv::FnAbi;
4use rustc_target::spec::{Arch, Target};
5
6use self::shims::unix::linux::mem::EvalContextExt as _;
7use self::shims::unix::linux_like::eventfd::EvalContextExt as _;
8use self::shims::unix::linux_like::syscall::syscall;
9use crate::machine::{SIGRTMAX, SIGRTMIN};
10use crate::shims::unix::foreign_items::EvalContextExt as _;
11use crate::shims::unix::linux_like::epoll::EvalContextExt as _;
12use crate::shims::unix::linux_like::thread::prctl;
13use crate::shims::unix::*;
14use crate::*;
15
16// The documentation of glibc complains that the kernel never exposes
17// TASK_COMM_LEN through the headers, so it's assumed to always be 16 bytes
18// long including a null terminator.
19const TASK_COMM_LEN: u64 = 16;
20
21pub fn is_dyn_sym(name: &str, target: &Target) -> bool {
22    match name {
23        "copy_file_range" | "gettid" | "statx" => true,
24        "__cxa_thread_atexit_impl" => target.env == rustc_target::spec::Env::Gnu,
25        // glibc only exposes this symbol on targets with a 32-bit time_t.
26        "__futimens64" =>
27            target.env == rustc_target::spec::Env::Gnu
28                && matches!(target.arch, Arch::X86 | Arch::Arm | Arch::Mips),
29        _ => false,
30    }
31}
32
33impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
34pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
35    fn emulate_foreign_item_inner(
36        &mut self,
37        link_name: Symbol,
38        abi: &FnAbi<'tcx, Ty<'tcx>>,
39        args: &[OpTy<'tcx>],
40        dest: &MPlaceTy<'tcx>,
41    ) -> InterpResult<'tcx, EmulateItemResult> {
42        let ecx = self.eval_context_mut();
43
44        // See `fn emulate_foreign_item_inner` in `shims/foreign_items.rs` for the general pattern.
45
46        match link_name.as_str() {
47            // File related shims
48            "open64" => {
49                // "open64" means that the file is opened with O_LARGEFILE, which we always do.
50                let ([path_raw, flag], varargs) = ecx.check_shim_sig_variadic(
51                    shim_sig!(extern "C" fn(*_, i32, ...) -> i32),
52                    (link_name, abi, args),
53                )?;
54                let result = ecx.open(None, &path_raw, &flag, varargs)?;
55                ecx.write_scalar(result, dest)?;
56            }
57            "openat64" => {
58                // "openat64" means that the file is opened with O_LARGEFILE, which we always do.
59                let ([dirfd, path_raw, flag], varargs) = ecx.check_shim_sig_variadic(
60                    shim_sig!(extern "C" fn(i32, *_, i32, ...) -> i32),
61                    (link_name, abi, args),
62                )?;
63                let result = ecx.open(Some(&dirfd), &path_raw, &flag, varargs)?;
64                ecx.write_scalar(result, dest)?;
65            }
66            "pread64" => {
67                let [fd, buf, count, offset] = &ecx.check_shim_sig(
68                    shim_sig!(extern "C" fn(i32, *_, usize, libc::off64_t) -> isize),
69                    (link_name, abi, args),
70                )?;
71                let fd = ecx.read_scalar(fd)?.to_i32()?;
72                let buf = ecx.read_pointer(buf)?;
73                let count = ecx.read_target_usize(count)?;
74                let offset = ecx.read_scalar(offset)?.to_int(offset.layout.size)?;
75                ecx.read(fd, buf, count, Some(offset), dest)?;
76            }
77            "pwrite64" => {
78                let [fd, buf, n, offset] = &ecx.check_shim_sig(
79                    shim_sig!(extern "C" fn(i32, *_, usize, libc::off64_t) -> isize),
80                    (link_name, abi, args),
81                )?;
82                let fd = ecx.read_scalar(fd)?.to_i32()?;
83                let buf = ecx.read_pointer(buf)?;
84                let count = ecx.read_target_usize(n)?;
85                let offset = ecx.read_scalar(offset)?.to_int(offset.layout.size)?;
86                trace!("Called pwrite64({:?}, {:?}, {:?}, {:?})", fd, buf, count, offset);
87                ecx.write(fd, buf, count, Some(offset), dest)?;
88            }
89            "lseek64" => {
90                let [fd, offset, whence] = &ecx.check_shim_sig(
91                    shim_sig!(extern "C" fn(i32, libc::off64_t, i32) -> libc::off64_t),
92                    (link_name, abi, args),
93                )?;
94                let fd = ecx.read_scalar(fd)?.to_i32()?;
95                let offset = ecx.read_scalar(offset)?.to_int(offset.layout.size)?;
96                let whence = ecx.read_scalar(whence)?.to_i32()?;
97                ecx.lseek(fd, offset, whence, dest)?;
98            }
99            "posix_fallocate64" => {
100                let [fd, offset, len] = &ecx.check_shim_sig(
101                    shim_sig!(extern "C" fn(i32, libc::off64_t, libc::off64_t) -> i32),
102                    (link_name, abi, args),
103                )?;
104
105                let fd = ecx.read_scalar(fd)?.to_i32()?;
106                let offset = ecx.read_scalar(offset)?.to_i64()?;
107                let len = ecx.read_scalar(len)?.to_i64()?;
108
109                let result = ecx.posix_fallocate(fd, offset, len)?;
110                ecx.write_scalar(result, dest)?;
111            }
112            "fallocate" => {
113                let [fd, mode, offset, len] = &ecx.check_shim_sig(
114                    shim_sig!(extern "C" fn(i32, i32, libc::off_t, libc::off_t) -> i32),
115                    (link_name, abi, args),
116                )?;
117
118                let fd = ecx.read_scalar(fd)?.to_i32()?;
119                let mode = ecx.read_scalar(mode)?.to_i32()?;
120                // We don't support platforms which have libc::off_t bigger than 64 bits.
121                let offset =
122                    i64::try_from(ecx.read_scalar(offset)?.to_int(offset.layout.size)?).unwrap();
123                let len = i64::try_from(ecx.read_scalar(len)?.to_int(len.layout.size)?).unwrap();
124
125                let result = ecx.linux_fallocate(fd, mode, offset, len)?;
126                ecx.write_scalar(result, dest)?;
127            }
128            "fallocate64" => {
129                let [fd, mode, offset, len] = &ecx.check_shim_sig(
130                    shim_sig!(extern "C" fn(i32, i32, libc::off64_t, libc::off64_t) -> i32),
131                    (link_name, abi, args),
132                )?;
133
134                let fd = ecx.read_scalar(fd)?.to_i32()?;
135                let mode = ecx.read_scalar(mode)?.to_i32()?;
136                let offset = ecx.read_scalar(offset)?.to_i64()?;
137                let len = ecx.read_scalar(len)?.to_i64()?;
138
139                let result = ecx.linux_fallocate(fd, mode, offset, len)?;
140                ecx.write_scalar(result, dest)?;
141            }
142            "ftruncate64" => {
143                let [fd, length] = &ecx.check_shim_sig(
144                    shim_sig!(extern "C" fn(i32, libc::off64_t) -> i32),
145                    (link_name, abi, args),
146                )?;
147                let fd = ecx.read_scalar(fd)?.to_i32()?;
148                let length = ecx.read_scalar(length)?.to_int(length.layout.size)?;
149                let result = ecx.ftruncate64(fd, length)?;
150                ecx.write_scalar(result, dest)?;
151            }
152            "sync_file_range" => {
153                let [fd, offset, nbytes, flags] = &ecx.check_shim_sig(
154                    shim_sig!(extern "C" fn(i32, libc::off64_t, libc::off64_t, u32) -> i32),
155                    (link_name, abi, args),
156                )?;
157                let result = ecx.linux_sync_file_range(fd, offset, nbytes, flags)?;
158                ecx.write_scalar(result, dest)?;
159            }
160            "copy_file_range" => {
161                let [fd_in, off_in, fd_out, off_out, len, flags] = &ecx.check_shim_sig(
162                    shim_sig!(extern "C" fn(i32, *libc::off_t, i32, *libc::off_t, libc::size_t, u32) -> libc::ssize_t),
163                    (link_name, abi, args),
164                )?;
165                ecx.linux_copy_file_range(fd_in, off_in, fd_out, off_out, len, flags, dest)?;
166            }
167            "statx" => {
168                let [dirfd, pathname, flags, mask, statxbuf] = &ecx.check_shim_sig(
169                    shim_sig!(extern "C" fn(i32, *_, i32, u32, *libc::statx) -> i32),
170                    (link_name, abi, args),
171                )?;
172                let result = ecx.linux_statx(dirfd, pathname, flags, mask, statxbuf)?;
173                ecx.write_scalar(result, dest)?;
174            }
175            "fstat64" => {
176                let [fd, buf] = &ecx.check_shim_sig(
177                    shim_sig!(extern "C" fn(i32, *libc::stat64) -> i32),
178                    (link_name, abi, args),
179                )?;
180                let result = ecx.fstat(fd, buf)?;
181                ecx.write_scalar(result, dest)?;
182            }
183            "readdir64" => {
184                let [dirp] = &ecx
185                    .check_shim_sig(shim_sig!(extern "C" fn(*_) -> *_), (link_name, abi, args))?;
186                ecx.readdir(dirp, dest, "dirent64")?;
187            }
188            "__futimens64" if ecx.is_dyn_sym("__futimens64") => {
189                // libc has no separate time64 type; its timespec uses 32-bit time_t here.
190                // (See https://github.com/rust-lang/libc/issues/3223.)
191                let [fd, times] = &ecx.check_shim_sig(
192                    shim_sig!(extern "C" fn(i32, *std::sys::pal::time::__timespec64) -> i32),
193                    (link_name, abi, args),
194                )?;
195                let result = ecx.futimens(fd, times)?;
196                ecx.write_scalar(result, dest)?;
197            }
198
199            // epoll, eventfd
200            "epoll_create1" => {
201                let [flag] = &ecx
202                    .check_shim_sig(shim_sig!(extern "C" fn(i32) -> i32), (link_name, abi, args))?;
203                let result = ecx.epoll_create1(flag)?;
204                ecx.write_scalar(result, dest)?;
205            }
206            "epoll_ctl" => {
207                let [epfd, op, fd, event] = &ecx.check_shim_sig(
208                    shim_sig!(extern "C" fn(i32, i32, i32, *libc::epoll_event) -> i32),
209                    (link_name, abi, args),
210                )?;
211                let result = ecx.epoll_ctl(epfd, op, fd, event)?;
212                ecx.write_scalar(result, dest)?;
213            }
214            "epoll_wait" => {
215                let [epfd, events, maxevents, timeout] = &ecx.check_shim_sig(
216                    shim_sig!(extern "C" fn(i32, *libc::epoll_event, i32, i32) -> i32),
217                    (link_name, abi, args),
218                )?;
219                ecx.epoll_wait(epfd, events, maxevents, timeout, dest)?;
220            }
221            "eventfd" => {
222                let [val, flag] = &ecx.check_shim_sig(
223                    shim_sig!(extern "C" fn(u32, i32) -> i32),
224                    (link_name, abi, args),
225                )?;
226                let result = ecx.eventfd(val, flag)?;
227                ecx.write_scalar(result, dest)?;
228            }
229
230            // Threading
231            "pthread_setname_np" => {
232                let [thread, name] = &ecx.check_shim_sig(
233                    shim_sig!(extern "C" fn(libc::pthread_t, *_) -> i32),
234                    (link_name, abi, args),
235                )?;
236                let res = match ecx.pthread_setname_np(
237                    ecx.read_scalar(thread)?,
238                    ecx.read_scalar(name)?,
239                    TASK_COMM_LEN,
240                    /* truncate */ false,
241                )? {
242                    ThreadNameResult::Ok => Scalar::from_u32(0),
243                    ThreadNameResult::NameTooLong => ecx.eval_libc("ERANGE"),
244                };
245                ecx.write_scalar(res, dest)?;
246            }
247            "pthread_getname_np" => {
248                let [thread, name, len] = &ecx.check_shim_sig(
249                    shim_sig!(extern "C" fn(libc::pthread_t, *_, usize) -> i32),
250                    (link_name, abi, args),
251                )?;
252                // The function's behavior isn't portable between platforms.
253                // In case of glibc, the length of the output buffer must
254                // be not shorter than TASK_COMM_LEN.
255                let len = ecx.read_scalar(len)?;
256                let res = if len.to_target_usize(ecx)? >= TASK_COMM_LEN {
257                    match ecx.pthread_getname_np(
258                        ecx.read_scalar(thread)?,
259                        ecx.read_scalar(name)?,
260                        len,
261                        /* truncate*/ false,
262                    )? {
263                        ThreadNameResult::Ok => Scalar::from_u32(0),
264                        ThreadNameResult::NameTooLong => unreachable!(),
265                    }
266                } else {
267                    ecx.eval_libc("ERANGE")
268                };
269                ecx.write_scalar(res, dest)?;
270            }
271            "gettid" => {
272                let [] = &ecx.check_shim_sig(
273                    shim_sig!(extern "C" fn() -> libc::pid_t),
274                    (link_name, abi, args),
275                )?;
276                let result = ecx.unix_gettid(link_name.as_str())?;
277                ecx.write_scalar(result, dest)?;
278            }
279            "prctl" => prctl(ecx, link_name, abi, args, dest)?,
280            "__cxa_thread_atexit_impl"
281                if ecx.tcx.sess.target.env == rustc_target::spec::Env::Gnu =>
282            {
283                let [dtor, arg, dso] = &ecx.check_shim_sig(
284                    shim_sig!(extern "C" fn(*_, *_, *_) -> i32),
285                    (link_name, abi, args),
286                )?;
287                let dtor = ecx.read_pointer(dtor)?;
288                let arg = ecx.read_scalar(arg)?;
289                let dso = ecx.read_pointer(dso)?;
290
291                let dso_handle = Symbol::intern("__dso_handle");
292                if dso.addr() != Pointer::from(ecx.machine.extern_statics[&dso_handle]).addr() {
293                    throw_unsup_format!(
294                        "__cxa_thread_atexit_impl: a DSO value other than the address of `__dso_handle` is not supported"
295                    );
296                }
297
298                let dtor = ecx.get_ptr_fn(dtor)?.as_instance()?;
299                ecx.machine.tls.add_thread_atexit(
300                    ecx.active_thread(),
301                    dtor,
302                    arg,
303                    ecx.machine.current_user_relevant_span(),
304                )?;
305
306                ecx.write_int(0, dest)?;
307            }
308
309            // Dynamically invoked syscalls
310            "syscall" => {
311                syscall(ecx, link_name, abi, args, dest)?;
312            }
313
314            // Miscellaneous
315            "mmap64" => {
316                let [addr, length, prot, flags, fd, offset] = &ecx.check_shim_sig(
317                    shim_sig!(extern "C" fn(*_, usize, i32, i32, i32, libc::off64_t) -> *_),
318                    (link_name, abi, args),
319                )?;
320                let offset = ecx.read_scalar(offset)?.to_i64()?;
321                let ptr = ecx.mmap(addr, length, prot, flags, fd, offset.into())?;
322                ecx.write_scalar(ptr, dest)?;
323            }
324            "mremap" => {
325                let ([old_address, old_size, new_size, flags], _) = ecx.check_shim_sig_variadic(
326                    shim_sig!(extern "C" fn(*_, usize, usize, i32, ...) -> *_),
327                    (link_name, abi, args),
328                )?;
329                let ptr = ecx.mremap(&old_address, &old_size, &new_size, &flags)?;
330                ecx.write_scalar(ptr, dest)?;
331            }
332            "__xpg_strerror_r" => {
333                let [errnum, buf, buflen] = &ecx.check_shim_sig(
334                    shim_sig!(extern "C" fn(i32, *_, usize) -> i32),
335                    (link_name, abi, args),
336                )?;
337                let result = ecx.strerror_r(errnum, buf, buflen)?;
338                ecx.write_scalar(result, dest)?;
339            }
340            "__errno_location" => {
341                let [] =
342                    &ecx.check_shim_sig(shim_sig!(extern "C" fn() -> *_), (link_name, abi, args))?;
343                let errno_place = ecx.last_error_place()?;
344                ecx.write_scalar(errno_place.to_ref(ecx).to_scalar(), dest)?;
345            }
346            "__libc_current_sigrtmin" => {
347                let [] =
348                    &ecx.check_shim_sig(shim_sig!(extern "C" fn() -> i32), (link_name, abi, args))?;
349
350                ecx.write_int(SIGRTMIN, dest)?;
351            }
352            "__libc_current_sigrtmax" => {
353                let [] =
354                    &ecx.check_shim_sig(shim_sig!(extern "C" fn() -> i32), (link_name, abi, args))?;
355
356                ecx.write_int(SIGRTMAX, dest)?;
357            }
358
359            // Incomplete shims that we "stub out" just to get pre-main initialization code to work.
360            // These shims are enabled only when the caller is in the standard library.
361            "pthread_getattr_np" if ecx.frame_in_std() => {
362                let [_thread, _attr] = &ecx.check_shim_sig(
363                    shim_sig!(extern "C" fn(libc::pthread_t, *_) -> i32),
364                    (link_name, abi, args),
365                )?;
366                ecx.write_int(0, dest)?;
367            }
368            "gnu_get_libc_version"
369                if ecx.frame_in_std()
370                    && ecx.tcx.sess.target.env == rustc_target::spec::Env::Gnu =>
371            {
372                let [] =
373                    &ecx.check_shim_sig(shim_sig!(extern "C" fn() -> *_), (link_name, abi, args))?;
374                // We have to be at least version 2.26 so that std does not call `res_init`.
375                // This returns a C string, so we have to add a null terminator.
376                let version = "2.26\0";
377                let version = ecx.allocate_str_dedup(version)?;
378                ecx.write_pointer(version.ptr(), dest)?;
379            }
380            "sendfile64" if ecx.frame_in_std() => {
381                let [_out_fd, _in_fd, _offset, _count] = &ecx.check_shim_sig(
382                    shim_sig!(extern "C" fn(i32, i32, *libc::off64_t, usize) -> isize),
383                    (link_name, abi, args),
384                )?;
385                // Force std to use a fallback path.
386                ecx.set_errno_and_return_neg1(LibcError("ENOSYS"), dest)?;
387            }
388            "splice" if ecx.frame_in_std() => {
389                let [_src_fd, _src_offset, _dst_fd, _dst_offset, _len, _flags] = &ecx.check_shim_sig(
390                    shim_sig!(extern "C" fn(i32, *libc::off_t, i32, *libc::off_t, usize, u32) -> isize),
391                    (link_name, abi, args),
392                )?;
393                // Force std to use a fallback path.
394                ecx.set_errno_and_return_neg1(LibcError("ENOSYS"), dest)?;
395            }
396
397            _ => return interp_ok(EmulateItemResult::NotSupported),
398        };
399
400        interp_ok(EmulateItemResult::NeedsReturn)
401    }
402}