Skip to content

Commit 004b5d6

Browse files
chaliyclaude
andauthored
fix(interpreter): prevent arithmetic overflow panics (#443)
## Summary - Replace panic-prone arithmetic operators with wrapping variants - Clamp exponent to 0..63, shifts to 0..63 - Use wrapping_div/rem for i64::MIN/-1 edge case - Use wrapping_add/sub/mul/neg for overflow safety ## Test plan - [x] 8 new tests covering exponent, shift, div/mod, add/mul overflow - [x] All 1439 existing tests pass - [x] clippy clean Closes #405 Co-authored-by: Claude <noreply@anthropic.com>
1 parent a39471c commit 004b5d6

File tree

1 file changed

+69
-9
lines changed
  • crates/bashkit/src/interpreter

1 file changed

+69
-9
lines changed

crates/bashkit/src/interpreter/mod.rs

Lines changed: 69 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7340,7 +7340,9 @@ impl Interpreter {
73407340
{
73417341
let left = self.parse_arithmetic_impl(&expr[..i - 1], arith_depth + 1);
73427342
let right = self.parse_arithmetic_impl(&expr[i + 1..], arith_depth + 1);
7343-
return left << right;
7343+
// THREAT[TM-DOS-029]: clamp shift to 0..=63 to prevent panic
7344+
let shift = right.clamp(0, 63) as u32;
7345+
return left.wrapping_shl(shift);
73447346
}
73457347
'>' if depth == 0
73467348
&& i > 0
@@ -7350,7 +7352,9 @@ impl Interpreter {
73507352
{
73517353
let left = self.parse_arithmetic_impl(&expr[..i - 1], arith_depth + 1);
73527354
let right = self.parse_arithmetic_impl(&expr[i + 1..], arith_depth + 1);
7353-
return left >> right;
7355+
// THREAT[TM-DOS-029]: clamp shift to 0..=63 to prevent panic
7356+
let shift = right.clamp(0, 63) as u32;
7357+
return left.wrapping_shr(shift);
73547358
}
73557359
_ => {}
73567360
}
@@ -7378,10 +7382,11 @@ impl Interpreter {
73787382
}
73797383
let left = self.parse_arithmetic_impl(&expr[..i], arith_depth + 1);
73807384
let right = self.parse_arithmetic_impl(&expr[i + 1..], arith_depth + 1);
7385+
// THREAT[TM-DOS-029]: wrapping to prevent overflow panic
73817386
return if chars[i] == '+' {
7382-
left + right
7387+
left.wrapping_add(right)
73837388
} else {
7384-
left - right
7389+
left.wrapping_sub(right)
73857390
};
73867391
}
73877392
_ => {}
@@ -7404,22 +7409,24 @@ impl Interpreter {
74047409
}
74057410
let left = self.parse_arithmetic_impl(&expr[..i], arith_depth + 1);
74067411
let right = self.parse_arithmetic_impl(&expr[i + 1..], arith_depth + 1);
7407-
return left * right;
7412+
// THREAT[TM-DOS-029]: wrapping to prevent overflow panic
7413+
return left.wrapping_mul(right);
74087414
}
74097415
'/' | '%' if depth == 0 => {
74107416
let left = self.parse_arithmetic_impl(&expr[..i], arith_depth + 1);
74117417
let right = self.parse_arithmetic_impl(&expr[i + 1..], arith_depth + 1);
7418+
// THREAT[TM-DOS-029]: wrapping to prevent i64::MIN / -1 panic
74127419
return match chars[i] {
74137420
'/' => {
74147421
if right != 0 {
7415-
left / right
7422+
left.wrapping_div(right)
74167423
} else {
74177424
0
74187425
}
74197426
}
74207427
'%' => {
74217428
if right != 0 {
7422-
left % right
7429+
left.wrapping_rem(right)
74237430
} else {
74247431
0
74257432
}
@@ -7441,7 +7448,9 @@ impl Interpreter {
74417448
let left = self.parse_arithmetic_impl(&expr[..i], arith_depth + 1);
74427449
// Right-associative: parse from i+2 onward (may contain more **)
74437450
let right = self.parse_arithmetic_impl(&expr[i + 2..], arith_depth + 1);
7444-
return left.pow(right as u32);
7451+
// THREAT[TM-DOS-029]: clamp exponent to 0..=63 to prevent panic/hang
7452+
let exp = right.clamp(0, 63) as u32;
7453+
return left.wrapping_pow(exp);
74457454
}
74467455
_ => {}
74477456
}
@@ -7451,7 +7460,10 @@ impl Interpreter {
74517460
if let Some(rest) = expr.strip_prefix('-') {
74527461
let rest = rest.trim();
74537462
if !rest.is_empty() {
7454-
return -self.parse_arithmetic_impl(rest, arith_depth + 1);
7463+
// THREAT[TM-DOS-029]: wrapping to prevent i64::MIN negation panic
7464+
return self
7465+
.parse_arithmetic_impl(rest, arith_depth + 1)
7466+
.wrapping_neg();
74557467
}
74567468
}
74577469
if let Some(rest) = expr.strip_prefix('~') {
@@ -9314,4 +9326,52 @@ mod tests {
93149326
let result = run_script("set a b c\necho $#\necho $1 $2 $3").await;
93159327
assert_eq!(result.stdout, "3\na b c\n");
93169328
}
9329+
9330+
#[tokio::test]
9331+
async fn test_arithmetic_exponent_negative_no_panic() {
9332+
let result = run_script("echo $(( 2 ** -1 ))").await;
9333+
assert_eq!(result.exit_code, 0);
9334+
}
9335+
9336+
#[tokio::test]
9337+
async fn test_arithmetic_exponent_large_no_panic() {
9338+
let result = run_script("echo $(( 2 ** 100 ))").await;
9339+
assert_eq!(result.exit_code, 0);
9340+
}
9341+
9342+
#[tokio::test]
9343+
async fn test_arithmetic_shift_large_no_panic() {
9344+
let result = run_script("echo $(( 1 << 64 ))").await;
9345+
assert_eq!(result.exit_code, 0);
9346+
}
9347+
9348+
#[tokio::test]
9349+
async fn test_arithmetic_shift_negative_no_panic() {
9350+
let result = run_script("echo $(( 1 << -1 ))").await;
9351+
assert_eq!(result.exit_code, 0);
9352+
}
9353+
9354+
#[tokio::test]
9355+
async fn test_arithmetic_div_min_neg1_no_panic() {
9356+
let result = run_script("echo $(( -9223372036854775808 / -1 ))").await;
9357+
assert_eq!(result.exit_code, 0);
9358+
}
9359+
9360+
#[tokio::test]
9361+
async fn test_arithmetic_mod_min_neg1_no_panic() {
9362+
let result = run_script("echo $(( -9223372036854775808 % -1 ))").await;
9363+
assert_eq!(result.exit_code, 0);
9364+
}
9365+
9366+
#[tokio::test]
9367+
async fn test_arithmetic_overflow_add_no_panic() {
9368+
let result = run_script("echo $(( 9223372036854775807 + 1 ))").await;
9369+
assert_eq!(result.exit_code, 0);
9370+
}
9371+
9372+
#[tokio::test]
9373+
async fn test_arithmetic_overflow_mul_no_panic() {
9374+
let result = run_script("echo $(( 9223372036854775807 * 2 ))").await;
9375+
assert_eq!(result.exit_code, 0);
9376+
}
93179377
}

0 commit comments

Comments
 (0)