Skip to content

Commit 8e97599

Browse files
committed
allow varargs for libc::open when it is allowed by the second argument
1 parent a284d4f commit 8e97599

File tree

5 files changed

+117
-30
lines changed

5 files changed

+117
-30
lines changed

src/shims/posix/foreign_items.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
5555

5656
// File related shims
5757
"open" | "open64" => {
58-
let &[ref path, ref flag, ref mode] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
59-
let result = this.open(path, flag, mode)?;
58+
// `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
59+
this.check_abi_and_shim_symbol_clash(abi, Abi::C { unwind: false }, link_name)?;
60+
let result = this.open(args)?;
6061
this.write_scalar(Scalar::from_i32(result), dest)?;
6162
}
6263
"fcntl" => {

src/shims/posix/fs.rs

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -474,23 +474,18 @@ fn maybe_sync_file(
474474

475475
impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {}
476476
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx> {
477-
fn open(
478-
&mut self,
479-
path_op: &OpTy<'tcx, Tag>,
480-
flag_op: &OpTy<'tcx, Tag>,
481-
mode_op: &OpTy<'tcx, Tag>,
482-
) -> InterpResult<'tcx, i32> {
483-
let this = self.eval_context_mut();
477+
fn open(&mut self, args: &[OpTy<'tcx, Tag>]) -> InterpResult<'tcx, i32> {
478+
if args.len() < 2 || args.len() > 3 {
479+
throw_ub_format!(
480+
"incorrect number of arguments for `open`: got {}, expected 2 or 3",
481+
args.len()
482+
);
483+
}
484484

485-
let flag = this.read_scalar(flag_op)?.to_i32()?;
485+
let this = self.eval_context_mut();
486486

487-
// Get the mode. On macOS, the argument type `mode_t` is actually `u16`, but
488-
// C integer promotion rules mean that on the ABI level, it gets passed as `u32`
489-
// (see https://github.com/rust-lang/rust/issues/71915).
490-
let mode = this.read_scalar(mode_op)?.to_u32()?;
491-
if mode != 0o666 {
492-
throw_unsup_format!("non-default mode 0o{:o} is not supported", mode);
493-
}
487+
let path_op = &args[0];
488+
let flag = this.read_scalar(&args[1])?.to_i32()?;
494489

495490
let mut options = OpenOptions::new();
496491

@@ -535,6 +530,22 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
535530
}
536531
let o_creat = this.eval_libc_i32("O_CREAT")?;
537532
if flag & o_creat != 0 {
533+
// Get the mode. On macOS, the argument type `mode_t` is actually `u16`, but
534+
// C integer promotion rules mean that on the ABI level, it gets passed as `u32`
535+
// (see https://github.com/rust-lang/rust/issues/71915).
536+
let mode = if let Some(arg) = args.get(2) {
537+
this.read_scalar(arg)?.to_u32()?
538+
} else {
539+
throw_ub_format!(
540+
"incorrect number of arguments for `open` with `O_CREAT`: got {}, expected 3",
541+
args.len()
542+
);
543+
};
544+
545+
if mode != 0o666 {
546+
throw_unsup_format!("non-default mode 0o{:o} is not supported", mode);
547+
}
548+
538549
mirror |= o_creat;
539550

540551
let o_excl = this.eval_libc_i32("O_EXCL")?;
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// ignore-windows: No libc on Windows
2+
// compile-flags: -Zmiri-disable-isolation
3+
4+
#![feature(rustc_private)]
5+
6+
extern crate libc;
7+
8+
fn main() {
9+
test_file_open_missing_needed_mode();
10+
}
11+
12+
fn test_file_open_missing_needed_mode() {
13+
let name = b"missing_arg.txt\0";
14+
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
16+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// ignore-windows: No libc on Windows
2+
// compile-flags: -Zmiri-disable-isolation
3+
4+
#![feature(rustc_private)]
5+
6+
extern crate libc;
7+
8+
fn main() {
9+
test_open_too_many_args();
10+
}
11+
12+
fn test_open_too_many_args() {
13+
let name = b"too_many_args.txt\0";
14+
let name_ptr = name.as_ptr().cast::<libc::c_char>();
15+
let _fd = unsafe { libc::open(name_ptr, libc::O_RDONLY, 0, 0) }; //~ ERROR Undefined Behavior: incorrect number of arguments for `open`: got 4, expected 2 or 3
16+
}

tests/run-pass/fs.rs

Lines changed: 56 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,10 @@
55

66
extern crate libc;
77

8-
use std::fs::{
9-
File, create_dir, OpenOptions, remove_dir, remove_dir_all, remove_file, rename,
10-
};
118
use std::ffi::CString;
12-
use std::io::{Read, Write, Error, ErrorKind, Result, Seek, SeekFrom};
13-
use std::path::{PathBuf, Path};
14-
9+
use std::fs::{create_dir, remove_dir, remove_dir_all, remove_file, rename, File, OpenOptions};
10+
use std::io::{Error, ErrorKind, Read, Result, Seek, SeekFrom, Write};
11+
use std::path::{Path, PathBuf};
1512

1613
fn main() {
1714
test_file();
@@ -26,6 +23,11 @@ fn main() {
2623
test_rename();
2724
test_directory();
2825
test_dup_stdout_stderr();
26+
27+
// These all require unix, if the test is changed to no longer `ignore-windows`, move these to a unix test
28+
test_file_open_unix_allow_two_args();
29+
test_file_open_unix_needs_three_args();
30+
test_file_open_unix_extra_third_arg();
2931
}
3032

3133
fn tmp() -> PathBuf {
@@ -41,7 +43,8 @@ fn tmp() -> PathBuf {
4143

4244
#[cfg(not(windows))]
4345
return PathBuf::from(tmp.replace("\\", "/"));
44-
}).unwrap_or_else(|_| std::env::temp_dir())
46+
})
47+
.unwrap_or_else(|_| std::env::temp_dir())
4548
}
4649

4750
/// Prepare: compute filename and make sure the file does not exist.
@@ -93,6 +96,39 @@ fn test_file() {
9396
remove_file(&path).unwrap();
9497
}
9598

99+
fn test_file_open_unix_allow_two_args() {
100+
use std::os::unix::ffi::OsStrExt;
101+
102+
let path = prepare_with_content("test_file_open_unix_allow_two_args.txt", &[]);
103+
104+
let mut name = path.into_os_string();
105+
name.push("\0");
106+
let name_ptr = name.as_bytes().as_ptr().cast::<libc::c_char>();
107+
let _fd = unsafe { libc::open(name_ptr, libc::O_RDONLY) };
108+
}
109+
110+
fn test_file_open_unix_needs_three_args() {
111+
use std::os::unix::ffi::OsStrExt;
112+
113+
let path = prepare_with_content("test_file_open_unix_needs_three_args.txt", &[]);
114+
115+
let mut name = path.into_os_string();
116+
name.push("\0");
117+
let name_ptr = name.as_bytes().as_ptr().cast::<libc::c_char>();
118+
let _fd = unsafe { libc::open(name_ptr, libc::O_CREAT, 0o666) };
119+
}
120+
121+
fn test_file_open_unix_extra_third_arg() {
122+
use std::os::unix::ffi::OsStrExt;
123+
124+
let path = prepare_with_content("test_file_open_unix_extra_third_arg.txt", &[]);
125+
126+
let mut name = path.into_os_string();
127+
name.push("\0");
128+
let name_ptr = name.as_bytes().as_ptr().cast::<libc::c_char>();
129+
let _fd = unsafe { libc::open(name_ptr, libc::O_RDONLY, 42) };
130+
}
131+
96132
fn test_file_clone() {
97133
let bytes = b"Hello, World!\n";
98134
let path = prepare_with_content("miri_test_fs_file_clone.txt", bytes);
@@ -115,7 +151,10 @@ fn test_file_create_new() {
115151
// Creating a new file that doesn't yet exist should succeed.
116152
OpenOptions::new().write(true).create_new(true).open(&path).unwrap();
117153
// Creating a new file that already exists should fail.
118-
assert_eq!(ErrorKind::AlreadyExists, OpenOptions::new().write(true).create_new(true).open(&path).unwrap_err().kind());
154+
assert_eq!(
155+
ErrorKind::AlreadyExists,
156+
OpenOptions::new().write(true).create_new(true).open(&path).unwrap_err().kind()
157+
);
119158
// Optionally creating a new file that already exists should succeed.
120159
OpenOptions::new().write(true).create(true).open(&path).unwrap();
121160

@@ -235,7 +274,6 @@ fn test_symlink() {
235274
symlink_file.read_to_end(&mut contents).unwrap();
236275
assert_eq!(bytes, contents.as_slice());
237276

238-
239277
#[cfg(unix)]
240278
{
241279
use std::os::unix::ffi::OsStrExt;
@@ -250,7 +288,9 @@ fn test_symlink() {
250288
// Make the buf one byte larger than it needs to be,
251289
// and check that the last byte is not overwritten.
252290
let mut large_buf = vec![0xFF; expected_path.len() + 1];
253-
let res = unsafe { libc::readlink(symlink_c_ptr, large_buf.as_mut_ptr().cast(), large_buf.len()) };
291+
let res = unsafe {
292+
libc::readlink(symlink_c_ptr, large_buf.as_mut_ptr().cast(), large_buf.len())
293+
};
254294
// Check that the resovled path was properly written into the buf.
255295
assert_eq!(&large_buf[..(large_buf.len() - 1)], expected_path);
256296
assert_eq!(large_buf.last(), Some(&0xFF));
@@ -259,18 +299,21 @@ fn test_symlink() {
259299
// Test that the resolved path is truncated if the provided buffer
260300
// is too small.
261301
let mut small_buf = [0u8; 2];
262-
let res = unsafe { libc::readlink(symlink_c_ptr, small_buf.as_mut_ptr().cast(), small_buf.len()) };
302+
let res = unsafe {
303+
libc::readlink(symlink_c_ptr, small_buf.as_mut_ptr().cast(), small_buf.len())
304+
};
263305
assert_eq!(small_buf, &expected_path[..small_buf.len()]);
264306
assert_eq!(res, small_buf.len() as isize);
265307

266308
// Test that we report a proper error for a missing path.
267309
let bad_path = CString::new("MIRI_MISSING_FILE_NAME").unwrap();
268-
let res = unsafe { libc::readlink(bad_path.as_ptr(), small_buf.as_mut_ptr().cast(), small_buf.len()) };
310+
let res = unsafe {
311+
libc::readlink(bad_path.as_ptr(), small_buf.as_mut_ptr().cast(), small_buf.len())
312+
};
269313
assert_eq!(res, -1);
270314
assert_eq!(Error::last_os_error().kind(), ErrorKind::NotFound);
271315
}
272316

273-
274317
// Test that metadata of a symbolic link is correct.
275318
check_metadata(bytes, &symlink_path).unwrap();
276319
// Test that the metadata of a symbolic link is correct when not following it.

0 commit comments

Comments
 (0)