Skip to content

Commit 21bed02

Browse files
authored
fix(parser): enforce subst depth limit in unquoted cmdsub (#1018)
## Summary - Enforce `max_subst_depth` in unquoted `$()` context in `read_word`, matching the limit already enforced in double-quoted strings - Defense-in-depth: prevents deeply nested `$()` from bypassing depth limit Closes #996 ## Test plan - [x] New spec tests: `cmdsub_depth_unquoted.test.sh` with 3 cases - [x] Existing `parse_incomplete_command_sub` test still passes - [x] `cargo test --all-features` passes - [x] `cargo clippy -- -D warnings` clean
1 parent 747cda7 commit 21bed02

2 files changed

Lines changed: 50 additions & 1 deletion

File tree

crates/bashkit/src/parser/lexer.rs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -682,11 +682,34 @@ impl<'a> Lexer<'a> {
682682
}
683683
}
684684
} else {
685-
// Command substitution $(...) - track nested parens
685+
// THREAT[TM-DOS]: Track substitution nesting depth to
686+
// enforce max_subst_depth consistently in both quoted
687+
// and unquoted contexts (issue #996).
686688
let mut depth = 1;
689+
let mut subst_depth = 1usize;
687690
while let Some(c) = self.peek_char() {
688691
word.push(c);
689692
self.advance();
693+
if c == '$' && self.peek_char() == Some('(') {
694+
subst_depth += 1;
695+
if subst_depth > self.max_subst_depth {
696+
// Depth limit exceeded — consume remaining
697+
// parens and stop nesting deeper.
698+
while let Some(ic) = self.peek_char() {
699+
word.push(ic);
700+
self.advance();
701+
if ic == '(' {
702+
depth += 1;
703+
} else if ic == ')' {
704+
depth -= 1;
705+
if depth == 0 {
706+
break;
707+
}
708+
}
709+
}
710+
break;
711+
}
712+
}
690713
if c == '(' {
691714
depth += 1;
692715
} else if c == ')' {
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Command substitution depth limit in unquoted context
2+
# Regression tests for issue #996
3+
4+
### shallow_cmdsub_unquoted
5+
# Shallow command substitution in unquoted context works
6+
x=$(echo hello)
7+
echo $x
8+
### expect
9+
hello
10+
### end
11+
12+
### nested_cmdsub_unquoted
13+
# Nested command substitution in unquoted context works
14+
x=$(echo $(echo nested))
15+
echo $x
16+
### expect
17+
nested
18+
### end
19+
20+
### cmdsub_depth_3_unquoted
21+
# Three levels of nesting works
22+
x=$(echo $(echo $(echo deep)))
23+
echo $x
24+
### expect
25+
deep
26+
### end

0 commit comments

Comments
 (0)