Skip to content

Commit 3c65ea3

Browse files
committed
Merge branch 'gix-glob-recursion'
2 parents cda5b51 + ac57855 commit 3c65ea3

File tree

2 files changed

+134
-116
lines changed

2 files changed

+134
-116
lines changed

gix-glob/src/wildmatch.rs

Lines changed: 118 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -185,144 +185,146 @@ pub(crate) mod function {
185185
let mut next = if negated { p.next() } else { Some((p_idx, p_ch)) };
186186
let mut prev_p_ch = 0;
187187
let mut matched = false;
188+
let mut p_idx_ofs = 0;
188189
loop {
189-
match next {
190-
None => return AbortAll,
191-
Some((p_idx, mut p_ch)) => match p_ch {
192-
BACKSLASH => match p.next() {
193-
Some((_, p_ch)) => {
194-
if p_ch == t_ch {
195-
matched = true
196-
} else {
197-
prev_p_ch = p_ch;
198-
}
199-
}
200-
None => return AbortAll,
201-
},
202-
b'-' if prev_p_ch != 0
203-
&& p.peek().is_some()
204-
&& p.peek().map(|t| t.1) != Some(BRACKET_CLOSE) =>
205-
{
206-
p_ch = p.next().expect("peeked").1;
207-
if p_ch == BACKSLASH {
208-
p_ch = match p.next() {
209-
Some(t) => t.1,
210-
None => return AbortAll,
211-
};
190+
let Some((mut p_idx, mut p_ch)) = next else {
191+
return AbortAll;
192+
};
193+
p_idx += p_idx_ofs;
194+
match p_ch {
195+
BACKSLASH => match p.next() {
196+
Some((_, p_ch)) => {
197+
if p_ch == t_ch {
198+
matched = true
199+
} else {
200+
prev_p_ch = p_ch;
212201
}
213-
if t_ch <= p_ch && t_ch >= prev_p_ch {
202+
}
203+
None => return AbortAll,
204+
},
205+
b'-' if prev_p_ch != 0
206+
&& p.peek().is_some()
207+
&& p.peek().map(|t| t.1) != Some(BRACKET_CLOSE) =>
208+
{
209+
p_ch = p.next().expect("peeked").1;
210+
if p_ch == BACKSLASH {
211+
p_ch = match p.next() {
212+
Some(t) => t.1,
213+
None => return AbortAll,
214+
};
215+
}
216+
if t_ch <= p_ch && t_ch >= prev_p_ch {
217+
matched = true;
218+
} else if mode.contains(Mode::IGNORE_CASE) && t_ch.is_ascii_lowercase() {
219+
let t_ch_upper = t_ch.to_ascii_uppercase();
220+
if (t_ch_upper <= p_ch.to_ascii_uppercase()
221+
&& t_ch_upper >= prev_p_ch.to_ascii_uppercase())
222+
|| (t_ch_upper <= prev_p_ch.to_ascii_uppercase()
223+
&& t_ch_upper >= p_ch.to_ascii_uppercase())
224+
{
214225
matched = true;
215-
} else if mode.contains(Mode::IGNORE_CASE) && t_ch.is_ascii_lowercase() {
216-
let t_ch_upper = t_ch.to_ascii_uppercase();
217-
if (t_ch_upper <= p_ch.to_ascii_uppercase()
218-
&& t_ch_upper >= prev_p_ch.to_ascii_uppercase())
219-
|| (t_ch_upper <= prev_p_ch.to_ascii_uppercase()
220-
&& t_ch_upper >= p_ch.to_ascii_uppercase())
221-
{
222-
matched = true;
223-
}
224226
}
225-
prev_p_ch = 0;
226227
}
227-
BRACKET_OPEN if matches!(p.peek(), Some((_, COLON))) => {
228+
prev_p_ch = 0;
229+
}
230+
BRACKET_OPEN if matches!(p.peek(), Some((_, COLON))) => {
231+
p.next();
232+
while p.peek().map_or(false, |t| t.1 != BRACKET_CLOSE) {
228233
p.next();
229-
while p.peek().map_or(false, |t| t.1 != BRACKET_CLOSE) {
230-
p.next();
234+
}
235+
let closing_bracket_idx = match p.next() {
236+
Some((idx, _)) => idx,
237+
None => return AbortAll,
238+
};
239+
const BRACKET__COLON__BRACKET: usize = 3;
240+
if closing_bracket_idx.saturating_sub(p_idx) < BRACKET__COLON__BRACKET
241+
|| pattern[closing_bracket_idx - 1] != COLON
242+
{
243+
if t_ch == BRACKET_OPEN {
244+
matched = true
231245
}
232-
let closing_bracket_idx = match p.next() {
233-
Some((idx, _)) => idx,
234-
None => return AbortAll,
235-
};
236-
const BRACKET__COLON__BRACKET: usize = 3;
237-
if closing_bracket_idx - p_idx < BRACKET__COLON__BRACKET
238-
|| pattern[closing_bracket_idx - 1] != COLON
239-
{
240-
if t_ch == BRACKET_OPEN {
241-
matched = true
242-
}
243-
p = pattern[p_idx + 1..]
244-
.iter()
245-
.map(possibly_lowercase)
246-
.enumerate()
247-
.peekable();
248-
} else {
249-
let class = &pattern.as_bytes()[p_idx + 2..closing_bracket_idx - 1];
250-
match class {
251-
b"alnum" => {
252-
if t_ch.is_ascii_alphanumeric() {
253-
matched = true;
254-
}
246+
if p_idx > pattern.len() {
247+
return AbortAll;
248+
}
249+
p = pattern[p_idx..].iter().map(possibly_lowercase).enumerate().peekable();
250+
p_idx_ofs += p_idx;
251+
} else {
252+
let class = &pattern.as_bytes()[p_idx + 2..closing_bracket_idx - 1];
253+
match class {
254+
b"alnum" => {
255+
if t_ch.is_ascii_alphanumeric() {
256+
matched = true;
255257
}
256-
b"alpha" => {
257-
if t_ch.is_ascii_alphabetic() {
258-
matched = true;
259-
}
258+
}
259+
b"alpha" => {
260+
if t_ch.is_ascii_alphabetic() {
261+
matched = true;
260262
}
261-
b"blank" => {
262-
if t_ch.is_ascii_whitespace() {
263-
matched = true;
264-
}
263+
}
264+
b"blank" => {
265+
if t_ch.is_ascii_whitespace() {
266+
matched = true;
265267
}
266-
b"cntrl" => {
267-
if t_ch.is_ascii_control() {
268-
matched = true;
269-
}
268+
}
269+
b"cntrl" => {
270+
if t_ch.is_ascii_control() {
271+
matched = true;
270272
}
271-
b"digit" => {
272-
if t_ch.is_ascii_digit() {
273-
matched = true;
274-
}
273+
}
274+
b"digit" => {
275+
if t_ch.is_ascii_digit() {
276+
matched = true;
275277
}
278+
}
276279

277-
b"graph" => {
278-
if t_ch.is_ascii_graphic() {
279-
matched = true;
280-
}
280+
b"graph" => {
281+
if t_ch.is_ascii_graphic() {
282+
matched = true;
281283
}
282-
b"lower" => {
283-
if t_ch.is_ascii_lowercase() {
284-
matched = true;
285-
}
284+
}
285+
b"lower" => {
286+
if t_ch.is_ascii_lowercase() {
287+
matched = true;
286288
}
287-
b"print" => {
288-
if (0x20u8..=0x7e).contains(&t_ch) {
289-
matched = true;
290-
}
289+
}
290+
b"print" => {
291+
if (0x20u8..=0x7e).contains(&t_ch) {
292+
matched = true;
291293
}
292-
b"punct" => {
293-
if t_ch.is_ascii_punctuation() {
294-
matched = true;
295-
}
294+
}
295+
b"punct" => {
296+
if t_ch.is_ascii_punctuation() {
297+
matched = true;
296298
}
297-
b"space" => {
298-
if t_ch == b' ' {
299-
matched = true;
300-
}
299+
}
300+
b"space" => {
301+
if t_ch == b' ' {
302+
matched = true;
301303
}
302-
b"upper" => {
303-
if t_ch.is_ascii_uppercase()
304-
|| mode.contains(Mode::IGNORE_CASE) && t_ch.is_ascii_lowercase()
305-
{
306-
matched = true;
307-
}
304+
}
305+
b"upper" => {
306+
if t_ch.is_ascii_uppercase()
307+
|| mode.contains(Mode::IGNORE_CASE) && t_ch.is_ascii_lowercase()
308+
{
309+
matched = true;
308310
}
309-
b"xdigit" => {
310-
if t_ch.is_ascii_hexdigit() {
311-
matched = true;
312-
}
311+
}
312+
b"xdigit" => {
313+
if t_ch.is_ascii_hexdigit() {
314+
matched = true;
313315
}
314-
_ => return AbortAll,
315-
};
316-
prev_p_ch = 0;
317-
}
316+
}
317+
_ => return AbortAll,
318+
};
319+
prev_p_ch = 0;
318320
}
319-
_ => {
320-
prev_p_ch = p_ch;
321-
if p_ch == t_ch {
322-
matched = true;
323-
}
321+
}
322+
_ => {
323+
prev_p_ch = p_ch;
324+
if p_ch == t_ch {
325+
matched = true;
324326
}
325-
},
327+
}
326328
};
327329
next = p.next();
328330
if let Some((_, BRACKET_CLOSE)) = next {

gix-glob/tests/pattern/matching.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,22 @@ fn single_paths_match_anywhere() {
325325
);
326326
}
327327

328+
#[test]
329+
fn exponential_runaway_denial_of_service() {
330+
// original: "?[at(/\u{1d}\0\u{4}\u{14}\0[[[[:[\0\0\0\0\0\0\0\0Wt(/\u{1d}\0\u{4}\u{14}\0[[[[:[\0\0\0\0\0\0\0\0\0\0\0]"
331+
// reduced: "[[:[:]"
332+
for pattern in [
333+
"*?[wxxxxxx\0!t[:rt]\u{14}*",
334+
"?[at(/\u{1d}\0\u{4}\u{14}\0[[[[:[\0\0\0\0\0\0\0\0\0\0/s\0\0\0*\0\0\0\0\0\0\0\0]\0\0\0\0\0\0\0\0\0",
335+
"[[:digit]ab]",
336+
"[[:]ab]",
337+
"[[:[:x]",
338+
] {
339+
let pat = pat(pattern);
340+
match_file(&pat, "relative/path", Case::Sensitive);
341+
}
342+
}
343+
328344
fn pat<'a>(pattern: impl Into<&'a BStr>) -> gix_glob::Pattern {
329345
gix_glob::Pattern::from_bytes(pattern.into()).expect("parsing works")
330346
}

0 commit comments

Comments
 (0)