diff --git a/CHANGELOG.md b/CHANGELOG.md index 52b09d9f3..377520cdd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ - Add `--exact` option to match the entire filename exactly (literal, non-substring). ## Bugfixes +- Reject `--threads` values above 64 instead of panicking during resource + allocation, see #2078 (@Sushanth012). - Sanitize control characters and bidirectional override characters in filenames when output goes to a terminal, to prevent terminal escape-sequence injection. Also reject a placeholder as the executable for `--exec-batch`, while still diff --git a/src/cli.rs b/src/cli.rs index 4cd54a88a..11bb22a29 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -18,6 +18,8 @@ use crate::filesystem; use crate::filter::OwnerFilter; use crate::filter::SizeFilter; +const MAX_NUM_THREADS: usize = 64; + #[derive(Parser)] #[command( name = "fd", @@ -553,7 +555,7 @@ pub struct Opts { /// Set number of threads to use for searching & executing (default: number /// of available CPU cores) - #[arg(long, short = 'j', value_name = "num", hide_short_help = true, value_parser = str::parse::)] + #[arg(long, short = 'j', value_name = "num", hide_short_help = true, value_parser = parse_num_threads)] pub threads: Option, /// Milliseconds to buffer before streaming search results to console @@ -792,13 +794,25 @@ fn default_num_threads() -> NonZeroUsize { let fallback = NonZeroUsize::MIN; // To limit startup overhead on massively parallel machines, don't use more // than 64 threads. - let limit = NonZeroUsize::new(64).unwrap(); + let limit = NonZeroUsize::new(MAX_NUM_THREADS).unwrap(); std::thread::available_parallelism() .unwrap_or(fallback) .min(limit) } +fn parse_num_threads(arg: &str) -> Result { + let threads = arg + .parse::() + .map_err(|error| error.to_string())?; + if threads.get() > MAX_NUM_THREADS { + return Err(format!( + "the number of threads cannot exceed {MAX_NUM_THREADS}" + )); + } + Ok(threads) +} + #[derive(Copy, Clone, PartialEq, Eq, ValueEnum)] pub enum FileType { #[value(alias = "f")] @@ -969,3 +983,15 @@ fn ensure_current_directory_exists(current_directory: &Path) -> anyhow::Result<( )) } } + +#[cfg(test)] +mod tests { + use super::parse_num_threads; + + #[test] + fn number_of_threads_is_bounded() { + assert_eq!(parse_num_threads("64").unwrap().get(), 64); + assert!(parse_num_threads("65").is_err()); + assert!(parse_num_threads("9223372036854775807").is_err()); + } +} diff --git a/tests/tests.rs b/tests/tests.rs index 191447b5e..48901386f 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -2659,6 +2659,8 @@ fn test_number_parsing_errors() { te.assert_failure(&["--threads=a"]); te.assert_failure(&["-j", ""]); te.assert_failure(&["--threads=0"]); + te.assert_failure(&["--threads=65"]); + te.assert_failure(&["--threads=9223372036854775807"]); te.assert_failure(&["--min-depth=a"]); te.assert_failure(&["--mindepth=a"]);