Skip to content

Commit b1d927e

Browse files
authored
Merge pull request rust-lang#4122 from tiif/fnabi
Check fixed args number for variadic function
2 parents 79fcb1d + 1e6081f commit b1d927e

15 files changed

+184
-53
lines changed

src/tools/miri/src/helpers.rs

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1002,6 +1002,22 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
10021002
check_arg_count(args)
10031003
}
10041004

1005+
/// Check shim for variadic function.
1006+
/// Returns a tuple that consisting of an array of fixed args, and a slice of varargs.
1007+
fn check_shim_variadic<'a, const N: usize>(
1008+
&mut self,
1009+
abi: &FnAbi<'tcx, Ty<'tcx>>,
1010+
exp_abi: Conv,
1011+
link_name: Symbol,
1012+
args: &'a [OpTy<'tcx>],
1013+
) -> InterpResult<'tcx, (&'a [OpTy<'tcx>; N], &'a [OpTy<'tcx>])>
1014+
where
1015+
&'a [OpTy<'tcx>; N]: TryFrom<&'a [OpTy<'tcx>]>,
1016+
{
1017+
self.check_abi_and_shim_symbol_clash(abi, exp_abi, link_name)?;
1018+
check_vargarg_fixed_arg_count(link_name, abi, args)
1019+
}
1020+
10051021
/// Mark a machine allocation that was just created as immutable.
10061022
fn mark_immutable(&mut self, mplace: &MPlaceTy<'tcx>) {
10071023
let this = self.eval_context_mut();
@@ -1195,7 +1211,8 @@ where
11951211
throw_ub_format!("incorrect number of arguments: got {}, expected {}", args.len(), N)
11961212
}
11971213

1198-
/// Check that the number of args is at least the minumim what we expect.
1214+
/// Check that the number of args is at least the minimum what we expect.
1215+
/// FIXME: Remove this function, use varargs and `check_min_vararg_count` instead.
11991216
pub fn check_min_arg_count<'a, 'tcx, const N: usize>(
12001217
name: &'a str,
12011218
args: &'a [OpTy<'tcx>],
@@ -1210,6 +1227,51 @@ pub fn check_min_arg_count<'a, 'tcx, const N: usize>(
12101227
)
12111228
}
12121229

