diff --git a/crates/bashkit/src/builtins/printf.rs b/crates/bashkit/src/builtins/printf.rs index a0e96c05..901301bd 100644 --- a/crates/bashkit/src/builtins/printf.rs +++ b/crates/bashkit/src/builtins/printf.rs @@ -291,6 +291,7 @@ struct CapSpec { position: CapArgLocation, width: Option, precision: Option, + specifier: u8, } enum CapValue { @@ -308,11 +309,45 @@ impl CapSpec { let precision = resolve_cap_value(precision, args, false); reject_over_cap("precision", precision)?; } - args.consume(self.position); + if is_float_specifier(self.specifier) { + let value = args.next_arg(self.position).unwrap_or_default(); + reject_float_exponent_over_cap(value)?; + } else { + args.consume(self.position); + } Ok(()) } } +fn is_float_specifier(specifier: u8) -> bool { + matches!( + specifier, + b'f' | b'F' | b'e' | b'E' | b'g' | b'G' | b'a' | b'A' + ) +} + +fn reject_float_exponent_over_cap(value: &str) -> std::result::Result<(), String> { + let Some(exponent) = parse_float_exponent(value) else { + return Ok(()); + }; + let exponent = exponent.unsigned_abs(); + if exponent > MAX_FORMAT_WIDTH as u64 { + return Err(format!( + "printf: format exponent {exponent} exceeds limit {MAX_FORMAT_WIDTH}\n" + )); + } + Ok(()) +} + +fn parse_float_exponent(value: &str) -> Option { + let bytes = value.as_bytes(); + let marker = bytes + .iter() + .rposition(|b| matches!(b, b'e' | b'E' | b'p' | b'P'))?; + let exponent = value.get(marker + 1..)?; + Some(parse_leading_i64(exponent)) +} + fn reject_over_cap(kind: &str, value: usize) -> std::result::Result<(), String> { if value > MAX_FORMAT_WIDTH { return Err(format!( @@ -394,6 +429,7 @@ fn parse_cap_spec(format: &[u8], start: usize) -> Option { position, width, precision, + specifier, }) } @@ -534,6 +570,12 @@ mod tests { assert!(err.contains("width 999999 exceeds limit")); } + #[test] + fn rejects_float_exponent_over_cap() { + let err = render_printf("%f", &["1e1000000000".into()]).unwrap_err(); + assert!(err.contains("exponent 1000000000 exceeds limit")); + } + #[tokio::test] async fn no_leak_printf_format_errors() { let r = crate::builtins::debug_leak_check::run("printf '%10001s' x").await;