[PATCH v4 1/2] dir: do not apply prefix to negative pathspecs
- From
Yannik Tausch <dev@ytausch.de>
- Date
- Sep 14, 2026, 07:25 UTC
- Message-ID
- <C6BF8D32-470C-4C54-B4BD-CF9B1E0F191F@ytausch.de>
- In-Reply-To
- <7CB757FB-1F2D-4EE6-8C31-8C2CD6D42397@ytausch.de>
common_prefix_len() derives the common prefix solely from non-exclude pathspec items. However, match_pathspec_with_flags() also passes that prefix when matching exclude items.
This can produce incorrect results because that prefix does not necessarily match an exclude item. For example, given non-exclude items "a/b" and "a/c" and an exclude item "x/b", stripping the two-byte prefix from both the pathname "a/b/m" and pattern "x/b" makes the remaining strings match and incorrectly excludes the pathname.
If an exclude item is shorter than the prefix, match_pathspec_item() instead advances item->match beyond its allocation and subtracts the prefix from item->len, producing a negative matchlen. It then dereferences the out-of-bounds pointer. If the resulting byte is not NUL, matchlen is converted to size_t when passed to ps_strncmp(), which may cause a much larger out-of-bounds read.
The out-of-bounds access can be reproduced with AddressSanitizer:
make SANITIZE=address CFLAGS="-g -O0" git
git init test &&
cd test &&
DIR=$(printf "a%.0s" {1..150}) &&
mkdir -p "$DIR" &&
touch "$DIR/f.txt" &&
git add -A &&
git commit -m test &&
../git ls-files -- "$DIR/" ":(exclude)xy"Fix the bug by using a zero prefix when matching exclude items. Add regression tests for both the deterministic incorrect match and the shorter exclude item that causes the out-of-bounds access.
Signed-off-by: Yannik Tausch <dev@ytausch.de> --- dir.c | 2 +- t/t6132-pathspec-exclude.sh | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-)
diff --git a/dir.c b/dir.c index 32430090dc..5f42c992d3 100644 --- a/dir.c +++ b/dir.c @@ -593,7 +593,7 @@ static int match_pathspec_with_flags(struct index_state *istate, if (!(ps->magic & PATHSPEC_EXCLUDE) || !positive) return positive; negative = do_match_pathspec(istate, ps, name, namelen, - prefix, seen, + 0, seen, flags | DO_MATCH_EXCLUDE); return negative ? 0 : positive; } diff --git a/t/t6132-pathspec-exclude.sh b/t/t6132-pathspec-exclude.sh index 9fdafeb1e9..e0c3f73ef0 100755 --- a/t/t6132-pathspec-exclude.sh +++ b/t/t6132-pathspec-exclude.sh @@ -183,6 +183,24 @@ EOF test_cmp expect actual ' +test_expect_success 'negative pathspec shorter than positive pathspec prefix' ' + git ls-files -- sub/sub/ ":(exclude)sub2" >actual && + cat <<-\EOF >expect && + sub/sub/file + sub/sub/sub/file + EOF + test_cmp expect actual +' + +test_expect_success 'exclude is matched against the full path' ' + git ls-files -- sub/sub/ ":(exclude)zzzzzzz" >actual && + cat <<-\EOF >expect && + sub/sub/file + sub/sub/sub/file + EOF + test_cmp expect actual +' + test_expect_success 'multiple exclusions' ' git ls-files -- ":^*/file2" ":^sub2" >actual && cat <<-\EOF >expect &&
-- 2.55.0