diff --git a/.github/workflows/fuzzing.yml b/.github/workflows/fuzzing.yml index cbb0574dee..2274f6905c 100644 --- a/.github/workflows/fuzzing.yml +++ b/.github/workflows/fuzzing.yml @@ -41,6 +41,7 @@ jobs: # https://github.com/uutils/coreutils/issues/5311 - { name: fuzz_date, should_pass: false } - { name: fuzz_expr, should_pass: true } + - { name: fuzz_printf, should_pass: false } - { name: fuzz_parse_glob, should_pass: true } - { name: fuzz_parse_size, should_pass: true } - { name: fuzz_parse_time, should_pass: true } diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 549f9a6b76..630af4650b 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -16,6 +16,7 @@ uucore = { path = "../src/uucore/" } uu_date = { path = "../src/uu/date/" } uu_test = { path = "../src/uu/test/" } uu_expr = { path = "../src/uu/expr/" } +uu_printf = { path = "../src/uu/printf/" } # Prevent this from interfering with workspaces @@ -28,6 +29,12 @@ path = "fuzz_targets/fuzz_date.rs" test = false doc = false +[[bin]] +name = "fuzz_printf" +path = "fuzz_targets/fuzz_printf.rs" +test = false +doc = false + [[bin]] name = "fuzz_expr" path = "fuzz_targets/fuzz_expr.rs" diff --git a/fuzz/fuzz_targets/fuzz_printf.rs b/fuzz/fuzz_targets/fuzz_printf.rs new file mode 100644 index 0000000000..78bb3e3ce3 --- /dev/null +++ b/fuzz/fuzz_targets/fuzz_printf.rs @@ -0,0 +1,110 @@ +// This file is part of the uutils coreutils package. +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. +// spell-checker:ignore parens + +#![no_main] +use libfuzzer_sys::fuzz_target; +use uu_printf::uumain; + +use rand::seq::SliceRandom; +use rand::Rng; +use std::ffi::OsString; + +mod fuzz_common; +use crate::fuzz_common::CommandResult; +use crate::fuzz_common::{ + compare_result, generate_and_run_uumain, generate_random_string, run_gnu_cmd, +}; + +static CMD_PATH: &str = "printf"; + +fn generate_escape_sequence(rng: &mut impl Rng) -> String { + let escape_sequences = [ + "\\\"", + "\\\\", + "\\a", + "\\b", + "\\c", + "\\e", + "\\f", + "\\n", + "\\r", + "\\t", + "\\v", + "\\000", + "\\x00", + "\\u0000", + "\\U00000000", + "%%", + ]; + escape_sequences.choose(rng).unwrap().to_string() +} + +fn generate_printf() -> String { + let mut rng = rand::thread_rng(); + let format_specifiers = ["%s", "%d", "%f", "%x", "%o", "%c", "%b", "%q"]; + let mut printf_str = String::new(); + // Add a 20% chance of generating an invalid format specifier + if rng.gen_bool(0.2) { + printf_str.push_str("%z"); // Invalid format specifier + } else { + let specifier = *format_specifiers.choose(&mut rng).unwrap(); + printf_str.push_str(specifier); + + // Add a 20% chance of introducing complex format strings + if rng.gen_bool(0.2) { + printf_str.push_str(&format!(" %{}", rng.gen_range(1..=1000))); + } else { + // Add a random string or number after the specifier + if specifier == "%s" { + printf_str.push_str(&format!( + " {}", + generate_random_string(rng.gen_range(1..=10)) + )); + } else { + printf_str.push_str(&format!(" {}", rng.gen_range(1..=1000))); + } + } + } + + // Add a 10% chance of including an escape sequence + if rng.gen_bool(0.1) { + printf_str.push_str(&generate_escape_sequence(&mut rng)); + } + printf_str +} + +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 gnu_result = match run_gnu_cmd(CMD_PATH, &args[1..], false) { + Ok(result) => result, + Err(error_result) => { + eprintln!("Failed to run GNU command:"); + eprintln!("Stderr: {}", error_result.stderr); + eprintln!("Exit Code: {}", error_result.exit_code); + CommandResult { + stdout: String::new(), + stderr: error_result.stderr, + exit_code: error_result.exit_code, + } + } + }; + + compare_result( + "printf", + &format!("{:?}", &args[1..]), + &rust_result.stdout, + &gnu_result.stdout, + &rust_result.stderr, + &gnu_result.stderr, + rust_result.exit_code, + gnu_result.exit_code, + false, // Set to true if you want to fail on stderr diff + ); +});