diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index c98f105ad..3d5862c69 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -10,6 +10,7 @@ cargo-fuzz = true [dependencies] libfuzzer-sys = "0.4" libc = "0.2" +tempfile = "3" rand = { version = "0.8", features = ["small_rng"] } uucore = { path = "../src/uucore/" } diff --git a/fuzz/fuzz_targets/fuzz_common.rs b/fuzz/fuzz_targets/fuzz_common.rs index 89c82fba2..11b75a2fa 100644 --- a/fuzz/fuzz_targets/fuzz_common.rs +++ b/fuzz/fuzz_targets/fuzz_common.rs @@ -3,14 +3,15 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. +use libc::STDIN_FILENO; use libc::{close, dup, dup2, pipe, STDERR_FILENO, STDOUT_FILENO}; use rand::prelude::SliceRandom; use rand::Rng; use std::ffi::OsString; use std::io; -use std::io::Write; -use std::os::fd::RawFd; -use std::process::Command; +use std::io::{Seek, SeekFrom, Write}; +use std::os::fd::{AsRawFd, RawFd}; +use std::process::{Command, Stdio}; use std::sync::atomic::Ordering; use std::sync::{atomic::AtomicBool, Once}; @@ -49,7 +50,11 @@ pub fn is_gnu_cmd(cmd_path: &str) -> Result<(), std::io::Error> { } } -pub fn generate_and_run_uumain(args: &[OsString], uumain_function: F) -> CommandResult +pub fn generate_and_run_uumain( + args: &[OsString], + uumain_function: F, + pipe_input: Option<&str>, +) -> CommandResult where F: FnOnce(std::vec::IntoIter) -> i32, { @@ -58,8 +63,8 @@ where let original_stderr_fd = unsafe { dup(STDERR_FILENO) }; if original_stdout_fd == -1 || original_stderr_fd == -1 { return CommandResult { - stdout: "Failed to duplicate STDOUT_FILENO or STDERR_FILENO".to_string(), - stderr: "".to_string(), + stdout: "".to_string(), + stderr: "Failed to duplicate STDOUT_FILENO or STDERR_FILENO".to_string(), exit_code: -1, }; } @@ -72,8 +77,8 @@ where || unsafe { pipe(pipe_stderr_fds.as_mut_ptr()) } == -1 { return CommandResult { - stdout: "Failed to create pipes".to_string(), - stderr: "".to_string(), + stdout: "".to_string(), + stderr: "Failed to create pipes".to_string(), exit_code: -1, }; } @@ -89,12 +94,32 @@ where close(pipe_stderr_fds[1]); } return CommandResult { - stdout: "Failed to redirect STDOUT_FILENO or STDERR_FILENO".to_string(), - stderr: "".to_string(), + stdout: "".to_string(), + stderr: "Failed to redirect STDOUT_FILENO or STDERR_FILENO".to_string(), exit_code: -1, }; } + let original_stdin_fd = if let Some(input_str) = pipe_input { + // we have pipe input + let mut input_file = tempfile::tempfile().unwrap(); + write!(input_file, "{}", input_str).unwrap(); + input_file.seek(SeekFrom::Start(0)).unwrap(); + + // Redirect stdin to read from the in-memory file + let original_stdin_fd = unsafe { dup(STDIN_FILENO) }; + if original_stdin_fd == -1 || unsafe { dup2(input_file.as_raw_fd(), STDIN_FILENO) } == -1 { + return CommandResult { + stdout: "".to_string(), + stderr: "Failed to set up stdin redirection".to_string(), + exit_code: -1, + }; + } + Some(original_stdin_fd) + } else { + None + }; + let uumain_exit_status = uumain_function(args.to_owned().into_iter()); io::stdout().flush().unwrap(); @@ -105,8 +130,8 @@ where || unsafe { dup2(original_stderr_fd, STDERR_FILENO) } == -1 { return CommandResult { - stdout: "Failed to restore the original STDOUT_FILENO or STDERR_FILENO".to_string(), - stderr: "".to_string(), + stdout: "".to_string(), + stderr: "Failed to restore the original STDOUT_FILENO or STDERR_FILENO".to_string(), exit_code: -1, }; } @@ -118,6 +143,18 @@ where close(pipe_stderr_fds[1]); } + // Restore the original stdin if it was modified + if let Some(fd) = original_stdin_fd { + if unsafe { dup2(fd, STDIN_FILENO) } == -1 { + return CommandResult { + stdout: "".to_string(), + stderr: "Failed to restore the original STDIN".to_string(), + exit_code: -1, + }; + } + unsafe { close(fd) }; + } + let captured_stdout = read_from_fd(pipe_stdout_fds[0]).trim().to_string(); let captured_stderr = read_from_fd(pipe_stderr_fds[0]).to_string(); let captured_stderr = captured_stderr @@ -165,6 +202,7 @@ pub fn run_gnu_cmd( cmd_path: &str, args: &[OsString], check_gnu: bool, + pipe_input: Option<&str>, ) -> Result { if check_gnu { match is_gnu_cmd(cmd_path) { @@ -185,18 +223,40 @@ pub fn run_gnu_cmd( command.arg(arg); } - let output = match command.output() { - Ok(output) => output, - Err(e) => { - return Err(CommandResult { - stdout: String::new(), - stderr: e.to_string(), - exit_code: -1, - }); + let output = if let Some(input_str) = pipe_input { + // We have an pipe input + command.stdin(Stdio::piped()).stdout(Stdio::piped()); + + let mut child = command.spawn().expect("Failed to execute command"); + let child_stdin = child.stdin.as_mut().unwrap(); + child_stdin + .write_all(input_str.as_bytes()) + .expect("Failed to write to stdin"); + + match child.wait_with_output() { + Ok(output) => output, + Err(e) => { + return Err(CommandResult { + stdout: String::new(), + stderr: e.to_string(), + exit_code: -1, + }); + } + } + } else { + // Just run with args + match command.output() { + Ok(output) => output, + Err(e) => { + return Err(CommandResult { + stdout: String::new(), + stderr: e.to_string(), + exit_code: -1, + }); + } } }; let exit_code = output.status.code().unwrap_or(-1); - // Here we get stdout and stderr as Strings let stdout = String::from_utf8_lossy(&output.stdout).to_string(); let stderr = String::from_utf8_lossy(&output.stderr).to_string(); @@ -233,12 +293,16 @@ pub fn run_gnu_cmd( pub fn compare_result( test_type: &str, input: &str, + pipe_input: Option<&str>, rust_result: &CommandResult, gnu_result: &CommandResult, fail_on_stderr_diff: bool, ) { println!("Test Type: {}", test_type); println!("Input: {}", input); + if let Some(pipe) = pipe_input { + println!("Pipe: {}", pipe); + } let mut discrepancies = Vec::new(); let mut should_panic = false; diff --git a/fuzz/fuzz_targets/fuzz_echo.rs b/fuzz/fuzz_targets/fuzz_echo.rs index bc391bd3e..3f15b257e 100644 --- a/fuzz/fuzz_targets/fuzz_echo.rs +++ b/fuzz/fuzz_targets/fuzz_echo.rs @@ -59,9 +59,9 @@ fuzz_target!(|_data: &[u8]| { let echo_input = generate_echo(); let mut args = vec![OsString::from("echo")]; args.extend(echo_input.split_whitespace().map(OsString::from)); - let rust_result = generate_and_run_uumain(&args, uumain); + let rust_result = generate_and_run_uumain(&args, uumain, None); - let gnu_result = match run_gnu_cmd(CMD_PATH, &args[1..], false) { + let gnu_result = match run_gnu_cmd(CMD_PATH, &args[1..], false, None) { Ok(result) => result, Err(error_result) => { eprintln!("Failed to run GNU command:"); @@ -78,6 +78,7 @@ fuzz_target!(|_data: &[u8]| { compare_result( "echo", &format!("{:?}", &args[1..]), + None, &rust_result, &gnu_result, true, diff --git a/fuzz/fuzz_targets/fuzz_expr.rs b/fuzz/fuzz_targets/fuzz_expr.rs index 9f2d28603..8bc18fae4 100644 --- a/fuzz/fuzz_targets/fuzz_expr.rs +++ b/fuzz/fuzz_targets/fuzz_expr.rs @@ -69,9 +69,9 @@ fuzz_target!(|_data: &[u8]| { // because uutils expr doesn't support localization yet // TODO remove once uutils expr supports localization env::set_var("LC_COLLATE", "C"); - let rust_result = generate_and_run_uumain(&args, uumain); + let rust_result = generate_and_run_uumain(&args, uumain, None); - let gnu_result = match run_gnu_cmd(CMD_PATH, &args[1..], false) { + let gnu_result = match run_gnu_cmd(CMD_PATH, &args[1..], false, None) { Ok(result) => result, Err(error_result) => { eprintln!("Failed to run GNU command:"); @@ -88,6 +88,7 @@ fuzz_target!(|_data: &[u8]| { compare_result( "expr", &format!("{:?}", &args[1..]), + None, &rust_result, &gnu_result, false, // Set to true if you want to fail on stderr diff diff --git a/fuzz/fuzz_targets/fuzz_printf.rs b/fuzz/fuzz_targets/fuzz_printf.rs index 25cf58c8e..72fac540b 100644 --- a/fuzz/fuzz_targets/fuzz_printf.rs +++ b/fuzz/fuzz_targets/fuzz_printf.rs @@ -80,9 +80,9 @@ fuzz_target!(|_data: &[u8]| { let printf_input = generate_printf(); let mut args = vec![OsString::from("printf")]; args.extend(printf_input.split_whitespace().map(OsString::from)); - let rust_result = generate_and_run_uumain(&args, uumain); + let rust_result = generate_and_run_uumain(&args, uumain, None); - let gnu_result = match run_gnu_cmd(CMD_PATH, &args[1..], false) { + let gnu_result = match run_gnu_cmd(CMD_PATH, &args[1..], false, None) { Ok(result) => result, Err(error_result) => { eprintln!("Failed to run GNU command:"); @@ -99,6 +99,7 @@ fuzz_target!(|_data: &[u8]| { compare_result( "printf", &format!("{:?}", &args[1..]), + None, &rust_result, &gnu_result, false, // Set to true if you want to fail on stderr diff diff --git a/fuzz/fuzz_targets/fuzz_seq.rs b/fuzz/fuzz_targets/fuzz_seq.rs index fdeaed16d..7bb4f8af9 100644 --- a/fuzz/fuzz_targets/fuzz_seq.rs +++ b/fuzz/fuzz_targets/fuzz_seq.rs @@ -48,9 +48,9 @@ fuzz_target!(|_data: &[u8]| { let mut args = vec![OsString::from("seq")]; args.extend(seq.split_whitespace().map(OsString::from)); - let rust_result = generate_and_run_uumain(&args, uumain); + let rust_result = generate_and_run_uumain(&args, uumain, None); - let gnu_result = match run_gnu_cmd(CMD_PATH, &args[1..], false) { + let gnu_result = match run_gnu_cmd(CMD_PATH, &args[1..], false, None) { Ok(result) => result, Err(error_result) => { eprintln!("Failed to run GNU command:"); @@ -67,6 +67,7 @@ fuzz_target!(|_data: &[u8]| { compare_result( "seq", &format!("{:?}", &args[1..]), + None, &rust_result, &gnu_result, false, // Set to true if you want to fail on stderr diff diff --git a/fuzz/fuzz_targets/fuzz_test.rs b/fuzz/fuzz_targets/fuzz_test.rs index eeee2dc3e..bed7ca770 100644 --- a/fuzz/fuzz_targets/fuzz_test.rs +++ b/fuzz/fuzz_targets/fuzz_test.rs @@ -184,9 +184,9 @@ fuzz_target!(|_data: &[u8]| { args.push(OsString::from(generate_test_arg())); } - let rust_result = generate_and_run_uumain(&args, uumain); + let rust_result = generate_and_run_uumain(&args, uumain, None); - let gnu_result = match run_gnu_cmd(CMD_PATH, &args[1..], false) { + let gnu_result = match run_gnu_cmd(CMD_PATH, &args[1..], false, None) { Ok(result) => result, Err(error_result) => { eprintln!("Failed to run GNU command:"); @@ -203,6 +203,7 @@ fuzz_target!(|_data: &[u8]| { compare_result( "test", &format!("{:?}", &args[1..]), + None, &rust_result, &gnu_result, false, // Set to true if you want to fail on stderr diff