Git fork

traverse_trees(): respect max_allowed_tree_depth

The tree-walk.c code walks trees recursively, and may run out of stack
space. The easiest way to see this is with git-archive; on my 64-bit
Linux system it runs out of stack trying to generate a tarfile with a
tree depth of 13,772.

I've picked 4100 as the depth for our "big" test. I ran it with a much
higher value to confirm that we do get a segfault without this patch.
But really anything over 4096 is sufficient for its stated purpose,
which is to find out if our default limit of 4096 is low enough to
prevent segfaults on all platforms. Keeping it small saves us time on
the test setup.

The tree-walk code that's touched here underlies unpack_trees(), so this
protects any programs which use it, not just git-archive (but archive is
easy to test, and was what alerted me to this issue in a real-world
case).

Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>

authored by

Jeff King and committed by
Junio C Hamano
f1f63a48 be20128b

+70
+66
t/t6700-tree-depth.sh
···
··· 1 + #!/bin/sh 2 + 3 + test_description='handling of deep trees in various commands' 4 + . ./test-lib.sh 5 + 6 + # We'll test against two depths here: a small one that will let us check the 7 + # behavior of the config setting easily, and a large one that should be 8 + # forbidden by default. Testing the default depth will let us know whether our 9 + # default is enough to prevent segfaults on systems that run the tests. 10 + small_depth=50 11 + big_depth=4100 12 + 13 + small_ok="-c core.maxtreedepth=$small_depth" 14 + small_no="-c core.maxtreedepth=$((small_depth-1))" 15 + 16 + # usage: mkdeep <name> <depth> 17 + # Create a tag <name> containing a file whose path has depth <depth>. 18 + # 19 + # We'll use fast-import here for two reasons: 20 + # 21 + # 1. It's faster than creating $big_depth tree objects. 22 + # 23 + # 2. As we tighten tree limits, it's more likely to allow large sizes 24 + # than trying to stuff a deep path into the index. 25 + mkdeep () { 26 + { 27 + echo "commit refs/tags/$1" && 28 + echo "committer foo <foo@example.com> 1234 -0000" && 29 + echo "data <<EOF" && 30 + echo "the commit message" && 31 + echo "EOF" && 32 + 33 + printf 'M 100644 inline ' && 34 + i=0 && 35 + while test $i -lt $2 36 + do 37 + printf 'a/' 38 + i=$((i+1)) 39 + done && 40 + echo "file" && 41 + 42 + echo "data <<EOF" && 43 + echo "the file contents" && 44 + echo "EOF" && 45 + echo 46 + } | git fast-import 47 + } 48 + 49 + test_expect_success 'create small tree' ' 50 + mkdeep small $small_depth 51 + ' 52 + 53 + test_expect_success 'create big tree' ' 54 + mkdeep big $big_depth 55 + ' 56 + 57 + test_expect_success 'limit recursion of git-archive' ' 58 + git $small_ok archive small >/dev/null && 59 + test_must_fail git $small_no archive small >/dev/null 60 + ' 61 + 62 + test_expect_success 'default limit for git-archive fails gracefully' ' 63 + test_must_fail git archive big >/dev/null 64 + ' 65 + 66 + test_done
+4
tree-walk.c
··· 9 #include "tree.h" 10 #include "pathspec.h" 11 #include "json-writer.h" 12 13 static const char *get_mode(const char *str, unsigned int *modep) 14 { ··· 448 struct strbuf base = STRBUF_INIT; 449 int interesting = 1; 450 char *traverse_path; 451 452 traverse_trees_count++; 453 traverse_trees_cur_depth++;
··· 9 #include "tree.h" 10 #include "pathspec.h" 11 #include "json-writer.h" 12 + #include "environment.h" 13 14 static const char *get_mode(const char *str, unsigned int *modep) 15 { ··· 449 struct strbuf base = STRBUF_INIT; 450 int interesting = 1; 451 char *traverse_path; 452 + 453 + if (traverse_trees_cur_depth > max_allowed_tree_depth) 454 + return error("exceeded maximum allowed tree depth"); 455 456 traverse_trees_count++; 457 traverse_trees_cur_depth++;