1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-28 11:37:44 +00:00

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

* fuzz: add the capability to pipe info into fuzz

* address the comments

* show the piped message if any
This commit is contained in:
Sylvestre Ledru 2023-12-21 16:18:00 +01:00 committed by GitHub
parent 6510115d9e
commit 546201bd00
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 101 additions and 31 deletions

View file

@ -10,6 +10,7 @@ cargo-fuzz = true
[dependencies] [dependencies]
libfuzzer-sys = "0.4" libfuzzer-sys = "0.4"
libc = "0.2" libc = "0.2"
tempfile = "3"
rand = { version = "0.8", features = ["small_rng"] } rand = { version = "0.8", features = ["small_rng"] }
uucore = { path = "../src/uucore/" } uucore = { path = "../src/uucore/" }

View file

@ -3,14 +3,15 @@
// For the full copyright and license information, please view the LICENSE // For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code. // file that was distributed with this source code.
use libc::STDIN_FILENO;
use libc::{close, dup, dup2, pipe, STDERR_FILENO, STDOUT_FILENO}; use libc::{close, dup, dup2, pipe, STDERR_FILENO, STDOUT_FILENO};
use rand::prelude::SliceRandom; use rand::prelude::SliceRandom;
use rand::Rng; use rand::Rng;
use std::ffi::OsString; use std::ffi::OsString;
use std::io; use std::io;
use std::io::Write; use std::io::{Seek, SeekFrom, Write};
use std::os::fd::RawFd; use std::os::fd::{AsRawFd, RawFd};
use std::process::Command; use std::process::{Command, Stdio};
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use std::sync::{atomic::AtomicBool, Once}; 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<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 where
F: FnOnce(std::vec::IntoIter<OsString>) -> i32, F: FnOnce(std::vec::IntoIter<OsString>) -> i32,
{ {
@ -58,8 +63,8 @@ where
let original_stderr_fd = unsafe { dup(STDERR_FILENO) }; let original_stderr_fd = unsafe { dup(STDERR_FILENO) };
if original_stdout_fd == -1 || original_stderr_fd == -1 { if original_stdout_fd == -1 || original_stderr_fd == -1 {
return CommandResult { return CommandResult {
stdout: "Failed to duplicate STDOUT_FILENO or STDERR_FILENO".to_string(), stdout: "".to_string(),
stderr: "".to_string(), stderr: "Failed to duplicate STDOUT_FILENO or STDERR_FILENO".to_string(),
exit_code: -1, exit_code: -1,
}; };
} }
@ -72,8 +77,8 @@ where
|| unsafe { pipe(pipe_stderr_fds.as_mut_ptr()) } == -1 || unsafe { pipe(pipe_stderr_fds.as_mut_ptr()) } == -1
{ {
return CommandResult { return CommandResult {
stdout: "Failed to create pipes".to_string(), stdout: "".to_string(),
stderr: "".to_string(), stderr: "Failed to create pipes".to_string(),
exit_code: -1, exit_code: -1,
}; };
} }
@ -89,12 +94,32 @@ where
close(pipe_stderr_fds[1]); close(pipe_stderr_fds[1]);
} }
return CommandResult { return CommandResult {
stdout: "Failed to redirect STDOUT_FILENO or STDERR_FILENO".to_string(), stdout: "".to_string(),
stderr: "".to_string(), stderr: "Failed to redirect STDOUT_FILENO or STDERR_FILENO".to_string(),
exit_code: -1, 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()); let uumain_exit_status = uumain_function(args.to_owned().into_iter());
io::stdout().flush().unwrap(); io::stdout().flush().unwrap();
@ -105,8 +130,8 @@ where
|| unsafe { dup2(original_stderr_fd, STDERR_FILENO) } == -1 || unsafe { dup2(original_stderr_fd, STDERR_FILENO) } == -1
{ {
return CommandResult { return CommandResult {
stdout: "Failed to restore the original STDOUT_FILENO or STDERR_FILENO".to_string(), stdout: "".to_string(),
stderr: "".to_string(), stderr: "Failed to restore the original STDOUT_FILENO or STDERR_FILENO".to_string(),
exit_code: -1, exit_code: -1,
}; };
} }
@ -118,6 +143,18 @@ where
close(pipe_stderr_fds[1]); 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_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 = read_from_fd(pipe_stderr_fds[0]).to_string();
let captured_stderr = captured_stderr let captured_stderr = captured_stderr
@ -165,6 +202,7 @@ pub fn run_gnu_cmd(
cmd_path: &str, cmd_path: &str,
args: &[OsString], args: &[OsString],
check_gnu: bool, check_gnu: bool,
pipe_input: Option<&str>,
) -> Result<CommandResult, CommandResult> { ) -> Result<CommandResult, CommandResult> {
if check_gnu { if check_gnu {
match is_gnu_cmd(cmd_path) { match is_gnu_cmd(cmd_path) {
@ -185,18 +223,40 @@ pub fn run_gnu_cmd(
command.arg(arg); command.arg(arg);
} }
let output = match command.output() { let output = if let Some(input_str) = pipe_input {
Ok(output) => output, // We have an pipe input
Err(e) => { command.stdin(Stdio::piped()).stdout(Stdio::piped());
return Err(CommandResult {
stdout: String::new(), let mut child = command.spawn().expect("Failed to execute command");
stderr: e.to_string(), let child_stdin = child.stdin.as_mut().unwrap();
exit_code: -1, 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); let exit_code = output.status.code().unwrap_or(-1);
// Here we get stdout and stderr as Strings // Here we get stdout and stderr as Strings
let stdout = String::from_utf8_lossy(&output.stdout).to_string(); let stdout = String::from_utf8_lossy(&output.stdout).to_string();
let stderr = String::from_utf8_lossy(&output.stderr).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( pub fn compare_result(
test_type: &str, test_type: &str,
input: &str, input: &str,
pipe_input: Option<&str>,
rust_result: &CommandResult, rust_result: &CommandResult,
gnu_result: &CommandResult, gnu_result: &CommandResult,
fail_on_stderr_diff: bool, fail_on_stderr_diff: bool,
) { ) {
println!("Test Type: {}", test_type); println!("Test Type: {}", test_type);
println!("Input: {}", input); println!("Input: {}", input);
if let Some(pipe) = pipe_input {
println!("Pipe: {}", pipe);
}
let mut discrepancies = Vec::new(); let mut discrepancies = Vec::new();
let mut should_panic = false; let mut should_panic = false;

View file

@ -59,9 +59,9 @@ fuzz_target!(|_data: &[u8]| {
let echo_input = generate_echo(); let echo_input = generate_echo();
let mut args = vec![OsString::from("echo")]; let mut args = vec![OsString::from("echo")];
args.extend(echo_input.split_whitespace().map(OsString::from)); 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, Ok(result) => result,
Err(error_result) => { Err(error_result) => {
eprintln!("Failed to run GNU command:"); eprintln!("Failed to run GNU command:");
@ -78,6 +78,7 @@ fuzz_target!(|_data: &[u8]| {
compare_result( compare_result(
"echo", "echo",
&format!("{:?}", &args[1..]), &format!("{:?}", &args[1..]),
None,
&rust_result, &rust_result,
&gnu_result, &gnu_result,
true, true,

View file

@ -69,9 +69,9 @@ fuzz_target!(|_data: &[u8]| {
// because uutils expr doesn't support localization yet // because uutils expr doesn't support localization yet
// TODO remove once uutils expr supports localization // TODO remove once uutils expr supports localization
env::set_var("LC_COLLATE", "C"); 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, Ok(result) => result,
Err(error_result) => { Err(error_result) => {
eprintln!("Failed to run GNU command:"); eprintln!("Failed to run GNU command:");
@ -88,6 +88,7 @@ fuzz_target!(|_data: &[u8]| {
compare_result( compare_result(
"expr", "expr",
&format!("{:?}", &args[1..]), &format!("{:?}", &args[1..]),
None,
&rust_result, &rust_result,
&gnu_result, &gnu_result,
false, // Set to true if you want to fail on stderr diff false, // Set to true if you want to fail on stderr diff

View file

@ -80,9 +80,9 @@ fuzz_target!(|_data: &[u8]| {
let printf_input = generate_printf(); let printf_input = generate_printf();
let mut args = vec![OsString::from("printf")]; let mut args = vec![OsString::from("printf")];
args.extend(printf_input.split_whitespace().map(OsString::from)); 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, Ok(result) => result,
Err(error_result) => { Err(error_result) => {
eprintln!("Failed to run GNU command:"); eprintln!("Failed to run GNU command:");
@ -99,6 +99,7 @@ fuzz_target!(|_data: &[u8]| {
compare_result( compare_result(
"printf", "printf",
&format!("{:?}", &args[1..]), &format!("{:?}", &args[1..]),
None,
&rust_result, &rust_result,
&gnu_result, &gnu_result,
false, // Set to true if you want to fail on stderr diff false, // Set to true if you want to fail on stderr diff

View file

@ -48,9 +48,9 @@ fuzz_target!(|_data: &[u8]| {
let mut args = vec![OsString::from("seq")]; let mut args = vec![OsString::from("seq")];
args.extend(seq.split_whitespace().map(OsString::from)); 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, Ok(result) => result,
Err(error_result) => { Err(error_result) => {
eprintln!("Failed to run GNU command:"); eprintln!("Failed to run GNU command:");
@ -67,6 +67,7 @@ fuzz_target!(|_data: &[u8]| {
compare_result( compare_result(
"seq", "seq",
&format!("{:?}", &args[1..]), &format!("{:?}", &args[1..]),
None,
&rust_result, &rust_result,
&gnu_result, &gnu_result,
false, // Set to true if you want to fail on stderr diff false, // Set to true if you want to fail on stderr diff

View file

@ -184,9 +184,9 @@ fuzz_target!(|_data: &[u8]| {
args.push(OsString::from(generate_test_arg())); 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, Ok(result) => result,
Err(error_result) => { Err(error_result) => {
eprintln!("Failed to run GNU command:"); eprintln!("Failed to run GNU command:");
@ -203,6 +203,7 @@ fuzz_target!(|_data: &[u8]| {
compare_result( compare_result(
"test", "test",
&format!("{:?}", &args[1..]), &format!("{:?}", &args[1..]),
None,
&rust_result, &rust_result,
&gnu_result, &gnu_result,
false, // Set to true if you want to fail on stderr diff false, // Set to true if you want to fail on stderr diff