diff --git a/src/helpers.rs b/src/helpers.rs index ca8dbdac12..f56a5ea6ec 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -990,6 +990,23 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { check_arg_count(args) } + /// Check shim for variadic function. + /// Returns a tuple that consisting of an array of fixed args, and a slice of varargs. + fn check_shim_variadic<'a, const N: usize>( + &mut self, + abi: &FnAbi<'tcx, Ty<'tcx>>, + exp_abi: Conv, + link_name: Symbol, + shim_name: &'a str, + args: &'a [OpTy<'tcx>], + ) -> InterpResult<'tcx, (&'a [OpTy<'tcx>; N], &'a [OpTy<'tcx>])> + where + &'a [OpTy<'tcx>; N]: TryFrom<&'a [OpTy<'tcx>]>, + { + self.check_abi_and_shim_symbol_clash(abi, exp_abi, link_name)?; + check_vargarg_fixed_arg_count(shim_name, abi, args) + } + /// Mark a machine allocation that was just created as immutable. fn mark_immutable(&mut self, mplace: &MPlaceTy<'tcx>) { let this = self.eval_context_mut(); @@ -1183,7 +1200,8 @@ where throw_ub_format!("incorrect number of arguments: got {}, expected {}", args.len(), N) } -/// Check that the number of args is at least the minumim what we expect. +/// Check that the number of args is at least the minimum what we expect. +/// FIXME: Remove this function, use varargs and `check_min_vararg_count` instead. pub fn check_min_arg_count<'a, 'tcx, const N: usize>( name: &'a str, args: &'a [OpTy<'tcx>], @@ -1198,6 +1216,49 @@ pub fn check_min_arg_count<'a, 'tcx, const N: usize>( ) } +/// Check that the number of varargs is at least the minimum what we expect. +/// Fixed args should not be included. +/// Use `check_vararg_fixed_arg_count` to extract the varargs slice from full function arguments. +pub fn check_min_vararg_count<'a, 'tcx, const N: usize>( + name: &'a str, + args: &'a [OpTy<'tcx>], +) -> InterpResult<'tcx, &'a [OpTy<'tcx>; N]> { + if let Some((ops, _)) = args.split_first_chunk() { + return interp_ok(ops); + } + throw_ub_format!( + "not enough variadic arguments for `{name}`: got {}, expected at least {}", + args.len(), + N + ) +} + +/// Check the number of fixed args of a vararg function. +/// Returns a tuple that consisting of an array of fixed args, and a slice of varargs. +fn check_vargarg_fixed_arg_count<'a, 'tcx, const N: usize>( + name: &'a str, + abi: &FnAbi<'tcx, Ty<'tcx>>, + args: &'a [OpTy<'tcx>], +) -> InterpResult<'tcx, (&'a [OpTy<'tcx>; N], &'a [OpTy<'tcx>])> { + if !abi.c_variadic { + panic!("This should not be used on non-vararg function"); + } + if abi.fixed_count != u32::try_from(N).unwrap() { + throw_ub_format!( + "incorrect number of fixed arguments for variadic function `{name}`: got {}, expected {N}", + abi.fixed_count + ) + } + if let Some(args) = args.split_first_chunk() { + return interp_ok(args); + } + throw_ub_format!( + "incorrect number of arguments for `{name}`: got {}, expected at least {}", + args.len(), + N + ) +} + pub fn isolation_abort_error<'tcx>(name: &str) -> InterpResult<'tcx> { throw_machine_stop!(TerminationInfo::UnsupportedInIsolation(format!( "{name} not available when isolation is enabled", diff --git a/src/shims/unix/android/thread.rs b/src/shims/unix/android/thread.rs index 8d5d4a52b6..73fbba2c04 100644 --- a/src/shims/unix/android/thread.rs +++ b/src/shims/unix/android/thread.rs @@ -3,7 +3,7 @@ use rustc_middle::ty::Ty; use rustc_span::Symbol; use rustc_target::callconv::{Conv, FnAbi}; -use crate::helpers::check_min_arg_count; +use crate::helpers::check_min_vararg_count; use crate::shims::unix::thread::{EvalContextExt as _, ThreadNameResult}; use crate::*; @@ -16,18 +16,15 @@ pub fn prctl<'tcx>( args: &[OpTy<'tcx>], dest: &MPlaceTy<'tcx>, ) -> InterpResult<'tcx> { - // We do not use `check_shim` here because `prctl` is variadic. The argument - // count is checked bellow. - ecx.check_abi_and_shim_symbol_clash(abi, Conv::C, link_name)?; + let ([op], varargs) = ecx.check_shim_variadic(abi, Conv::C, link_name, "prctl", args)?; // FIXME: Use constants once https://github.com/rust-lang/libc/pull/3941 backported to the 0.2 branch. let pr_set_name = 15; let pr_get_name = 16; - let [op] = check_min_arg_count("prctl", args)?; let res = match ecx.read_scalar(op)?.to_i32()? { op if op == pr_set_name => { - let [_, name] = check_min_arg_count("prctl(PR_SET_NAME, ...)", args)?; + let [name] = check_min_vararg_count("prctl(PR_SET_NAME, ...)", varargs)?; let name = ecx.read_scalar(name)?; let thread = ecx.pthread_self()?; // The Linux kernel silently truncates long names. @@ -38,7 +35,7 @@ pub fn prctl<'tcx>( Scalar::from_u32(0) } op if op == pr_get_name => { - let [_, name] = check_min_arg_count("prctl(PR_GET_NAME, ...)", args)?; + let [name] = check_min_vararg_count("prctl(PR_GET_NAME, ...)", varargs)?; let name = ecx.read_scalar(name)?; let thread = ecx.pthread_self()?; let len = Scalar::from_target_usize(TASK_COMM_LEN as u64, ecx); diff --git a/src/shims/unix/fd.rs b/src/shims/unix/fd.rs index 0b59490308..e7b11e59f8 100644 --- a/src/shims/unix/fd.rs +++ b/src/shims/unix/fd.rs @@ -6,7 +6,7 @@ use std::io::ErrorKind; use rustc_abi::Size; -use crate::helpers::check_min_arg_count; +use crate::helpers::check_min_vararg_count; use crate::shims::files::FileDescription; use crate::shims::unix::linux_like::epoll::EpollReadyEvents; use crate::shims::unix::*; @@ -127,11 +127,14 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?)) } - fn fcntl(&mut self, args: &[OpTy<'tcx>]) -> InterpResult<'tcx, Scalar> { + fn fcntl( + &mut self, + fd_num: &OpTy<'tcx>, + cmd: &OpTy<'tcx>, + varargs: &[OpTy<'tcx>], + ) -> InterpResult<'tcx, Scalar> { let this = self.eval_context_mut(); - let [fd_num, cmd] = check_min_arg_count("fcntl", args)?; - let fd_num = this.read_scalar(fd_num)?.to_i32()?; let cmd = this.read_scalar(cmd)?.to_i32()?; @@ -163,7 +166,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { "fcntl(fd, F_DUPFD_CLOEXEC, ...)" }; - let [_, _, start] = check_min_arg_count(cmd_name, args)?; + let [start] = check_min_vararg_count(cmd_name, varargs)?; let start = this.read_scalar(start)?.to_i32()?; if let Some(fd) = this.machine.fds.get(fd_num) { diff --git a/src/shims/unix/foreign_items.rs b/src/shims/unix/foreign_items.rs index 3353cf2cc5..9713157169 100644 --- a/src/shims/unix/foreign_items.rs +++ b/src/shims/unix/foreign_items.rs @@ -205,10 +205,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(result, dest)?; } "fcntl" => { - // `fcntl` is variadic. The argument count is checked based on the first argument - // in `this.fcntl()`, so we do not use `check_shim` here. - this.check_abi_and_shim_symbol_clash(abi, Conv::C, link_name)?; - let result = this.fcntl(args)?; + let ([fd_num, cmd], varargs) = + this.check_shim_variadic(abi, Conv::C, link_name, "fcntl", args)?; + let result = this.fcntl(fd_num, cmd, varargs)?; this.write_scalar(result, dest)?; } "dup" => { @@ -236,8 +235,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { "open" | "open64" => { // `open` is variadic, the third argument is only present when the second argument // has O_CREAT (or on linux O_TMPFILE, but miri doesn't support that) set - this.check_abi_and_shim_symbol_clash(abi, Conv::C, link_name)?; - let result = this.open(args)?; + let ([path_raw, flag], varargs) = + this.check_shim_variadic(abi, Conv::C, link_name, "open/open64", args)?; + let result = this.open(path_raw, flag, varargs)?; this.write_scalar(result, dest)?; } "unlink" => { diff --git a/src/shims/unix/fs.rs b/src/shims/unix/fs.rs index 25594b7803..dd2a87b0cf 100644 --- a/src/shims/unix/fs.rs +++ b/src/shims/unix/fs.rs @@ -13,7 +13,7 @@ use rustc_abi::Size; use rustc_data_structures::fx::FxHashMap; use self::shims::time::system_time_to_duration; -use crate::helpers::check_min_arg_count; +use crate::helpers::check_min_vararg_count; use crate::shims::files::{EvalContextExt as _, FileDescription, FileDescriptionRef}; use crate::shims::os_str::bytes_to_os_str; use crate::shims::unix::fd::{FlockOp, UnixFileDescription}; @@ -452,9 +452,12 @@ fn maybe_sync_file( impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {} pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { - fn open(&mut self, args: &[OpTy<'tcx>]) -> InterpResult<'tcx, Scalar> { - let [path_raw, flag] = check_min_arg_count("open", args)?; - + fn open( + &mut self, + path_raw: &OpTy<'tcx>, + flag: &OpTy<'tcx>, + varargs: &[OpTy<'tcx>], + ) -> InterpResult<'tcx, Scalar> { let this = self.eval_context_mut(); let path_raw = this.read_pointer(path_raw)?; @@ -507,7 +510,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Get the mode. On macOS, the argument type `mode_t` is actually `u16`, but // C integer promotion rules mean that on the ABI level, it gets passed as `u32` // (see https://github.com/rust-lang/rust/issues/71915). - let [_, _, mode] = check_min_arg_count("open(pathname, O_CREAT, ...)", args)?; + let [mode] = check_min_vararg_count("open(pathname, O_CREAT, ...)", varargs)?; let mode = this.read_scalar(mode)?.to_u32()?; #[cfg(unix)] diff --git a/src/shims/unix/linux_like/sync.rs b/src/shims/unix/linux_like/sync.rs index 51124fb2a0..8369811336 100644 --- a/src/shims/unix/linux_like/sync.rs +++ b/src/shims/unix/linux_like/sync.rs @@ -1,5 +1,5 @@ use crate::concurrency::sync::FutexRef; -use crate::helpers::check_min_arg_count; +use crate::helpers::check_min_vararg_count; use crate::*; struct LinuxFutex { @@ -10,7 +10,7 @@ struct LinuxFutex { /// `args` is the arguments *including* the syscall number. pub fn futex<'tcx>( ecx: &mut MiriInterpCx<'tcx>, - args: &[OpTy<'tcx>], + varargs: &[OpTy<'tcx>], dest: &MPlaceTy<'tcx>, ) -> InterpResult<'tcx> { // The amount of arguments used depends on the type of futex operation. @@ -21,7 +21,7 @@ pub fn futex<'tcx>( // may or may not be left out from the `syscall()` call. // Therefore we don't use `check_arg_count` here, but only check for the // number of arguments to fall within a range. - let [_, addr, op, val] = check_min_arg_count("`syscall(SYS_futex, ...)`", args)?; + let [addr, op, val] = check_min_vararg_count("`syscall(SYS_futex, ...)`", varargs)?; // The first three arguments (after the syscall number itself) are the same to all futex operations: // (int *addr, int op, int val). @@ -55,14 +55,16 @@ pub fn futex<'tcx>( let wait_bitset = op & !futex_realtime == futex_wait_bitset; let (timeout, bitset) = if wait_bitset { - let [_, _, _, _, timeout, uaddr2, bitset] = - check_min_arg_count("`syscall(SYS_futex, FUTEX_WAIT_BITSET, ...)`", args)?; + let [_, _, _, timeout, uaddr2, bitset] = check_min_vararg_count( + "`syscall(SYS_futex, FUTEX_WAIT_BITSET, ...)`", + varargs, + )?; let _timeout = ecx.read_pointer(timeout)?; let _uaddr2 = ecx.read_pointer(uaddr2)?; (timeout, ecx.read_scalar(bitset)?.to_u32()?) } else { - let [_, _, _, _, timeout] = - check_min_arg_count("`syscall(SYS_futex, FUTEX_WAIT, ...)`", args)?; + let [_, _, _, timeout] = + check_min_vararg_count("`syscall(SYS_futex, FUTEX_WAIT, ...)`", varargs)?; (timeout, u32::MAX) }; @@ -190,8 +192,10 @@ pub fn futex<'tcx>( let futex_ref = futex_ref.futex.clone(); let bitset = if op == futex_wake_bitset { - let [_, _, _, _, timeout, uaddr2, bitset] = - check_min_arg_count("`syscall(SYS_futex, FUTEX_WAKE_BITSET, ...)`", args)?; + let [_, _, _, timeout, uaddr2, bitset] = check_min_vararg_count( + "`syscall(SYS_futex, FUTEX_WAKE_BITSET, ...)`", + varargs, + )?; let _timeout = ecx.read_pointer(timeout)?; let _uaddr2 = ecx.read_pointer(uaddr2)?; ecx.read_scalar(bitset)?.to_u32()? diff --git a/src/shims/unix/linux_like/syscall.rs b/src/shims/unix/linux_like/syscall.rs index 5fb262e176..d1d5f0e5fa 100644 --- a/src/shims/unix/linux_like/syscall.rs +++ b/src/shims/unix/linux_like/syscall.rs @@ -2,7 +2,7 @@ use rustc_middle::ty::Ty; use rustc_span::Symbol; use rustc_target::callconv::{Conv, FnAbi}; -use crate::helpers::check_min_arg_count; +use crate::helpers::check_min_vararg_count; use crate::shims::unix::linux_like::eventfd::EvalContextExt as _; use crate::shims::unix::linux_like::sync::futex; use crate::*; @@ -14,9 +14,7 @@ pub fn syscall<'tcx>( args: &[OpTy<'tcx>], dest: &MPlaceTy<'tcx>, ) -> InterpResult<'tcx> { - // We do not use `check_shim` here because `syscall` is variadic. The argument - // count is checked bellow. - ecx.check_abi_and_shim_symbol_clash(abi, Conv::C, link_name)?; + let ([op], varargs) = ecx.check_shim_variadic(abi, Conv::C, link_name, "syscall", args)?; // The syscall variadic function is legal to call with more arguments than needed, // extra arguments are simply ignored. The important check is that when we use an // argument, we have to also check all arguments *before* it to ensure that they @@ -26,14 +24,13 @@ pub fn syscall<'tcx>( let sys_futex = ecx.eval_libc("SYS_futex").to_target_usize(ecx)?; let sys_eventfd2 = ecx.eval_libc("SYS_eventfd2").to_target_usize(ecx)?; - let [op] = check_min_arg_count("syscall", args)?; match ecx.read_target_usize(op)? { // `libc::syscall(NR_GETRANDOM, buf.as_mut_ptr(), buf.len(), GRND_NONBLOCK)` // is called if a `HashMap` is created the regular way (e.g. HashMap). num if num == sys_getrandom => { // Used by getrandom 0.1 // The first argument is the syscall id, so skip over it. - let [_, ptr, len, flags] = check_min_arg_count("syscall(SYS_getrandom, ...)", args)?; + let [ptr, len, flags] = check_min_vararg_count("syscall(SYS_getrandom, ...)", varargs)?; let ptr = ecx.read_pointer(ptr)?; let len = ecx.read_target_usize(len)?; @@ -47,10 +44,10 @@ pub fn syscall<'tcx>( } // `futex` is used by some synchronization primitives. num if num == sys_futex => { - futex(ecx, args, dest)?; + futex(ecx, varargs, dest)?; } num if num == sys_eventfd2 => { - let [_, initval, flags] = check_min_arg_count("syscall(SYS_evetfd2, ...)", args)?; + let [initval, flags] = check_min_vararg_count("syscall(SYS_evetfd2, ...)", varargs)?; let result = ecx.eventfd(initval, flags)?; ecx.write_int(result.to_i32()?, dest)?; diff --git a/src/shims/unix/macos/foreign_items.rs b/src/shims/unix/macos/foreign_items.rs index 2afe0b5cd3..a4e8091c70 100644 --- a/src/shims/unix/macos/foreign_items.rs +++ b/src/shims/unix/macos/foreign_items.rs @@ -3,7 +3,6 @@ use rustc_span::Symbol; use rustc_target::callconv::{Conv, FnAbi}; use super::sync::EvalContextExt as _; -use crate::helpers::check_min_arg_count; use crate::shims::unix::*; use crate::*; @@ -69,10 +68,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(result, dest)?; } "ioctl" => { - // `ioctl` is variadic. The argument count is checked based on the first argument - // in `this.ioctl()`, so we do not use `check_shim` here. - this.check_abi_and_shim_symbol_clash(abi, Conv::C, link_name)?; - let result = this.ioctl(args)?; + let ([fd_num, cmd], varargs) = + this.check_shim_variadic(abi, Conv::C, link_name, "ioctl", args)?; + let result = this.ioctl(fd_num, cmd, varargs)?; this.write_scalar(result, dest)?; } @@ -243,12 +241,16 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { interp_ok(EmulateItemResult::NeedsReturn) } - fn ioctl(&mut self, args: &[OpTy<'tcx>]) -> InterpResult<'tcx, Scalar> { + fn ioctl( + &mut self, + fd_num: &OpTy<'tcx>, + cmd: &OpTy<'tcx>, + _varargs: &[OpTy<'tcx>], + ) -> InterpResult<'tcx, Scalar> { let this = self.eval_context_mut(); let fioclex = this.eval_libc_u64("FIOCLEX"); - let [fd_num, cmd] = check_min_arg_count("ioctl", args)?; let fd_num = this.read_scalar(fd_num)?.to_i32()?; let cmd = this.read_scalar(cmd)?.to_u64()?; diff --git a/tests/fail-dep/libc/fs/unix_open_missing_required_mode.rs b/tests/fail-dep/libc/fs/unix_open_missing_required_mode.rs index 4b6f344a78..457f32e554 100644 --- a/tests/fail-dep/libc/fs/unix_open_missing_required_mode.rs +++ b/tests/fail-dep/libc/fs/unix_open_missing_required_mode.rs @@ -8,5 +8,5 @@ fn main() { fn test_file_open_missing_needed_mode() { let name = b"missing_arg.txt\0"; let name_ptr = name.as_ptr().cast::(); - let _fd = unsafe { libc::open(name_ptr, libc::O_CREAT) }; //~ ERROR: Undefined Behavior: incorrect number of arguments for `open(pathname, O_CREAT, ...)`: got 2, expected at least 3 + let _fd = unsafe { libc::open(name_ptr, libc::O_CREAT) }; //~ ERROR: Undefined Behavior: not enough variadic arguments } diff --git a/tests/fail-dep/libc/fs/unix_open_missing_required_mode.stderr b/tests/fail-dep/libc/fs/unix_open_missing_required_mode.stderr index ca9e3c6c4b..f48b75460d 100644 --- a/tests/fail-dep/libc/fs/unix_open_missing_required_mode.stderr +++ b/tests/fail-dep/libc/fs/unix_open_missing_required_mode.stderr @@ -1,8 +1,8 @@ -error: Undefined Behavior: incorrect number of arguments for `open(pathname, O_CREAT, ...)`: got 2, expected at least 3 +error: Undefined Behavior: not enough variadic arguments for `open(pathname, O_CREAT, ...)`: got 0, expected at least 1 --> tests/fail-dep/libc/fs/unix_open_missing_required_mode.rs:LL:CC | -LL | ... { libc::open(name_ptr, libc::O_CREAT) }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ incorrect number of arguments for `open(pathname, O_CREAT, ...)`: got 2, expected at least 3 +LL | let _fd = unsafe { libc::open(name_ptr, libc::O_CREAT) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not enough variadic arguments for `open(pathname, O_CREAT, ...)`: got 0, expected at least 1 | = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information diff --git a/tests/fail-dep/libc/wrong_fixed_arg_count.rs b/tests/fail-dep/libc/wrong_fixed_arg_count.rs new file mode 100644 index 0000000000..7e400cd87d --- /dev/null +++ b/tests/fail-dep/libc/wrong_fixed_arg_count.rs @@ -0,0 +1,14 @@ +//@ignore-target: windows # File handling is not implemented yet +//@compile-flags: -Zmiri-disable-isolation +use std::ffi::{CString, OsStr}; +use std::os::unix::ffi::OsStrExt; + +extern "C" { + fn open(path: *const libc::c_char, ...) -> libc::c_int; +} + +fn main() { + let c_path = CString::new(OsStr::new("./text").as_bytes()).expect("CString::new failed"); + let _fd = unsafe { open(c_path.as_ptr(), libc::O_RDWR) }; + //~^ ERROR: incorrect number of fixed arguments for variadic function +} diff --git a/tests/fail-dep/libc/wrong_fixed_arg_count.stderr b/tests/fail-dep/libc/wrong_fixed_arg_count.stderr new file mode 100644 index 0000000000..192841fbf7 --- /dev/null +++ b/tests/fail-dep/libc/wrong_fixed_arg_count.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: incorrect number of fixed arguments for variadic function `open/open64`: got 1, expected 2 + --> tests/fail-dep/libc/wrong_fixed_arg_count.rs:LL:CC + | +LL | let _fd = unsafe { open(c_path.as_ptr(), libc::O_RDWR) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ incorrect number of fixed arguments for variadic function `open/open64`: got 1, expected 2 + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at tests/fail-dep/libc/wrong_fixed_arg_count.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error +