diff --git a/crates/bashkit/src/interpreter/mod.rs b/crates/bashkit/src/interpreter/mod.rs index d426e7b6..cd8acada 100644 --- a/crates/bashkit/src/interpreter/mod.rs +++ b/crates/bashkit/src/interpreter/mod.rs @@ -6303,8 +6303,14 @@ impl Interpreter { WordPart::Length(name) => { let value = if let Some(bracket_pos) = name.find('[') { let arr_name = &name[..bracket_pos]; - let index_end = name.find(']').unwrap_or(name.len()); - let index_str = &name[bracket_pos + 1..index_end]; + // Search for ']' after '[' to avoid panic when malformed + // input has ']' before '[' (e.g. null-byte-laden fuzz input). + let index_end = name[bracket_pos..] + .find(']') + .map(|i| bracket_pos + i) + .unwrap_or(name.len()); + let start = (bracket_pos + 1).min(index_end); + let index_str = &name[start..index_end]; let idx: usize = self.evaluate_arithmetic(index_str).try_into().unwrap_or(0); if let Some(arr) = self.arrays.get(arr_name) { diff --git a/crates/bashkit/tests/byte_range_panic_tests.rs b/crates/bashkit/tests/byte_range_panic_tests.rs new file mode 100644 index 00000000..13aa28f1 --- /dev/null +++ b/crates/bashkit/tests/byte_range_panic_tests.rs @@ -0,0 +1,55 @@ +// Regression tests for byte range panics in the interpreter. +// Issue #1090: WordPart::Length handler panicked with "byte range starts at 43 +// but ends at 8" when malformed input caused ']' to appear before '[' in the +// name string passed to ${#...}. + +use bashkit::{Bash, ExecutionLimits}; + +/// Fuzz crash input from issue #1090 (decoded from base64). +/// Original artifact: crash-3c5c6ff235787b4ba345b870d35590436d6bc2c1 +#[tokio::test] +async fn fuzz_crash_1090_byte_range_panic() { + use base64::Engine; + let b64 = "JCgAanEkeyMAAAAAAAAAAF0AAAAAADMAAAAAAAAAACQmAAAAAAAAAAAAAAAAAAAAAABbWz0m"; + let decoded = base64::engine::general_purpose::STANDARD + .decode(b64) + .unwrap_or_default(); + let input = String::from_utf8_lossy(&decoded); + let script = format!("echo $(({})) 2>/dev/null", input); + + let mut bash = Bash::builder() + .limits( + ExecutionLimits::new() + .max_commands(100) + .max_function_depth(10) + .max_subst_depth(5) + .max_stdout_bytes(4096) + .max_stderr_bytes(4096) + .timeout(std::time::Duration::from_millis(500)), + ) + .build(); + + // Must not panic — errors are acceptable + let _ = bash.exec(&script).await; +} + +/// Direct test: ${#name} where name contains ']' before '['. +#[tokio::test] +async fn length_with_bracket_before_open() { + let mut bash = Bash::builder().build(); + + // This creates a ${#...} expression where the parser might produce + // a Length node with malformed name containing ']' before '[' + let result = bash.exec("x=']foo[bar'; echo ${#x}").await.unwrap(); + // Should not panic — just returns the length of $x + assert!(result.exit_code == 0); +} + +/// Edge case: ${#arr[idx]} with empty array name. +#[tokio::test] +async fn length_empty_array_name() { + let mut bash = Bash::builder().build(); + let result = bash.exec("echo ${#[0]} 2>/dev/null").await; + // Should not panic — error or empty is acceptable + let _ = result; +}