summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSylvestre Ledru <sylvestre@debian.org>2023-12-21 16:18:00 +0100
committerGitHub <noreply@github.com>2023-12-21 16:18:00 +0100
commit546201bd00c913ead6f75d983c0dfabd2b921348 (patch)
tree4aa0b96db1c1fe637f5cf8609d7786c79cfc7978
parent6510115d9ec26602afd6b39511f37ae0d91820c2 (diff)
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
-rw-r--r--fuzz/Cargo.toml1
-rw-r--r--fuzz/fuzz_targets/fuzz_common.rs106
-rw-r--r--fuzz/fuzz_targets/fuzz_echo.rs5
-rw-r--r--fuzz/fuzz_targets/fuzz_expr.rs5
-rw-r--r--fuzz/fuzz_targets/fuzz_printf.rs5
-rw-r--r--fuzz/fuzz_targets/fuzz_seq.rs5
-rw-r--r--fuzz/fuzz_targets/fuzz_test.rs5
7 files changed, 101 insertions, 31 deletions
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<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,
{
@@ -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<CommandResult, CommandResult> {
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