Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fuzz: add the capability to pipe info into fuzz #5668

Merged
merged 3 commits into from Dec 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions fuzz/Cargo.toml
Expand Up @@ -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/" }
Expand Down
106 changes: 85 additions & 21 deletions fuzz/fuzz_targets/fuzz_common.rs
Expand Up @@ -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};

Expand Down Expand Up @@ -49,7 +50,11 @@ pub fn is_gnu_cmd(cmd_path: &str) -> Result<(), std::io::Error> {
}
}

pub fn generate_and_run_uumain<F>(args: &[OsString], uumain_function: F) -> CommandResult
pub fn generate_and_run_uumain<F>(
args: &[OsString],
uumain_function: F,
pipe_input: Option<&str>,
) -> CommandResult
where
F: FnOnce(std::vec::IntoIter<OsString>) -> i32,
{
Expand All @@ -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,
};
}
Expand All @@ -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,
};
}
Expand All @@ -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();
Expand All @@ -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,
};
}
Expand All @@ -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
Expand Down Expand Up @@ -165,6 +202,7 @@ pub fn run_gnu_cmd(
cmd_path: &str,
args: &[OsString],
check_gnu: bool,
pipe_input: Option<&str>,
) -> Result<CommandResult, CommandResult> {
if check_gnu {
match is_gnu_cmd(cmd_path) {
Expand All @@ -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();
Expand Down Expand Up @@ -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;
Expand Down
5 changes: 3 additions & 2 deletions fuzz/fuzz_targets/fuzz_echo.rs
Expand Up @@ -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:");
Expand All @@ -78,6 +78,7 @@ fuzz_target!(|_data: &[u8]| {
compare_result(
"echo",
&format!("{:?}", &args[1..]),
None,
&rust_result,
&gnu_result,
true,
Expand Down
5 changes: 3 additions & 2 deletions fuzz/fuzz_targets/fuzz_expr.rs
Expand Up @@ -67,9 +67,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:");
Expand All @@ -86,6 +86,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
Expand Down
5 changes: 3 additions & 2 deletions fuzz/fuzz_targets/fuzz_printf.rs
Expand Up @@ -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:");
Expand All @@ -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
Expand Down
5 changes: 3 additions & 2 deletions fuzz/fuzz_targets/fuzz_seq.rs
Expand Up @@ -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:");
Expand All @@ -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
Expand Down
5 changes: 3 additions & 2 deletions fuzz/fuzz_targets/fuzz_test.rs
Expand Up @@ -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:");
Expand All @@ -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
Expand Down