Skip to content

Commit f3a9856

Browse files
committed
Auto merge of #2058 - RalfJung:variadic, r=RalfJung
For variadic functions, accept arbitrary trailing arguments However, make sure that if we use argument N we check the size of all arguments before that, because otherwise this might not work properly depending on how varargs are implemented. This caught bugs in our futex tests. ;) I couldn't find a good way to systematically ensure this, so it is just something we have to be on the look for during review. (This generally applies also for fixed-arg shims: we should check the size of each parameter.) Also treat prctl like a variadic function, Cc `@saethlin.`
2 parents 8acc9b2 + cac48dd commit f3a9856

File tree

7 files changed

+81
-81
lines changed

7 files changed

+81
-81
lines changed

src/shims/posix/fs.rs

+17-14
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ use rustc_middle::ty::{self, layout::LayoutOf};
1515
use rustc_target::abi::{Align, Size};
1616

1717
use crate::*;
18-
use helpers::check_arg_count;
1918
use shims::os_str::os_str_to_bytes;
2019
use shims::time::system_time_to_duration;
2120

@@ -479,16 +478,16 @@ fn maybe_sync_file(
479478
impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {}
480479
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx> {
481480
fn open(&mut self, args: &[OpTy<'tcx, Tag>]) -> InterpResult<'tcx, i32> {
482-
if args.len() < 2 || args.len() > 3 {
481+
if args.len() < 2 {
483482
throw_ub_format!(
484-
"incorrect number of arguments for `open`: got {}, expected 2 or 3",
483+
"incorrect number of arguments for `open`: got {}, expected at least 2",
485484
args.len()
486485
);
487486
}
488487

489488
let this = self.eval_context_mut();
490489

491-
let path_op = &args[0];
490+
let path = this.read_pointer(&args[0])?;
492491
let flag = this.read_scalar(&args[1])?.to_i32()?;
493492

494493
let mut options = OpenOptions::new();
@@ -541,7 +540,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
541540
this.read_scalar(arg)?.to_u32()?
542541
} else {
543542
throw_ub_format!(
544-
"incorrect number of arguments for `open` with `O_CREAT`: got {}, expected 3",
543+
"incorrect number of arguments for `open` with `O_CREAT`: got {}, expected at least 3",
545544
args.len()
546545
);
547546
};
@@ -572,7 +571,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
572571
throw_unsup_format!("unsupported flags {:#x}", flag & !mirror);
573572
}
574573

575-
let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
574+
let path = this.read_path_from_c_str(path)?;
576575

577576
// Reject if isolation is enabled.
578577
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
@@ -614,7 +613,6 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
614613
// `FD_CLOEXEC` value without checking if the flag is set for the file because `std`
615614
// always sets this flag when opening a file. However we still need to check that the
616615
// file itself is open.
617-
let &[_, _] = check_arg_count(args)?;
618616
if this.machine.file_handler.handles.contains_key(&fd) {
619617
Ok(this.eval_libc_i32("FD_CLOEXEC")?)
620618
} else {
@@ -627,8 +625,13 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
627625
// because exec() isn't supported. The F_DUPFD and F_DUPFD_CLOEXEC commands only
628626
// differ in whether the FD_CLOEXEC flag is pre-set on the new file descriptor,
629627
// thus they can share the same implementation here.
630-
let &[_, _, ref start] = check_arg_count(args)?;
631-
let start = this.read_scalar(start)?.to_i32()?;
628+
if args.len() < 3 {
629+
throw_ub_format!(
630+
"incorrect number of arguments for fcntl with cmd=`F_DUPFD`/`F_DUPFD_CLOEXEC`: got {}, expected at least 3",
631+
args.len()
632+
);
633+
}
634+
let start = this.read_scalar(&args[2])?.to_i32()?;
632635

633636
let fh = &mut this.machine.file_handler;
634637

@@ -646,7 +649,6 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
646649
None => return this.handle_not_found(),
647650
}
648651
} else if this.tcx.sess.target.os == "macos" && cmd == this.eval_libc_i32("F_FULLFSYNC")? {
649-
let &[_, _] = check_arg_count(args)?;
650652
if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) {
651653
// FIXME: Support fullfsync for all FDs
652654
let FileHandle { file, writable } = file_descriptor.as_file_handle()?;
@@ -919,15 +921,18 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
919921
dirfd_op: &OpTy<'tcx, Tag>, // Should be an `int`
920922
pathname_op: &OpTy<'tcx, Tag>, // Should be a `const char *`
921923
flags_op: &OpTy<'tcx, Tag>, // Should be an `int`
922-
_mask_op: &OpTy<'tcx, Tag>, // Should be an `unsigned int`
924+
mask_op: &OpTy<'tcx, Tag>, // Should be an `unsigned int`
923925
statxbuf_op: &OpTy<'tcx, Tag>, // Should be a `struct statx *`
924926
) -> InterpResult<'tcx, i32> {
925927
let this = self.eval_context_mut();
926928

927929
this.assert_target_os("linux", "statx");
928930

929-
let statxbuf_ptr = this.read_pointer(statxbuf_op)?;
931+
let dirfd = this.read_scalar(dirfd_op)?.to_i32()?;
930932
let pathname_ptr = this.read_pointer(pathname_op)?;
933+
let flags = this.read_scalar(flags_op)?.to_i32()?;
934+
let _mask = this.read_scalar(mask_op)?.to_u32()?;
935+
let statxbuf_ptr = this.read_pointer(statxbuf_op)?;
931936

932937
// If the statxbuf or pathname pointers are null, the function fails with `EFAULT`.
933938
if this.ptr_is_null(statxbuf_ptr)? || this.ptr_is_null(pathname_ptr)? {
@@ -953,9 +958,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
953958

954959
let path = this.read_path_from_c_str(pathname_ptr)?.into_owned();
955960
// See <https://github.com/rust-lang/rust/pull/79196> for a discussion of argument sizes.
956-
let flags = this.read_scalar(flags_op)?.to_i32()?;
957961
let empty_path_flag = flags & this.eval_libc("AT_EMPTY_PATH")?.to_i32()? != 0;
958-
let dirfd = this.read_scalar(dirfd_op)?.to_i32()?;
959962
// We only support:
960963
// * interpreting `path` as an absolute directory,
961964
// * interpreting `path` as a path relative to `dirfd` when the latter is `AT_FDCWD`, or

src/shims/posix/linux/foreign_items.rs

+7-14
Original file line numberDiff line numberDiff line change
@@ -106,9 +106,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
106106

107107
// Threading
108108
"prctl" => {
109-
let &[ref option, ref arg2, ref arg3, ref arg4, ref arg5] =
110-
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
111-
let result = this.prctl(option, arg2, arg3, arg4, arg5)?;
109+
// prctl is variadic. (It is not documented like that in the manpage, but defined like that in the libc crate.)
110+
this.check_abi_and_shim_symbol_clash(abi, Abi::C { unwind: false }, link_name)?;
111+
let result = this.prctl(args)?;
112112
this.write_scalar(Scalar::from_i32(result), dest)?;
113113
}
114114
"pthread_condattr_setclock" => {
@@ -130,16 +130,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
130130
// count is checked bellow.
131131
this.check_abi_and_shim_symbol_clash(abi, Abi::C { unwind: false }, link_name)?;
132132
// The syscall variadic function is legal to call with more arguments than needed,
133-
// extra arguments are simply ignored. However, all arguments need to be scalars;
134-
// other types might be treated differently by the calling convention.
135-
for arg in args {
136-
if !matches!(arg.layout.abi, rustc_target::abi::Abi::Scalar(_)) {
137-
throw_ub_format!(
138-
"`syscall` arguments must all have scalar layout, but {} does not",
139-
arg.layout.ty
140-
);
141-
}
142-
}
133+
// extra arguments are simply ignored. The important check is that when we use an
134+
// argument, we have to also check all arguments *before* it to ensure that they
135+
// have the right type.
143136

144137
let sys_getrandom = this.eval_libc("SYS_getrandom")?.to_machine_usize(this)?;
145138

@@ -181,7 +174,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
181174
}
182175
// `futex` is used by some synchonization primitives.
183176
id if id == sys_futex => {
184-
futex(this, args, dest)?;
177+
futex(this, &args[1..], dest)?;
185178
}
186179
id => {
187180
this.handle_unsupported(format!("can't execute syscall with ID {}", id))?;

src/shims/posix/linux/sync.rs

+23-17
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use rustc_target::abi::{Align, Size};
44
use std::time::{Instant, SystemTime};
55

66
/// Implementation of the SYS_futex syscall.
7+
/// `args` is the arguments *after* the syscall number.
78
pub fn futex<'tcx>(
89
this: &mut MiriEvalContext<'_, 'tcx>,
910
args: &[OpTy<'tcx, Tag>],
@@ -17,22 +18,23 @@ pub fn futex<'tcx>(
1718
// may or may not be left out from the `syscall()` call.
1819
// Therefore we don't use `check_arg_count` here, but only check for the
1920
// number of arguments to fall within a range.
20-
if args.len() < 4 {
21+
if args.len() < 3 {
2122
throw_ub_format!(
22-
"incorrect number of arguments for `futex` syscall: got {}, expected at least 4",
23+
"incorrect number of arguments for `futex` syscall: got {}, expected at least 3",
2324
args.len()
2425
);
2526
}
2627

2728
// The first three arguments (after the syscall number itself) are the same to all futex operations:
2829
// (int *addr, int op, int val).
2930
// We checked above that these definitely exist.
30-
let addr = this.read_immediate(&args[1])?;
31-
let op = this.read_scalar(&args[2])?.to_i32()?;
32-
let val = this.read_scalar(&args[3])?.to_i32()?;
31+
let addr = this.read_immediate(&args[0])?;
32+
let op = this.read_scalar(&args[1])?.to_i32()?;
33+
let val = this.read_scalar(&args[2])?.to_i32()?;
3334

3435
let thread = this.get_active_thread();
3536
let addr_scalar = addr.to_scalar()?;
37+
let addr_usize = addr_scalar.to_machine_usize(this)?;
3638

3739
let futex_private = this.eval_libc_i32("FUTEX_PRIVATE_FLAG")?;
3840
let futex_wait = this.eval_libc_i32("FUTEX_WAIT")?;
@@ -56,17 +58,19 @@ pub fn futex<'tcx>(
5658
let wait_bitset = op & !futex_realtime == futex_wait_bitset;
5759

5860
let bitset = if wait_bitset {
59-
if args.len() != 7 {
61+
if args.len() < 6 {
6062
throw_ub_format!(
61-
"incorrect number of arguments for `futex` syscall with `op=FUTEX_WAIT_BITSET`: got {}, expected 7",
63+
"incorrect number of arguments for `futex` syscall with `op=FUTEX_WAIT_BITSET`: got {}, expected at least 6",
6264
args.len()
6365
);
6466
}
65-
this.read_scalar(&args[6])?.to_u32()?
67+
let _timeout = this.read_pointer(&args[3])?;
68+
let _uaddr2 = this.read_pointer(&args[4])?;
69+
this.read_scalar(&args[5])?.to_u32()?
6670
} else {
67-
if args.len() < 5 {
71+
if args.len() < 4 {
6872
throw_ub_format!(
69-
"incorrect number of arguments for `futex` syscall with `op=FUTEX_WAIT`: got {}, expected at least 5",
73+
"incorrect number of arguments for `futex` syscall with `op=FUTEX_WAIT`: got {}, expected at least 4",
7074
args.len()
7175
);
7276
}
@@ -81,7 +85,7 @@ pub fn futex<'tcx>(
8185
}
8286

8387
// `deref_operand` but not actually dereferencing the ptr yet (it might be NULL!).
84-
let timeout = this.ref_to_mplace(&this.read_immediate(&args[4])?)?;
88+
let timeout = this.ref_to_mplace(&this.read_immediate(&args[3])?)?;
8589
let timeout_time = if this.ptr_is_null(timeout.ptr)? {
8690
None
8791
} else {
@@ -145,7 +149,7 @@ pub fn futex<'tcx>(
145149
if val == futex_val {
146150
// The value still matches, so we block the trait make it wait for FUTEX_WAKE.
147151
this.block_thread(thread);
148-
this.futex_wait(addr_scalar.to_machine_usize(this)?, thread, bitset);
152+
this.futex_wait(addr_usize, thread, bitset);
149153
// Succesfully waking up from FUTEX_WAIT always returns zero.
150154
this.write_scalar(Scalar::from_machine_isize(0, this), dest)?;
151155
// Register a timeout callback if a timeout was specified.
@@ -157,7 +161,7 @@ pub fn futex<'tcx>(
157161
timeout_time,
158162
Box::new(move |this| {
159163
this.unblock_thread(thread);
160-
this.futex_remove_waiter(addr_scalar.to_machine_usize(this)?, thread);
164+
this.futex_remove_waiter(addr_usize, thread);
161165
let etimedout = this.eval_libc("ETIMEDOUT")?;
162166
this.set_last_error(etimedout)?;
163167
this.write_scalar(Scalar::from_machine_isize(-1, this), &dest)?;
@@ -181,13 +185,15 @@ pub fn futex<'tcx>(
181185
// Same as FUTEX_WAKE, but allows you to specify a bitset to select which threads to wake up.
182186
op if op == futex_wake || op == futex_wake_bitset => {
183187
let bitset = if op == futex_wake_bitset {
184-
if args.len() != 7 {
188+
if args.len() < 6 {
185189
throw_ub_format!(
186-
"incorrect number of arguments for `futex` syscall with `op=FUTEX_WAKE_BITSET`: got {}, expected 7",
190+
"incorrect number of arguments for `futex` syscall with `op=FUTEX_WAKE_BITSET`: got {}, expected at least 6",
187191
args.len()
188192
);
189193
}
190-
this.read_scalar(&args[6])?.to_u32()?
194+
let _timeout = this.read_pointer(&args[3])?;
195+
let _uaddr2 = this.read_pointer(&args[4])?;
196+
this.read_scalar(&args[5])?.to_u32()?
191197
} else {
192198
u32::MAX
193199
};
@@ -199,7 +205,7 @@ pub fn futex<'tcx>(
199205
}
200206
let mut n = 0;
201207
for _ in 0..val {
202-
if let Some(thread) = this.futex_wake(addr_scalar.to_machine_usize(this)?, bitset) {
208+
if let Some(thread) = this.futex_wake(addr_usize, bitset) {
203209
this.unblock_thread(thread);
204210
this.unregister_timeout_callback_if_exists(thread);
205211
n += 1;

src/shims/posix/thread.rs

+25-11
Original file line numberDiff line numberDiff line change
@@ -97,28 +97,42 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
9797
this.write_scalar(Scalar::from_uint(thread_id.to_u32(), dest.layout.size), dest)
9898
}
9999

100-
fn prctl(
101-
&mut self,
102-
option: &OpTy<'tcx, Tag>,
103-
arg2: &OpTy<'tcx, Tag>,
104-
_arg3: &OpTy<'tcx, Tag>,
105-
_arg4: &OpTy<'tcx, Tag>,
106-
_arg5: &OpTy<'tcx, Tag>,
107-
) -> InterpResult<'tcx, i32> {
100+
fn prctl(&mut self, args: &[OpTy<'tcx, Tag>]) -> InterpResult<'tcx, i32> {
108101
let this = self.eval_context_mut();
109102
this.assert_target_os("linux", "prctl");
110103

111-
let option = this.read_scalar(option)?.to_i32()?;
104+
if args.len() < 1 {
105+
throw_ub_format!(
106+
"incorrect number of arguments for `prctl`: got {}, expected at least 1",
107+
args.len()
108+
);
109+
}
110+
111+
let option = this.read_scalar(&args[0])?.to_i32()?;
112112
if option == this.eval_libc_i32("PR_SET_NAME")? {
113-
let address = this.read_pointer(arg2)?;
113+
if args.len() < 2 {
114+
throw_ub_format!(
115+
"incorrect number of arguments for `prctl` with `PR_SET_NAME`: got {}, expected at least 2",
116+
args.len()
117+
);
118+
}
119+
120+
let address = this.read_pointer(&args[1])?;
114121
let mut name = this.read_c_str(address)?.to_owned();
115122
// The name should be no more than 16 bytes, including the null
116123
// byte. Since `read_c_str` returns the string without the null
117124
// byte, we need to truncate to 15.
118125
name.truncate(15);
119126
this.set_active_thread_name(name);
120127
} else if option == this.eval_libc_i32("PR_GET_NAME")? {
121-
let address = this.read_pointer(arg2)?;
128+
if args.len() < 2 {
129+
throw_ub_format!(
130+
"incorrect number of arguments for `prctl` with `PR_SET_NAME`: got {}, expected at least 2",
131+
args.len()
132+
);
133+
}
134+
135+
let address = this.read_pointer(&args[1])?;
122136
let mut name = this.get_active_thread_name().to_vec();
123137
name.push(0u8);
124138
assert!(name.len() <= 16);

tests/compile-fail/fs/unix_open_missing_required_mode.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,5 @@ fn main() {
1212
fn test_file_open_missing_needed_mode() {
1313
let name = b"missing_arg.txt\0";
1414
let name_ptr = name.as_ptr().cast::<libc::c_char>();
15-
let _fd = unsafe { libc::open(name_ptr, libc::O_CREAT) }; //~ ERROR Undefined Behavior: incorrect number of arguments for `open` with `O_CREAT`: got 2, expected 3
15+
let _fd = unsafe { libc::open(name_ptr, libc::O_CREAT) }; //~ ERROR Undefined Behavior: incorrect number of arguments for `open` with `O_CREAT`: got 2, expected at least 3
1616
}

tests/compile-fail/fs/unix_open_too_many_args.rs

-16
This file was deleted.

tests/run-pass/concurrency/linux-futex.rs

+8-8
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ fn wake_nobody() {
3232
&futex as *const i32,
3333
libc::FUTEX_WAKE,
3434
1,
35-
0,
36-
0,
35+
ptr::null::<libc::timespec>(),
36+
0usize,
3737
0,
3838
), 0);
3939
}
@@ -121,7 +121,7 @@ fn wait_absolute_timeout() {
121121
libc::FUTEX_WAIT_BITSET,
122122
123,
123123
&timeout,
124-
0,
124+
0usize,
125125
u32::MAX,
126126
), -1);
127127
assert_eq!(*libc::__errno_location(), libc::ETIMEDOUT);
@@ -173,8 +173,8 @@ fn wait_wake_bitset() {
173173
&FUTEX as *const i32,
174174
libc::FUTEX_WAKE_BITSET,
175175
10, // Wake up at most 10 threads.
176-
0,
177-
0,
176+
ptr::null::<libc::timespec>(),
177+
0usize,
178178
0b1001, // bitset
179179
), 0); // Didn't match any thread.
180180
}
@@ -185,8 +185,8 @@ fn wait_wake_bitset() {
185185
&FUTEX as *const i32,
186186
libc::FUTEX_WAKE_BITSET,
187187
10, // Wake up at most 10 threads.
188-
0,
189-
0,
188+
ptr::null::<libc::timespec>(),
189+
0usize,
190190
0b0110, // bitset
191191
), 1); // Woken up one thread.
192192
}
@@ -199,7 +199,7 @@ fn wait_wake_bitset() {
199199
libc::FUTEX_WAIT_BITSET,
200200
0,
201201
ptr::null::<libc::timespec>(),
202-
0,
202+
0usize,
203203
0b0100, // bitset
204204
), 0);
205205
}

0 commit comments

Comments
 (0)