Skip to content

Commit 524dc54

Browse files
authored
fix(interpreter): cap glob_match calls in remove_pattern_glob (#1016)
## Summary - Cap `glob_match` invocations in `remove_pattern_glob` to 10,000 to prevent O(n^2) CPU exhaustion - Bracket expressions like `[a]*` on long strings no longer cause quadratic scanning Closes #994 ## Test plan - [x] New spec tests: `glob_match_cap.test.sh` with 4 cases - [x] `cargo test --all-features` passes - [x] `cargo clippy -- -D warnings` clean
1 parent 9bfe855 commit 524dc54

File tree

2 files changed

+48
-0
lines changed

2 files changed

+48
-0
lines changed

crates/bashkit/src/interpreter/mod.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6921,18 +6921,27 @@ impl Interpreter {
69216921
}
69226922

69236923
/// Remove prefix/suffix using glob_match for patterns with brackets or extglob.
6924+
///
6925+
/// THREAT[TM-DOS]: Cap glob_match invocations to prevent O(n^2) CPU
6926+
/// exhaustion on long strings with bracket/extglob patterns.
69246927
fn remove_pattern_glob(
69256928
&self,
69266929
value: &str,
69276930
pattern: &str,
69286931
prefix: bool,
69296932
longest: bool,
69306933
) -> String {
6934+
const MAX_GLOB_MATCH_CALLS: usize = 10_000;
69316935
let chars: Vec<char> = value.chars().collect();
6936+
let mut calls = 0usize;
69326937
if prefix {
69336938
// Try each prefix length; shortest = first match, longest = last match
69346939
let mut last_match = None;
69356940
for i in 0..=chars.len() {
6941+
calls += 1;
6942+
if calls > MAX_GLOB_MATCH_CALLS {
6943+
break;
6944+
}
69366945
let candidate: String = chars[..i].iter().collect();
69376946
if self.glob_match(&candidate, pattern) {
69386947
if !longest {
@@ -6948,6 +6957,10 @@ impl Interpreter {
69486957
// Suffix removal: try each suffix length
69496958
let mut last_match = None;
69506959
for i in (0..=chars.len()).rev() {
6960+
calls += 1;
6961+
if calls > MAX_GLOB_MATCH_CALLS {
6962+
break;
6963+
}
69516964
let candidate: String = chars[i..].iter().collect();
69526965
if self.glob_match(&candidate, pattern) {
69536966
if !longest {
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Glob match cap in remove_pattern_glob
2+
# Regression tests for issue #994
3+
4+
### bracket_prefix_removal_works
5+
# Normal bracket pattern prefix removal still works
6+
x="abcdef"
7+
echo "${x#[a]*}"
8+
### expect
9+
bcdef
10+
### end
11+
12+
### bracket_suffix_removal_works
13+
# Normal bracket pattern suffix removal still works
14+
x="abcdef"
15+
echo "${x%[f]}"
16+
### expect
17+
abcde
18+
### end
19+
20+
### bracket_longest_prefix_removal_works
21+
# Longest bracket pattern prefix removal still works
22+
x="aaabbb"
23+
result="${x##[a]*}"
24+
echo "result=${#result}"
25+
### expect
26+
result=0
27+
### end
28+
29+
### normal_prefix_removal_unaffected
30+
# Standard patterns still work correctly
31+
x="hello_world"
32+
echo "${x#hello_}"
33+
### expect
34+
world
35+
### end

0 commit comments

Comments
 (0)