1230+
/// Check that the number of varargs is at least the minimum what we expect.
1231+
/// Fixed args should not be included.
1232+
/// Use `check_vararg_fixed_arg_count` to extract the varargs slice from full function arguments.
1233+
pub fn check_min_vararg_count<'a, 'tcx, const N: usize>(
1234+
name: &'a str,
1235+
args: &'a [OpTy<'tcx>],
1236+
) -> InterpResult<'tcx, &'a [OpTy<'tcx>; N]> {
1237+
if let Some((ops, _)) = args.split_first_chunk() {
1238+
return interp_ok(ops);
1239+
}
1240+
throw_ub_format!(
1241+
"not enough variadic arguments for `{name}`: got {}, expected at least {}",
1242+
args.len(),
1243+
N
1244+
)
1245+
}
1246+
1247+
/// Check the number of fixed args of a vararg function.
1248+
/// Returns a tuple that consisting of an array of fixed args, and a slice of varargs.
1249+
fn check_vargarg_fixed_arg_count<'a, 'tcx, const N: usize>(
1250+
link_name: Symbol,
1251+
abi: &FnAbi<'tcx, Ty<'tcx>>,
1252+
args: &'a [OpTy<'tcx>],
1253+
) -> InterpResult<'tcx, (&'a [OpTy<'tcx>; N], &'a [OpTy<'tcx>])> {
1254+
if !abi.c_variadic {
1255+
throw_ub_format!("calling a variadic function with a non-variadic caller-side signature");
1256+
}
1257+
if abi.fixed_count != u32::try_from(N).unwrap() {
1258+
throw_ub_format!(
1259+
"incorrect number of fixed arguments for variadic function `{}`: got {}, expected {N}",
1260+
link_name.as_str(),
1261+
abi.fixed_count
1262+
)
1263+
}
1264+
if let Some(args) = args.split_first_chunk() {
1265+
return interp_ok(args);
1266+
}
1267+
throw_ub_format!(
1268+
"incorrect number of arguments for `{}`: got {}, expected at least {}",
1269+
link_name.as_str(),
1270+
args.len(),
1271+
N
1272+
)
1273+
}
1274+
12131275
pub fn isolation_abort_error<'tcx>(name: &str) -> InterpResult<'tcx> {
12141276
throw_machine_stop!(TerminationInfo::UnsupportedInIsolation(format!(
12151277
"{name} not available when isolation is enabled",

src/tools/miri/src/shims/unix/android/thread.rs

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use rustc_middle::ty::Ty;
33
use rustc_span::Symbol;
44
use rustc_target::callconv::{Conv, FnAbi};
55

6-
use crate::helpers::check_min_arg_count;
6+
use crate::helpers::check_min_vararg_count;
77
use crate::shims::unix::thread::{EvalContextExt as _, ThreadNameResult};
88
use crate::*;
99

@@ -16,18 +16,15 @@ pub fn prctl<'tcx>(
1616
args: &[OpTy<'tcx>],
1717
dest: &MPlaceTy<'tcx>,
1818
) -> InterpResult<'tcx> {
19-
// We do not use `check_shim` here because `prctl` is variadic. The argument
20-
// count is checked bellow.
21-
ecx.check_abi_and_shim_symbol_clash(abi, Conv::C, link_name)?;
19+
let ([op], varargs) = ecx.check_shim_variadic(abi, Conv::C, link_name, args)?;
2220

2321
// FIXME: Use constants once https://github.com/rust-lang/libc/pull/3941 backported to the 0.2 branch.
2422
let pr_set_name = 15;
2523
let pr_get_name = 16;
2624

27-
let [op] = check_min_arg_count("prctl", args)?;
2825
let res = match ecx.read_scalar(op)?.to_i32()? {
2926
op if op == pr_set_name => {
30-
let [_, name] = check_min_arg_count("prctl(PR_SET_NAME, ...)", args)?;
27+
let [name] = check_min_vararg_count("prctl(PR_SET_NAME, ...)", varargs)?;
3128
let name = ecx.read_scalar(name)?;
3229
let thread = ecx.pthread_self()?;
3330
// The Linux kernel silently truncates long names.
@@ -38,7 +35,7 @@ pub fn prctl<'tcx>(
3835
Scalar::from_u32(0)
3936
}
4037
op if op == pr_get_name => {
41-
let [_, name] = check_min_arg_count("prctl(PR_GET_NAME, ...)", args)?;
38+
let [name] = check_min_vararg_count("prctl(PR_GET_NAME, ...)", varargs)?;
4239
let name = ecx.read_scalar(name)?;
4340
let thread = ecx.pthread_self()?;
4441
let len = Scalar::from_target_usize(TASK_COMM_LEN as u64, ecx);

src/tools/miri/src/shims/unix/fd.rs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use std::io::ErrorKind;
66

77
use rustc_abi::Size;
88

9-
use crate::helpers::check_min_arg_count;
9+
use crate::helpers::check_min_vararg_count;
1010
use crate::shims::files::FileDescription;
1111
use crate::shims::unix::linux_like::epoll::EpollReadyEvents;
1212
use crate::shims::unix::*;
@@ -127,11 +127,14 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
127127
interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
128128
}
129129

130-
fn fcntl(&mut self, args: &[OpTy<'tcx>]) -> InterpResult<'tcx, Scalar> {
130+
fn fcntl(
131+
&mut self,
132+
fd_num: &OpTy<'tcx>,
133+
cmd: &OpTy<'tcx>,
134+
varargs: &[OpTy<'tcx>],
135+
) -> InterpResult<'tcx, Scalar> {
131136
let this = self.eval_context_mut();
132137

133-
let [fd_num, cmd] = check_min_arg_count("fcntl", args)?;
134-
135138
let fd_num = this.read_scalar(fd_num)?.to_i32()?;
136139
let cmd = this.read_scalar(cmd)?.to_i32()?;
137140

@@ -163,7 +166,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
163166
"fcntl(fd, F_DUPFD_CLOEXEC, ...)"
164167
};
165168

166-
let [_, _, start] = check_min_arg_count(cmd_name, args)?;
169+
let [start] = check_min_vararg_count(cmd_name, varargs)?;
167170
let start = this.read_scalar(start)?.to_i32()?;
168171

169172
if let Some(fd) = this.machine.fds.get(fd_num) {

src/tools/miri/src/shims/unix/foreign_items.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -205,10 +205,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
205205
this.write_scalar(result, dest)?;
206206
}
207207
"fcntl" => {
208-
// `fcntl` is variadic. The argument count is checked based on the first argument
209-
// in `this.fcntl()`, so we do not use `check_shim` here.
210-
this.check_abi_and_shim_symbol_clash(abi, Conv::C, link_name)?;
211-
let result = this.fcntl(args)?;
208+
let ([fd_num, cmd], varargs) =
209+
this.check_shim_variadic(abi, Conv::C, link_name, args)?;
210+
let result = this.fcntl(fd_num, cmd, varargs)?;
212211
this.write_scalar(result, dest)?;
213212
}
214213
"dup" => {
@@ -236,8 +235,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
236235
"open" | "open64" => {
237236
// `open` is variadic, the third argument is only present when the second argument
238237
// has O_CREAT (or on linux O_TMPFILE, but miri doesn't support that) set
239-
this.check_abi_and_shim_symbol_clash(abi, Conv::C, link_name)?;
240-
let result = this.open(args)?;
238+
let ([path_raw, flag], varargs) =
239+
this.check_shim_variadic(abi, Conv::C, link_name, args)?;
240+
let result = this.open(path_raw, flag, varargs)?;
241241
this.write_scalar(result, dest)?;
242242
}
243243
"unlink" => {

src/tools/miri/src/shims/unix/fs.rs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use rustc_abi::Size;
1313
use rustc_data_structures::fx::FxHashMap;
1414

1515
use self::shims::time::system_time_to_duration;
16-
use crate::helpers::check_min_arg_count;
16+
use crate::helpers::check_min_vararg_count;
1717
use crate::shims::files::{EvalContextExt as _, FileDescription, FileDescriptionRef};
1818
use crate::shims::os_str::bytes_to_os_str;
1919
use crate::shims::unix::fd::{FlockOp, UnixFileDescription};
@@ -452,9 +452,12 @@ fn maybe_sync_file(
452452

453453
impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
454454
pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
455-
fn open(&mut self, args: &[OpTy<'tcx>]) -> InterpResult<'tcx, Scalar> {
456-
let [path_raw, flag] = check_min_arg_count("open", args)?;
457-
455+
fn open(
456+
&mut self,
457+
path_raw: &OpTy<'tcx>,
458+
flag: &OpTy<'tcx>,
459+
varargs: &[OpTy<'tcx>],
460+
) -> InterpResult<'tcx, Scalar> {
458461
let this = self.eval_context_mut();
459462

460463
let path_raw = this.read_pointer(path_raw)?;
@@ -507,7 +510,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
507510
// Get the mode. On macOS, the argument type `mode_t` is actually `u16`, but
508511
// C integer promotion rules mean that on the ABI level, it gets passed as `u32`
509512
// (see https://github.com/rust-lang/rust/issues/71915).
510-
let [_, _, mode] = check_min_arg_count("open(pathname, O_CREAT, ...)", args)?;
513+
let [mode] = check_min_vararg_count("open(pathname, O_CREAT, ...)", varargs)?;
511514
let mode = this.read_scalar(mode)?.to_u32()?;
512515

513516
#[cfg(unix)]

src/tools/miri/src/shims/unix/linux_like/sync.rs

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::concurrency::sync::FutexRef;
2-
use crate::helpers::check_min_arg_count;
2+
use crate::helpers::check_min_vararg_count;
33
use crate::*;
44

55
struct LinuxFutex {
@@ -10,7 +10,7 @@ struct LinuxFutex {
1010
/// `args` is the arguments *including* the syscall number.
1111
pub fn futex<'tcx>(
1212
ecx: &mut MiriInterpCx<'tcx>,
13-
args: &[OpTy<'tcx>],
13+
varargs: &[OpTy<'tcx>],
1414
dest: &MPlaceTy<'tcx>,
1515
) -> InterpResult<'tcx> {
1616
// The amount of arguments used depends on the type of futex operation.
@@ -21,7 +21,7 @@ pub fn futex<'tcx>(
2121
// may or may not be left out from the `syscall()` call.
2222
// Therefore we don't use `check_arg_count` here, but only check for the
2323
// number of arguments to fall within a range.
24-
let [_, addr, op, val] = check_min_arg_count("`syscall(SYS_futex, ...)`", args)?;
24+
let [addr, op, val] = check_min_vararg_count("`syscall(SYS_futex, ...)`", varargs)?;
2525

2626
// The first three arguments (after the syscall number itself) are the same to all futex operations:
2727
// (int *addr, int op, int val).
@@ -55,14 +55,16 @@ pub fn futex<'tcx>(
5555
let wait_bitset = op & !futex_realtime == futex_wait_bitset;
5656

5757
let (timeout, bitset) = if wait_bitset {
58-
let [_, _, _, _, timeout, uaddr2, bitset] =
59-
check_min_arg_count("`syscall(SYS_futex, FUTEX_WAIT_BITSET, ...)`", args)?;
58+
let [_, _, _, timeout, uaddr2, bitset] = check_min_vararg_count(
59+
"`syscall(SYS_futex, FUTEX_WAIT_BITSET, ...)`",
60+
varargs,
61+
)?;
6062
let _timeout = ecx.read_pointer(timeout)?;
6163
let _uaddr2 = ecx.read_pointer(uaddr2)?;
6264
(timeout, ecx.read_scalar(bitset)?.to_u32()?)
6365
} else {
64-
let [_, _, _, _, timeout] =
65-
check_min_arg_count("`syscall(SYS_futex, FUTEX_WAIT, ...)`", args)?;
66+
let [_, _, _, timeout] =
67+
check_min_vararg_count("`syscall(SYS_futex, FUTEX_WAIT, ...)`", varargs)?;
6668
(timeout, u32::MAX)
6769
};
6870

@@ -190,8 +192,10 @@ pub fn futex<'tcx>(
190192
let futex_ref = futex_ref.futex.clone();
191193

192194
let bitset = if op == futex_wake_bitset {
193-
let [_, _, _, _, timeout, uaddr2, bitset] =
194-
check_min_arg_count("`syscall(SYS_futex, FUTEX_WAKE_BITSET, ...)`", args)?;
195+
let [_, _, _, timeout, uaddr2, bitset] = check_min_vararg_count(
196+
"`syscall(SYS_futex, FUTEX_WAKE_BITSET, ...)`",
197+
varargs,
198+
)?;
195199
let _timeout = ecx.read_pointer(timeout)?;
196200
let _uaddr2 = ecx.read_pointer(uaddr2)?;
197201
ecx.read_scalar(bitset)?.to_u32()?

src/tools/miri/src/shims/unix/linux_like/syscall.rs

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use rustc_middle::ty::Ty;
22
use rustc_span::Symbol;
33
use rustc_target::callconv::{Conv, FnAbi};
44

5-
use crate::helpers::check_min_arg_count;
5+
use crate::helpers::check_min_vararg_count;
66
use crate::shims::unix::linux_like::eventfd::EvalContextExt as _;
77
use crate::shims::unix::linux_like::sync::futex;
88
use crate::*;
@@ -14,9 +14,7 @@ pub fn syscall<'tcx>(
1414
args: &[OpTy<'tcx>],
1515
dest: &MPlaceTy<'tcx>,
1616
) -> InterpResult<'tcx> {
17-
// We do not use `check_shim` here because `syscall` is variadic. The argument
18-
// count is checked bellow.
19-
ecx.check_abi_and_shim_symbol_clash(abi, Conv::C, link_name)?;
17+
let ([op], varargs) = ecx.check_shim_variadic(abi, Conv::C, link_name, args)?;
2018
// The syscall variadic function is legal to call with more arguments than needed,
2119
// extra arguments are simply ignored. The important check is that when we use an
2220
// argument, we have to also check all arguments *before* it to ensure that they
@@ -26,14 +24,13 @@ pub fn syscall<'tcx>(
2624
let sys_futex = ecx.eval_libc("SYS_futex").to_target_usize(ecx)?;
2725
let sys_eventfd2 = ecx.eval_libc("SYS_eventfd2").to_target_usize(ecx)?;
2826

29-
let [op] = check_min_arg_count("syscall", args)?;
3027
match ecx.read_target_usize(op)? {
3128
// `libc::syscall(NR_GETRANDOM, buf.as_mut_ptr(), buf.len(), GRND_NONBLOCK)`
3229
// is called if a `HashMap` is created the regular way (e.g. HashMap<K, V>).
3330
num if num == sys_getrandom => {
3431
// Used by getrandom 0.1
3532
// The first argument is the syscall id, so skip over it.
36-
let [_, ptr, len, flags] = check_min_arg_count("syscall(SYS_getrandom, ...)", args)?;
33+
let [ptr, len, flags] = check_min_vararg_count("syscall(SYS_getrandom, ...)", varargs)?;
3734

3835
let ptr = ecx.read_pointer(ptr)?;
3936
let len = ecx.read_target_usize(len)?;
@@ -47,10 +44,10 @@ pub fn syscall<'tcx>(
4744
}
4845
// `futex` is used by some synchronization primitives.
4946
num if num == sys_futex => {
50-
futex(ecx, args, dest)?;
47+
futex(ecx, varargs, dest)?;
5148
}
5249
num if num == sys_eventfd2 => {
53-
let [_, initval, flags] = check_min_arg_count("syscall(SYS_evetfd2, ...)", args)?;
50+
let [initval, flags] = check_min_vararg_count("syscall(SYS_evetfd2, ...)", varargs)?;
5451

5552
let result = ecx.eventfd(initval, flags)?;
5653
ecx.write_int(result.to_i32()?, dest)?;

src/tools/miri/src/shims/unix/macos/foreign_items.rs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ use rustc_span::Symbol;
33
use rustc_target::callconv::{Conv, FnAbi};
44

55
use super::sync::EvalContextExt as _;
6-
use crate::helpers::check_min_arg_count;
76
use crate::shims::unix::*;
87
use crate::*;
98

@@ -69,10 +68,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
6968
this.write_scalar(result, dest)?;
7069
}
7170
"ioctl" => {
72-
// `ioctl` is variadic. The argument count is checked based on the first argument
73-
// in `this.ioctl()`, so we do not use `check_shim` here.
74-
this.check_abi_and_shim_symbol_clash(abi, Conv::C, link_name)?;
75-
let result = this.ioctl(args)?;
71+
let ([fd_num, cmd], varargs) =
72+
this.check_shim_variadic(abi, Conv::C, link_name, args)?;
73+
let result = this.ioctl(fd_num, cmd, varargs)?;
7674
this.write_scalar(result, dest)?;
7775
}
7876

@@ -243,12 +241,16 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
243241
interp_ok(EmulateItemResult::NeedsReturn)
244242
}
245243

246-
fn ioctl(&mut self, args: &[OpTy<'tcx>]) -> InterpResult<'tcx, Scalar> {
244+
fn ioctl(
245+
&mut self,
246+
fd_num: &OpTy<'tcx>,
247+
cmd: &OpTy<'tcx>,
248+
_varargs: &[OpTy<'tcx>],
249+
) -> InterpResult<'tcx, Scalar> {
247250
let this = self.eval_context_mut();
248251

249252
let fioclex = this.eval_libc_u64("FIOCLEX");
250253

251-
let [fd_num, cmd] = check_min_arg_count("ioctl", args)?;
252254
let fd_num = this.read_scalar(fd_num)?.to_i32()?;
253255
let cmd = this.read_scalar(cmd)?.to_u64()?;
254256

src/tools/miri/test_dependencies/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ version = "0.1.0"
88
edition = "2021"
99

1010
[dependencies]
11-
# all dependencies (and their transitive ones) listed here can be used in `tests/`.
11+
# all dependencies (and their transitive ones) listed here can be used in `tests/*-dep`.
1212
libc = "0.2"
1313
num_cpus = "1.10.1"
1414
cfg-if = "1"

src/tools/miri/tests/fail-dep/libc/fs/unix_open_missing_required_mode.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,5 @@ fn main() {
88
fn test_file_open_missing_needed_mode() {
99
let name = b"missing_arg.txt\0";
1010
let name_ptr = name.as_ptr().cast::<libc::c_char>();
11-
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
11+
let _fd = unsafe { libc::open(name_ptr, libc::O_CREAT) }; //~ ERROR: Undefined Behavior: not enough variadic arguments
1212
}

src/tools/miri/tests/fail-dep/libc/fs/unix_open_missing_required_mode.stderr

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
error: Undefined Behavior: incorrect number of arguments for `open(pathname, O_CREAT, ...)`: got 2, expected at least 3
1+
error: Undefined Behavior: not enough variadic arguments for `open(pathname, O_CREAT, ...)`: got 0, expected at least 1
22
--> tests/fail-dep/libc/fs/unix_open_missing_required_mode.rs:LL:CC
33
|
4-
LL | ... { libc::open(name_ptr, libc::O_CREAT) };
5-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ incorrect number of arguments for `open(pathname, O_CREAT, ...)`: got 2, expected at least 3
4+
LL | let _fd = unsafe { libc::open(name_ptr, libc::O_CREAT) };
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not enough variadic arguments for `open(pathname, O_CREAT, ...)`: got 0, expected at least 1
66
|
77
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
88
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information

0 commit comments

Comments
 (0)