Skip to content
Merged
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
60 changes: 50 additions & 10 deletions crates/bashkit/src/builtins/awk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2490,7 +2490,14 @@ impl AwkInterpreter {
}
let format = self.eval_expr(&args[0]).as_string();
let values: Vec<AwkValue> = args[1..].iter().map(|a| self.eval_expr(a)).collect();
AwkValue::String(self.format_string(&format, &values))
match self.format_string(&format, &values) {
Ok(s) => AwkValue::String(s),
Err(e) => {
self.stderr_output.push_str(&e);
self.stderr_output.push('\n');
AwkValue::String(String::new())
}
}
}
"toupper" => {
if args.is_empty() {
Expand Down Expand Up @@ -2768,7 +2775,14 @@ impl AwkInterpreter {
return_value
}

fn format_string(&self, format: &str, values: &[AwkValue]) -> String {
/// Max width/precision for format specifiers to prevent memory exhaustion
const MAX_FORMAT_WIDTH: usize = 10000;

fn format_string(
&self,
format: &str,
values: &[AwkValue],
) -> std::result::Result<String, String> {
let mut result = String::new();
let mut chars = format.chars().peekable();
let mut value_idx = 0;
Expand Down Expand Up @@ -2839,8 +2853,17 @@ impl AwkInterpreter {
break;
}
}
if !w.is_empty() {
width = w.parse().ok();
if !w.is_empty()
&& let Ok(w_val) = w.parse::<usize>()
{
if w_val > Self::MAX_FORMAT_WIDTH {
return Err(format!(
"awk: format width {} exceeds maximum ({})",
w_val,
Self::MAX_FORMAT_WIDTH
));
}
width = Some(w_val);
}

// Parse precision
Expand All @@ -2857,8 +2880,17 @@ impl AwkInterpreter {
}
precision = if p.is_empty() {
Some(0)
} else if let Ok(p_val) = p.parse::<usize>() {
if p_val > Self::MAX_FORMAT_WIDTH {
return Err(format!(
"awk: format precision {} exceeds maximum ({})",
p_val,
Self::MAX_FORMAT_WIDTH
));
}
Some(p_val)
} else {
p.parse().ok()
None
};
}

Expand Down Expand Up @@ -2967,7 +2999,7 @@ impl AwkInterpreter {
}
}

result
Ok(result)
}

/// Total bytes buffered across all output streams.
Expand Down Expand Up @@ -3026,11 +3058,19 @@ impl AwkInterpreter {
AwkAction::Printf(format_expr, args, target) => {
let format_str = self.eval_expr(format_expr).as_string();
let values: Vec<AwkValue> = args.iter().map(|a| self.eval_expr(a)).collect();
let text = self.format_string(&format_str, &values);
if !self.write_output(&text, target) {
return AwkFlow::Exit(Some(2));
match self.format_string(&format_str, &values) {
Ok(text) => {
if !self.write_output(&text, target) {
return AwkFlow::Exit(Some(2));
}
AwkFlow::Continue
}
Err(e) => {
self.stderr_output.push_str(&e);
self.stderr_output.push('\n');
AwkFlow::Exit(Some(2))
}
}
AwkFlow::Continue
}
AwkAction::Assign(name, expr) => {
let value = self.eval_expr(expr);
Expand Down
14 changes: 10 additions & 4 deletions crates/bashkit/src/builtins/printf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ impl Builtin for Printf {
}

/// Parsed format specification
// Max precision to prevent panics in Rust's format! macro (u16::MAX limit)
const MAX_PRECISION: usize = 10000;
// Max width/precision to prevent memory exhaustion from huge format specifiers
const MAX_FORMAT_WIDTH: usize = 10000;

struct FormatSpec {
left_align: bool,
Expand Down Expand Up @@ -119,7 +119,10 @@ impl FormatSpec {
let width = if width_str.is_empty() {
None
} else {
width_str.parse().ok()
width_str
.parse()
.ok()
.map(|w: usize| w.min(MAX_FORMAT_WIDTH))
};

// Parse precision
Expand All @@ -140,7 +143,10 @@ impl FormatSpec {
if prec_str.is_empty() {
Some(0)
} else {
prec_str.parse().ok().map(|p: usize| p.min(MAX_PRECISION))
prec_str
.parse()
.ok()
.map(|p: usize| p.min(MAX_FORMAT_WIDTH))
}
} else {
None
Expand Down
33 changes: 33 additions & 0 deletions crates/bashkit/tests/spec_cases/bash/awk-printf-width.test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
### awk_printf_huge_width_rejected
### bash_diff: bashkit caps printf width to prevent OOM; real bash allows unlimited width
# printf with enormous width should error, not OOM
echo "" | awk '{printf "%999999999d", 1}' 2>&1
echo "exit: $?"
### expect
awk: format width 999999999 exceeds maximum (10000)
exit: 2
### end

### awk_printf_normal_width_works
# Normal width should still work
echo "" | awk '{printf "%20d\n", 42}'
### expect
42
### end

### awk_printf_max_width_works
# Width at limit boundary should work
echo "" | awk '{printf "%10000d\n", 1}' | wc -c
### expect
10001
### end

### awk_printf_huge_precision_rejected
### bash_diff: bashkit caps printf precision to prevent OOM; real bash allows unlimited precision
# Huge precision should also error
echo "" | awk '{printf "%.999999999f", 1}' 2>&1
echo "exit: $?"
### expect
awk: format precision 999999999 exceeds maximum (10000)
exit: 2
### end
Loading