Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .github/workflows/fuzzing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,7 @@ jobs:
matrix:
test-target:
- { name: fuzz_test, should_pass: true }
# https://github.com/uutils/coreutils/issues/5311
- { name: fuzz_date, should_pass: false }
- { name: fuzz_date, should_pass: true }
- { name: fuzz_expr, should_pass: true }
- { name: fuzz_printf, should_pass: true }
- { name: fuzz_echo, should_pass: true }
Expand Down
32 changes: 29 additions & 3 deletions fuzz/fuzz_targets/fuzz_date.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,38 @@ use libfuzzer_sys::fuzz_target;

use std::ffi::OsString;
use uu_date::uumain;
use uufuzz::generate_and_run_uumain;

fuzz_target!(|data: &[u8]| {
let delim: u8 = 0; // Null byte
let args = data
let fuzz_args: Vec<OsString> = data
.split(|b| *b == delim)
.filter_map(|e| std::str::from_utf8(e).ok())
.map(OsString::from);
uumain(args);
.map(OsString::from)
.collect();

// Skip test cases that would cause the program to read from stdin
// These would hang the fuzzer waiting for input
for i in 0..fuzz_args.len() {
if let Some(arg) = fuzz_args.get(i) {
let arg_str = arg.to_string_lossy();
// Skip if -f- or --file=- (reads dates from stdin)
if (arg_str == "-f"
&& fuzz_args
.get(i + 1)
.map(|a| a.to_string_lossy() == "-")
.unwrap_or(false))
|| arg_str == "-f-"
|| arg_str == "--file=-"
{
return;
}
}
}

// Add program name as first argument (required for proper argument parsing)
let mut args = vec![OsString::from("date")];
args.extend(fuzz_args);

let _ = generate_and_run_uumain(&args, uumain, None);
});
1 change: 1 addition & 0 deletions src/uu/date/locales/en-US.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,4 @@ date-error-date-overflow = date overflow '{$date}'
date-error-setting-date-not-supported-macos = setting the date is not supported by macOS
date-error-setting-date-not-supported-redox = setting the date is not supported by Redox
date-error-cannot-set-date = cannot set date
date-error-extra-operand = extra operand '{$operand}'
1 change: 1 addition & 0 deletions src/uu/date/locales/fr-FR.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,4 @@ date-error-date-overflow = débordement de date '{$date}'
date-error-setting-date-not-supported-macos = la définition de la date n'est pas prise en charge par macOS
date-error-setting-date-not-supported-redox = la définition de la date n'est pas prise en charge par Redox
date-error-cannot-set-date = impossible de définir la date
date-error-extra-operand = opérande supplémentaire '{$operand}'
36 changes: 34 additions & 2 deletions src/uu/date/src/date.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,39 @@ fn parse_military_timezone_with_offset(s: &str) -> Option<i32> {
#[uucore::main]
#[allow(clippy::cognitive_complexity)]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uucore::clap_localization::handle_clap_result(uu_app(), args)?;
let args: Vec<std::ffi::OsString> = args.collect();
let matches = match uu_app().try_get_matches_from(&args) {
Ok(matches) => matches,
Err(e) => {
match e.kind() {
clap::error::ErrorKind::DisplayHelp | clap::error::ErrorKind::DisplayVersion => {
return Err(e.into());
}
_ => {
// Convert unknown options to be treated as invalid date format
// This ensures consistent exit status 1 instead of clap's exit status 77
if let Some(arg) = args.get(1) {
return Err(USimpleError::new(
1,
translate!("date-error-invalid-date", "date" => arg.to_string_lossy()),
));
}
return Err(USimpleError::new(1, e.to_string()));
}
}
}
};

// Check for extra operands (multiple positional arguments)
if let Some(formats) = matches.get_many::<String>(OPT_FORMAT) {
let format_args: Vec<&String> = formats.collect();
if format_args.len() > 1 {
return Err(USimpleError::new(
1,
translate!("date-error-extra-operand", "operand" => format_args[1]),
));
}
}

let format = if let Some(form) = matches.get_one::<String>(OPT_FORMAT) {
if !form.starts_with('+') {
Expand Down Expand Up @@ -513,7 +545,7 @@ pub fn uu_app() -> Command {
.help(translate!("date-help-universal"))
.action(ArgAction::SetTrue),
)
.arg(Arg::new(OPT_FORMAT))
.arg(Arg::new(OPT_FORMAT).num_args(0..).trailing_var_arg(true))
}

/// Return the appropriate format string for the given settings.
Expand Down
39 changes: 39 additions & 0 deletions tests/by-util/test_date.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,45 @@ fn test_invalid_arg() {
new_ucmd!().arg("--definitely-invalid").fails_with_code(1);
}

#[test]
fn test_empty_arguments() {
new_ucmd!().arg("").fails_with_code(1);
new_ucmd!().args(&["", ""]).fails_with_code(1);
new_ucmd!().args(&["", "", ""]).fails_with_code(1);
}

#[test]
fn test_extra_operands() {
new_ucmd!()
.args(&["test", "extra"])
.fails_with_code(1)
.stderr_contains("extra operand 'extra'");
}

#[test]
fn test_invalid_long_option() {
new_ucmd!()
.arg("--fB")
.fails_with_code(1)
.stderr_contains("invalid date '--fB'");
}

#[test]
fn test_invalid_short_option() {
new_ucmd!()
.arg("-w")
.fails_with_code(1)
.stderr_contains("invalid date '-w'");
}

#[test]
fn test_single_dash_as_date() {
new_ucmd!()
.arg("-")
.fails_with_code(1)
.stderr_contains("invalid date");
}

#[test]
fn test_date_email() {
for param in ["--rfc-email", "--rfc-e", "-R", "--rfc-2822", "--rfc-822"] {
Expand Down
Loading