Add non-UTF-8 filename support to tee and improve fuzzer coverage

This commit is contained in:
Sylvestre Ledru
2025-08-14 10:52:24 +02:00
parent b67d04557e
commit 9f0447cbe9
2 changed files with 74 additions and 13 deletions
+65 -6
View File
@@ -17,8 +17,22 @@ use std::path::PathBuf;
use uufuzz::{run_gnu_cmd, CommandResult};
// Programs that typically take file/path arguments and should be tested
static PATH_PROGRAMS: &[&str] = &[
"basename", "cat", "chmod", "cp", "dirname", "du", "head", "ln", "ls", "mkdir", "mv",
"readlink", "realpath", "rm", "rmdir", "tail", "touch", "unlink",
// Core file operations
"cat", "cp", "mv", "rm", "ln", "link", "unlink", "touch", "truncate",
// Directory operations
"ls", "mkdir", "rmdir", "du", "stat", "mktemp",
// Path operations
"basename", "dirname", "readlink", "realpath", "pathchk",
// File content operations
"head", "tail", "tee", "more", "od", "wc", "cksum", "sum",
// File processing
"sort", "uniq", "split", "csplit", "cut", "tr", "shred",
// File permissions/ownership
"chmod", "chown", "chgrp", "install",
// Text processing with files
"comm", "join", "paste", "pr", "fmt", "fold", "expand", "unexpand",
// Directory listing variants
"dir", "vdir",
];
fn generate_non_utf8_bytes() -> Vec<u8> {
@@ -84,20 +98,65 @@ fn test_program_with_non_utf8_path(program: &str, path: &PathBuf) -> CommandResu
// Build appropriate arguments for each program
let local_args = match program {
// Programs that need mode/permissions
"chmod" => vec![
OsString::from(program),
OsString::from("644"),
path_os.to_owned(),
],
"cp" | "mv" | "ln" => {
// These need a destination - create a temp destination
"chown" => vec![
OsString::from(program),
OsString::from("root:root"),
path_os.to_owned(),
],
"chgrp" => vec![
OsString::from(program),
OsString::from("root"),
path_os.to_owned(),
],
// Programs that need source and destination
"cp" | "mv" | "ln" | "link" => {
let dest_path = path.with_extension("dest");
vec![
OsString::from(program),
path_os.to_owned(),
dest_path.as_os_str().to_owned(),
]
}
},
"install" => {
let dest_path = path.with_extension("dest");
vec![
OsString::from(program),
path_os.to_owned(),
dest_path.as_os_str().to_owned(),
]
},
// Programs that need size/truncate operations
"truncate" => vec![
OsString::from(program),
OsString::from("--size=0"),
path_os.to_owned(),
],
"split" => vec![
OsString::from(program),
path_os.to_owned(),
OsString::from("split_prefix_"),
],
"csplit" => vec![
OsString::from(program),
path_os.to_owned(),
OsString::from("1"),
],
// Programs that work with multiple files (use just one for testing)
"comm" | "join" => {
// These need two files, use the same file twice for simplicity
vec![
OsString::from(program),
path_os.to_owned(),
path_os.to_owned(),
]
},
// Programs that typically take file input
_ => vec![OsString::from(program), path_os.to_owned()],
};
@@ -175,7 +234,7 @@ fuzz_target!(|_data: &[u8]| {
}
// Special cases for programs that need additional testing
if **program == "mkdir" {
if **program == "mkdir" || **program == "mktemp" {
let non_utf8_dir_name = generate_non_utf8_osstring();
let non_utf8_dir = temp_root.join(non_utf8_dir_name);
+9 -7
View File
@@ -6,6 +6,7 @@
// cSpell:ignore POLLERR POLLRDBAND pfds revents
use clap::{Arg, ArgAction, Command, builder::PossibleValue};
use std::ffi::OsString;
use std::fs::OpenOptions;
use std::io::{Error, ErrorKind, Read, Result, Write, stdin, stdout};
use std::path::PathBuf;
@@ -34,7 +35,7 @@ struct Options {
append: bool,
ignore_interrupts: bool,
ignore_pipe_errors: bool,
files: Vec<String>,
files: Vec<OsString>,
output_error: Option<OutputErrorMode>,
}
@@ -77,8 +78,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
};
let files = matches
.get_many::<String>(options::FILE)
.map(|v| v.map(ToString::to_string).collect())
.get_many::<OsString>(options::FILE)
.map(|v| v.cloned().collect())
.unwrap_or_default();
let options = Options {
@@ -127,7 +128,8 @@ pub fn uu_app() -> Command {
.arg(
Arg::new(options::FILE)
.action(ArgAction::Append)
.value_hint(clap::ValueHint::FilePath),
.value_hint(clap::ValueHint::FilePath)
.value_parser(clap::value_parser!(OsString)),
)
.arg(
Arg::new(options::IGNORE_PIPE_ERRORS)
@@ -252,7 +254,7 @@ fn copy(mut input: impl Read, mut output: impl Write) -> Result<usize> {
/// If that error should lead to program termination, this function returns Some(Err()),
/// otherwise it returns None.
fn open(
name: &str,
name: &OsString,
append: bool,
output_error: Option<&OutputErrorMode>,
) -> Option<Result<NamedWriter>> {
@@ -266,10 +268,10 @@ fn open(
match mode.write(true).create(true).open(path.as_path()) {
Ok(file) => Some(Ok(NamedWriter {
inner: Box::new(file),
name: name.to_owned(),
name: name.to_string_lossy().to_string(),
})),
Err(f) => {
show_error!("{}: {f}", name.maybe_quote());
show_error!("{}: {f}", name.to_string_lossy().maybe_quote());
match output_error {
Some(OutputErrorMode::Exit | OutputErrorMode::ExitNoPipe) => Some(Err(f)),
_ => None,