Skip to content

Commit ea405ae

Browse files
committed
non-deterministically truncate reads/writes
1 parent 9675a3d commit ea405ae

File tree

4 files changed

+72
-19
lines changed

4 files changed

+72
-19
lines changed

src/shims/unix/fd.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
use std::io;
55
use std::io::ErrorKind;
66

7+
use rand::Rng;
78
use rustc_abi::Size;
89

910
use crate::helpers::check_min_vararg_count;
@@ -257,6 +258,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
257258
let count = usize::try_from(count).unwrap(); // now it fits in a `usize`
258259
let communicate = this.machine.communicate();
259260

261+
// Non-deterministically decide to further reduce the count, simulating a partial read (but
262+
// never to 0, that has different behavior).
263+
let count =
264+
if count >= 2 && this.machine.rng.get_mut().random() { count / 2 } else { count };
265+
260266
// Get the FD.
261267
let Some(fd) = this.machine.fds.get(fd_num) else {
262268
trace!("read: FD not found");
@@ -265,7 +271,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
265271

266272
trace!("read: FD mapped to {fd:?}");
267273
// We want to read at most `count` bytes. We are sure that `count` is not negative
268-
// because it was a target's `usize`. Also we are sure that its smaller than
274+
// because it was a target's `usize`. Also we are sure that it's smaller than
269275
// `usize::MAX` because it is bounded by the host's `isize`.
270276

271277
let finish = {
@@ -323,6 +329,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
323329
let count = usize::try_from(count).unwrap(); // now it fits in a `usize`
324330
let communicate = this.machine.communicate();
325331

332+
// Non-deterministically decide to further reduce the count, simulating a partial write (but
333+
// never to 0, that has different behavior).
334+
let count =
335+
if count >= 2 && this.machine.rng.get_mut().random() { count / 2 } else { count };
336+
326337
// We temporarily dup the FD to be able to retain mutable access to `this`.
327338
let Some(fd) = this.machine.fds.get(fd_num) else {
328339
return this.set_last_error_and_return(LibcError("EBADF"), dest);

tests/pass-dep/libc/libc-fs.rs

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,11 @@ fn test_dup_stdout_stderr() {
7474
unsafe {
7575
let new_stdout = libc::fcntl(1, libc::F_DUPFD, 0);
7676
let new_stderr = libc::fcntl(2, libc::F_DUPFD, 0);
77-
libc::write(new_stdout, bytes.as_ptr() as *const libc::c_void, bytes.len());
78-
libc::write(new_stderr, bytes.as_ptr() as *const libc::c_void, bytes.len());
77+
// We write byte-for-byte as that's the easiest way to deal with partial writes...
78+
for b in bytes.iter() {
79+
assert_eq!(libc::write(new_stdout, &raw const *b as *const libc::c_void, 1), 1);
80+
assert_eq!(libc::write(new_stderr, &raw const *b as *const libc::c_void, 1), 1);
81+
}
7982
}
8083
}
8184

@@ -92,16 +95,24 @@ fn test_dup() {
9295
let new_fd2 = libc::dup2(fd, 8);
9396

9497
let mut first_buf = [0u8; 4];
95-
libc::read(fd, first_buf.as_mut_ptr() as *mut libc::c_void, 4);
96-
assert_eq!(&first_buf, b"dup ");
98+
let first_len = libc::read(fd, first_buf.as_mut_ptr() as *mut libc::c_void, 4);
99+
assert!(first_len > 0);
100+
let first_len = first_len as usize;
101+
assert_eq!(first_buf[..first_len], bytes[..first_len]);
102+
let remaining_bytes = &bytes[first_len..];
97103

98104
let mut second_buf = [0u8; 4];
99-
libc::read(new_fd, second_buf.as_mut_ptr() as *mut libc::c_void, 4);
100-
assert_eq!(&second_buf, b"and ");
105+
let second_len = libc::read(new_fd, second_buf.as_mut_ptr() as *mut libc::c_void, 4);
106+
assert!(second_len > 0);
107+
let second_len = second_len as usize;
108+
assert_eq!(second_buf[..second_len], remaining_bytes[..second_len]);
109+
let remaining_bytes = &remaining_bytes[second_len..];
101110

102111
let mut third_buf = [0u8; 4];
103-
libc::read(new_fd2, third_buf.as_mut_ptr() as *mut libc::c_void, 4);
104-
assert_eq!(&third_buf, b"dup2");
112+
let third_len = libc::read(new_fd2, third_buf.as_mut_ptr() as *mut libc::c_void, 4);
113+
assert!(third_len > 0);
114+
let third_len = third_len as usize;
115+
assert_eq!(third_buf[..third_len], remaining_bytes[..third_len]);
105116
}
106117
}
107118

@@ -145,7 +156,7 @@ fn test_ftruncate<T: From<i32>>(
145156
let bytes = b"hello";
146157
let path = utils::prepare("miri_test_libc_fs_ftruncate.txt");
147158
let mut file = File::create(&path).unwrap();
148-
file.write(bytes).unwrap();
159+
file.write_all(bytes).unwrap();
149160
file.sync_all().unwrap();
150161
assert_eq!(file.metadata().unwrap().len(), 5);
151162

@@ -402,25 +413,33 @@ fn test_read_and_uninit() {
402413
unsafe {
403414
let fd = libc::open(cpath.as_ptr(), libc::O_RDONLY);
404415
assert_ne!(fd, -1);
405-
let mut buf: MaybeUninit<[u8; 2]> = std::mem::MaybeUninit::uninit();
406-
assert_eq!(libc::read(fd, buf.as_mut_ptr().cast::<std::ffi::c_void>(), 2), 2);
416+
let mut buf: MaybeUninit<u8> = std::mem::MaybeUninit::uninit();
417+
assert_eq!(libc::read(fd, buf.as_mut_ptr().cast::<std::ffi::c_void>(), 1), 1);
407418
let buf = buf.assume_init();
408-
assert_eq!(buf, [1, 2]);
419+
assert_eq!(buf, 1);
409420
assert_eq!(libc::close(fd), 0);
410421
}
411422
remove_file(&path).unwrap();
412423
}
413424
{
414425
// We test that if we requested to read 4 bytes, but actually read 3 bytes, then
415426
// 3 bytes (not 4) will be overwritten, and remaining byte will be left as-is.
416-
let path = utils::prepare_with_content("pass-libc-read-and-uninit-2.txt", &[1u8, 2, 3]);
427+
let data = [1u8, 2, 3];
428+
let path = utils::prepare_with_content("pass-libc-read-and-uninit-2.txt", &data);
417429
let cpath = CString::new(path.clone().into_os_string().into_encoded_bytes()).unwrap();
418430
unsafe {
419431
let fd = libc::open(cpath.as_ptr(), libc::O_RDONLY);
420432
assert_ne!(fd, -1);
421433
let mut buf = [42u8; 5];
422-
assert_eq!(libc::read(fd, buf.as_mut_ptr().cast::<std::ffi::c_void>(), 4), 3);
423-
assert_eq!(buf, [1, 2, 3, 42, 42]);
434+
let res = libc::read(fd, buf.as_mut_ptr().cast::<std::ffi::c_void>(), 4);
435+
assert!(res > 0 && res < 4);
436+
for i in 0..buf.len() {
437+
assert_eq!(
438+
buf[i],
439+
if i < res as usize { data[i] } else { 42 },
440+
"wrong result at pos {i}"
441+
);
442+
}
424443
assert_eq!(libc::close(fd), 0);
425444
}
426445
remove_file(&path).unwrap();

tests/pass/shims/fs.rs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ mod utils;
1717
fn main() {
1818
test_path_conversion();
1919
test_file();
20+
test_file_partial_reads_writes();
2021
test_file_create_new();
2122
test_metadata();
2223
test_seek();
@@ -53,7 +54,7 @@ fn test_file() {
5354
file.write(&mut []).unwrap();
5455
assert_eq!(file.metadata().unwrap().len(), 0);
5556

56-
file.write(bytes).unwrap();
57+
file.write_all(bytes).unwrap();
5758
assert_eq!(file.metadata().unwrap().len(), bytes.len() as u64);
5859
// Test opening, reading and closing a file.
5960
let mut file = File::open(&path).unwrap();
@@ -70,6 +71,28 @@ fn test_file() {
7071
remove_file(&path).unwrap();
7172
}
7273

74+
fn test_file_partial_reads_writes() {
75+
let path = utils::prepare_with_content("miri_test_fs_file.txt", b"abcdefg");
76+
77+
// Ensure we sometimes do incomplete writes.
78+
let got_short_write = (0..16).any(|_| {
79+
let _ = remove_file(&path); // FIXME(win): we get errors if the file already exists?
80+
let mut file = File::create(&path).unwrap();
81+
file.write(&[0; 4]).unwrap() != 4
82+
});
83+
assert!(got_short_write);
84+
// Ensure we sometimes do incomplete reads.
85+
let got_short_read = (0..16).any(|_| {
86+
let mut file = File::open(&path).unwrap();
87+
let mut buf = [0; 4];
88+
file.read(&mut buf).unwrap() != 4
89+
});
90+
assert!(got_short_read);
91+
92+
// Clean up
93+
remove_file(&path).unwrap();
94+
}
95+
7396
fn test_file_clone() {
7497
let bytes = b"Hello, World!\n";
7598
let path = utils::prepare_with_content("miri_test_fs_file_clone.txt", bytes);

tests/pass/shims/pipe.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ use std::io::{Read, Write, pipe};
44

55
fn main() {
66
let (mut ping_rx, mut ping_tx) = pipe().unwrap();
7-
ping_tx.write(b"hello").unwrap();
7+
ping_tx.write_all(b"hello").unwrap();
88
let mut buf: [u8; 5] = [0; 5];
9-
ping_rx.read(&mut buf).unwrap();
9+
ping_rx.read_exact(&mut buf).unwrap();
1010
assert_eq!(&buf, "hello".as_bytes());
1111
}

0 commit comments

Comments
 (0)