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
13 changes: 8 additions & 5 deletions src/glob/matcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -435,7 +435,7 @@ fn match_brace(
brace_stack: &mut BraceStack,
brace_budget: &mut u32,
) -> bool {
let mut brace_depth: i16 = 0;
let mut brace_depth: i32 = 0;
let mut in_brackets = false;

let open_brace_index = state.glob_index;
Expand Down Expand Up @@ -548,21 +548,24 @@ fn match_brace_branch(

fn skip_branch(state: &mut State, glob: &[u8]) {
let mut in_brackets = false;
let end_brace_depth = state.brace_depth - 1;
// `state.brace_depth` only counts groups entered via `match_brace_branch`,
// so nesting merely scanned over while skipping is tracked locally, not in state.
let mut nested: u32 = 0;
Comment thread
robobun marked this conversation as resolved.
while (state.glob_index as usize) < glob.len() {
match glob[state.glob_index as usize] {
b'{' => {
if !in_brackets {
state.brace_depth += 1;
nested += 1;
}
}
b'}' => {
if !in_brackets {
state.brace_depth -= 1;
if state.brace_depth == end_brace_depth {
if nested == 0 {
state.brace_depth -= 1;
state.glob_index += 1;
return;
}
nested -= 1;
}
}
b'[' => {
Expand Down
31 changes: 31 additions & 0 deletions test/js/bun/glob/match.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,37 @@ describe("Glob.match", () => {
expect(glob.match("bde")).toBeTrue();
});

test("deeply nested braces do not overflow depth counters", () => {
const opens = Buffer.alloc(300, "{").toString();
const closes = Buffer.alloc(300, "}").toString();

// First branch matches; skip_branch must scan past >255 nested braces to
// find the closing brace of the outer group.
let glob = new Glob("{a," + opens + closes + "}");
expect(glob.match("a")).toBeTrue();
expect(glob.match("b")).toBeFalse();

// Deeply nested braces in the first (non-matching) branch, second branch matches.
glob = new Glob("{" + opens + closes + ",a}");
expect(glob.match("a")).toBeTrue();

// Deep nest with content, surrounded by matching branches on both sides.
glob = new Glob("{a," + opens + "x" + closes + ",b}");
expect(glob.match("a")).toBeTrue();
expect(glob.match("b")).toBeTrue();
expect(glob.match("y")).toBeFalse();

// Same shape inside a wildcard backtrack.
glob = new Glob("*{a," + opens + closes + "}");
expect(glob.match("za")).toBeTrue();

// >32767 consecutive `{` overflows match_brace's group pre-scan counter.
// The group never closes, so nothing matches.
glob = new Glob(Buffer.alloc(40_000, "{").toString());
expect(glob.match("a")).toBeFalse();
expect(glob.match("{")).toBeFalse();
});

// Most of the potential bugs when dealing with non-ASCII patterns is when the
// pattern matching algorithm wants to deal with single chars, for example
// using the `[...]` syntax, it tries to match each char in the brackets. With
Expand Down
Loading