mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 03:27: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:
parent
6510115d9e
commit
546201bd00
7 changed files with 101 additions and 31 deletions
|
@ -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/" }
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue