diff options
-rw-r--r-- | Documentation/config/core.txt | 6 | ||||
-rw-r--r-- | Documentation/fsck-msgids.txt | 7 | ||||
-rw-r--r-- | config.c | 5 | ||||
-rw-r--r-- | environment.c | 1 | ||||
-rw-r--r-- | environment.h | 1 | ||||
-rw-r--r-- | fsck.c | 24 | ||||
-rw-r--r-- | fsck.h | 1 | ||||
-rw-r--r-- | list-objects.c | 8 | ||||
-rw-r--r-- | sparse-index.c | 2 | ||||
-rwxr-xr-x | t/t1450-fsck.sh | 10 | ||||
-rwxr-xr-x | t/t6700-tree-depth.sh | 93 | ||||
-rw-r--r-- | tree-diff.c | 23 | ||||
-rw-r--r-- | tree-walk.c | 20 | ||||
-rw-r--r-- | tree-walk.h | 2 | ||||
-rw-r--r-- | tree.c | 9 | ||||
-rw-r--r-- | tree.h | 1 | ||||
-rw-r--r-- | unpack-trees.c | 9 | ||||
-rw-r--r-- | unpack-trees.h | 2 | ||||
-rw-r--r-- | wt-status.c | 2 |
19 files changed, 201 insertions, 25 deletions
diff --git a/Documentation/config/core.txt b/Documentation/config/core.txt index dfbdaf00b8..0e8c2832bf 100644 --- a/Documentation/config/core.txt +++ b/Documentation/config/core.txt @@ -736,3 +736,9 @@ core.abbrev:: If set to "no", no abbreviation is made and the object names are shown in their full length. The minimum length is 4. + +core.maxTreeDepth:: + The maximum depth Git is willing to recurse while traversing a + tree (e.g., "a/b/cde/f" has a depth of 4). This is a fail-safe + to allow Git to abort cleanly, and should not generally need to + be adjusted. The default is 4096. diff --git a/Documentation/fsck-msgids.txt b/Documentation/fsck-msgids.txt index 12eae8a222..09b0aecbf8 100644 --- a/Documentation/fsck-msgids.txt +++ b/Documentation/fsck-msgids.txt @@ -103,6 +103,13 @@ `hasDotgit`:: (WARN) A tree contains an entry named `.git`. +`largePathname`:: + (WARN) A tree contains an entry with a very long path name. If + the value of `fsck.largePathname` contains a colon, that value + is used as the maximum allowable length (e.g., "warn:10" would + complain about any path component of 11 or more bytes). The + default value is 4096. + `mailmapSymlink`:: (INFO) `.mailmap` is a symlink. @@ -1801,6 +1801,11 @@ static int git_default_core_config(const char *var, const char *value, return 0; } + if (!strcmp(var, "core.maxtreedepth")) { + max_allowed_tree_depth = git_config_int(var, value, ctx->kvi); + return 0; + } + /* Add other config variables here and to Documentation/config.txt. */ return platform_core_config(var, value, ctx, cb); } diff --git a/environment.c b/environment.c index f98d76f080..bb3c2a96a3 100644 --- a/environment.c +++ b/environment.c @@ -81,6 +81,7 @@ int merge_log_config = -1; int precomposed_unicode = -1; /* see probe_utf8_pathname_composition() */ unsigned long pack_size_limit_cfg; enum log_refs_config log_all_ref_updates = LOG_REFS_UNSET; +int max_allowed_tree_depth = 2048; #ifndef PROTECT_HFS_DEFAULT #define PROTECT_HFS_DEFAULT 0 diff --git a/environment.h b/environment.h index c5377473c6..e5351c9dd9 100644 --- a/environment.h +++ b/environment.h @@ -132,6 +132,7 @@ extern size_t packed_git_limit; extern size_t delta_base_cache_limit; extern unsigned long big_file_threshold; extern unsigned long pack_size_limit_cfg; +extern int max_allowed_tree_depth; /* * Accessors for the core.sharedrepository config which lazy-load the value @@ -24,6 +24,8 @@ #include "credential.h" #include "help.h" +static ssize_t max_tree_entry_len = 4096; + #define STR(x) #x #define MSG_ID(id, msg_type) { STR(id), NULL, NULL, FSCK_##msg_type }, static struct { @@ -154,15 +156,29 @@ void fsck_set_msg_type(struct fsck_options *options, const char *msg_id_str, const char *msg_type_str) { int msg_id = parse_msg_id(msg_id_str); - enum fsck_msg_type msg_type = parse_msg_type(msg_type_str); + char *to_free = NULL; + enum fsck_msg_type msg_type; if (msg_id < 0) die("Unhandled message id: %s", msg_id_str); + if (msg_id == FSCK_MSG_LARGE_PATHNAME) { + const char *colon = strchr(msg_type_str, ':'); + if (colon) { + msg_type_str = to_free = + xmemdupz(msg_type_str, colon - msg_type_str); + colon++; + if (!git_parse_ssize_t(colon, &max_tree_entry_len)) + die("unable to parse max tree entry len: %s", colon); + } + } + msg_type = parse_msg_type(msg_type_str); + if (msg_type != FSCK_ERROR && msg_id_info[msg_id].msg_type == FSCK_FATAL) die("Cannot demote %s to %s", msg_id_str, msg_type_str); fsck_set_msg_type_from_ids(options, msg_id, msg_type); + free(to_free); } void fsck_set_msg_types(struct fsck_options *options, const char *values) @@ -578,6 +594,7 @@ static int fsck_tree(const struct object_id *tree_oid, int has_bad_modes = 0; int has_dup_entries = 0; int not_properly_sorted = 0; + int has_large_name = 0; struct tree_desc desc; unsigned o_mode; const char *o_name; @@ -607,6 +624,7 @@ static int fsck_tree(const struct object_id *tree_oid, has_dotdot |= !strcmp(name, ".."); has_dotgit |= is_hfs_dotgit(name) || is_ntfs_dotgit(name); has_zero_pad |= *(char *)desc.buffer == '0'; + has_large_name |= tree_entry_len(&desc.entry) > max_tree_entry_len; if (is_hfs_dotgitmodules(name) || is_ntfs_dotgitmodules(name)) { if (!S_ISLNK(mode)) @@ -749,6 +767,10 @@ static int fsck_tree(const struct object_id *tree_oid, retval += report(options, tree_oid, OBJ_TREE, FSCK_MSG_TREE_NOT_SORTED, "not properly sorted"); + if (has_large_name) + retval += report(options, tree_oid, OBJ_TREE, + FSCK_MSG_LARGE_PATHNAME, + "contains excessively large pathname"); return retval; } @@ -73,6 +73,7 @@ enum fsck_msg_type { FUNC(NULL_SHA1, WARN) \ FUNC(ZERO_PADDED_FILEMODE, WARN) \ FUNC(NUL_IN_COMMIT, WARN) \ + FUNC(LARGE_PATHNAME, WARN) \ /* infos (reported as warnings, but ignored by default) */ \ FUNC(BAD_FILEMODE, INFO) \ FUNC(GITMODULES_PARSE, INFO) \ diff --git a/list-objects.c b/list-objects.c index e60a6cd5b4..c25c72b32c 100644 --- a/list-objects.c +++ b/list-objects.c @@ -14,6 +14,7 @@ #include "packfile.h" #include "object-store-ll.h" #include "trace.h" +#include "environment.h" struct traversal_context { struct rev_info *revs; @@ -21,6 +22,7 @@ struct traversal_context { show_commit_fn show_commit; void *show_data; struct filter *filter; + int depth; }; static void show_commit(struct traversal_context *ctx, @@ -118,7 +120,9 @@ static void process_tree_contents(struct traversal_context *ctx, entry.path, oid_to_hex(&tree->object.oid)); } t->object.flags |= NOT_USER_GIVEN; + ctx->depth++; process_tree(ctx, t, base, entry.path); + ctx->depth--; } else if (S_ISGITLINK(entry.mode)) ; /* ignore gitlink */ @@ -156,6 +160,9 @@ static void process_tree(struct traversal_context *ctx, !revs->include_check_obj(&tree->object, revs->include_check_data)) return; + if (ctx->depth > max_allowed_tree_depth) + die("exceeded maximum allowed tree depth"); + failed_parse = parse_tree_gently(tree, 1); if (failed_parse) { if (revs->ignore_missing_links) @@ -349,6 +356,7 @@ static void traverse_non_commits(struct traversal_context *ctx, if (!path) path = ""; if (obj->type == OBJ_TREE) { + ctx->depth = 0; process_tree(ctx, (struct tree *)obj, base, path); continue; } diff --git a/sparse-index.c b/sparse-index.c index 1fdb07a9e6..3578feb283 100644 --- a/sparse-index.c +++ b/sparse-index.c @@ -391,7 +391,7 @@ void expand_index(struct index_state *istate, struct pattern_list *pl) strbuf_setlen(&base, 0); strbuf_add(&base, ce->name, strlen(ce->name)); - read_tree_at(istate->repo, tree, &base, &ps, + read_tree_at(istate->repo, tree, &base, 0, &ps, add_path_to_index, &ctx); /* free directory entries. full entries are re-used */ diff --git a/t/t1450-fsck.sh b/t/t1450-fsck.sh index 5805d47eb9..10a539158c 100755 --- a/t/t1450-fsck.sh +++ b/t/t1450-fsck.sh @@ -589,6 +589,16 @@ test_expect_success 'fsck notices submodule entry pointing to null sha1' ' ) ' +test_expect_success 'fsck notices excessively large tree entry name' ' + git init large-name && + ( + cd large-name && + test_commit a-long-name && + git -c fsck.largePathname=warn:10 fsck 2>out && + grep "warning.*large pathname" out + ) +' + while read name path pretty; do while read mode type; do : ${pretty:=$path} diff --git a/t/t6700-tree-depth.sh b/t/t6700-tree-depth.sh new file mode 100755 index 0000000000..e410c41234 --- /dev/null +++ b/t/t6700-tree-depth.sh @@ -0,0 +1,93 @@ +#!/bin/sh + +test_description='handling of deep trees in various commands' +. ./test-lib.sh + +# We'll test against two depths here: a small one that will let us check the +# behavior of the config setting easily, and a large one that should be +# forbidden by default. Testing the default depth will let us know whether our +# default is enough to prevent segfaults on systems that run the tests. +small_depth=50 +big_depth=4100 + +small_ok="-c core.maxtreedepth=$small_depth" +small_no="-c core.maxtreedepth=$((small_depth-1))" + +# usage: mkdeep <name> <depth> +# Create a tag <name> containing a file whose path has depth <depth>. +# +# We'll use fast-import here for two reasons: +# +# 1. It's faster than creating $big_depth tree objects. +# +# 2. As we tighten tree limits, it's more likely to allow large sizes +# than trying to stuff a deep path into the index. +mkdeep () { + { + echo "commit refs/tags/$1" && + echo "committer foo <foo@example.com> 1234 -0000" && + echo "data <<EOF" && + echo "the commit message" && + echo "EOF" && + + printf 'M 100644 inline ' && + i=0 && + while test $i -lt $2 + do + printf 'a/' + i=$((i+1)) + done && + echo "file" && + + echo "data <<EOF" && + echo "the file contents" && + echo "EOF" && + echo + } | git fast-import +} + +test_expect_success 'create small tree' ' + mkdeep small $small_depth +' + +test_expect_success 'create big tree' ' + mkdeep big $big_depth +' + +test_expect_success 'limit recursion of git-archive' ' + git $small_ok archive small >/dev/null && + test_must_fail git $small_no archive small >/dev/null +' + +test_expect_success 'default limit for git-archive fails gracefully' ' + test_must_fail git archive big >/dev/null +' + +test_expect_success 'limit recursion of ls-tree -r' ' + git $small_ok ls-tree -r small && + test_must_fail git $small_no ls-tree -r small +' + +test_expect_success 'default limit for ls-tree fails gracefully' ' + test_must_fail git ls-tree -r big >/dev/null +' + +test_expect_success 'limit recursion of rev-list --objects' ' + git $small_ok rev-list --objects small >/dev/null && + test_must_fail git $small_no rev-list --objects small >/dev/null +' + +test_expect_success 'default limit for rev-list fails gracefully' ' + test_must_fail git rev-list --objects big >/dev/null +' + +test_expect_success 'limit recursion of diff-tree -r' ' + git $small_ok diff-tree -r $EMPTY_TREE small && + test_must_fail git $small_no diff-tree -r $EMPTY_TREE small +' + +test_expect_success 'default limit for diff-tree fails gracefully' ' + test_must_fail git diff-tree -r $EMPTY_TREE big +' + +test_done diff --git a/tree-diff.c b/tree-diff.c index 8fc159b86e..46107772d1 100644 --- a/tree-diff.c +++ b/tree-diff.c @@ -7,6 +7,7 @@ #include "hash.h" #include "tree.h" #include "tree-walk.h" +#include "environment.h" /* * Some mode bits are also used internally for computations. @@ -45,7 +46,8 @@ static struct combine_diff_path *ll_diff_tree_paths( struct combine_diff_path *p, const struct object_id *oid, const struct object_id **parents_oid, int nparent, - struct strbuf *base, struct diff_options *opt); + struct strbuf *base, struct diff_options *opt, + int depth); static void ll_diff_tree_oid(const struct object_id *old_oid, const struct object_id *new_oid, struct strbuf *base, struct diff_options *opt); @@ -196,7 +198,7 @@ static struct combine_diff_path *path_appendnew(struct combine_diff_path *last, static struct combine_diff_path *emit_path(struct combine_diff_path *p, struct strbuf *base, struct diff_options *opt, int nparent, struct tree_desc *t, struct tree_desc *tp, - int imin) + int imin, int depth) { unsigned short mode; const char *path; @@ -302,7 +304,8 @@ static struct combine_diff_path *emit_path(struct combine_diff_path *p, strbuf_add(base, path, pathlen); strbuf_addch(base, '/'); - p = ll_diff_tree_paths(p, oid, parents_oid, nparent, base, opt); + p = ll_diff_tree_paths(p, oid, parents_oid, nparent, base, opt, + depth + 1); FAST_ARRAY_FREE(parents_oid, nparent); } @@ -423,12 +426,16 @@ static inline void update_tp_entries(struct tree_desc *tp, int nparent) static struct combine_diff_path *ll_diff_tree_paths( struct combine_diff_path *p, const struct object_id *oid, const struct object_id **parents_oid, int nparent, - struct strbuf *base, struct diff_options *opt) + struct strbuf *base, struct diff_options *opt, + int depth) { struct tree_desc t, *tp; void *ttree, **tptree; int i; + if (depth > max_allowed_tree_depth) + die("exceeded maximum allowed tree depth"); + FAST_ARRAY_ALLOC(tp, nparent); FAST_ARRAY_ALLOC(tptree, nparent); @@ -522,7 +529,7 @@ static struct combine_diff_path *ll_diff_tree_paths( /* D += {δ(t,pi) if pi=p[imin]; "+a" if pi > p[imin]} */ p = emit_path(p, base, opt, nparent, - &t, tp, imin); + &t, tp, imin, depth); skip_emit_t_tp: /* t↓, ∀ pi=p[imin] pi↓ */ @@ -534,7 +541,7 @@ static struct combine_diff_path *ll_diff_tree_paths( else if (cmp < 0) { /* D += "+t" */ p = emit_path(p, base, opt, nparent, - &t, /*tp=*/NULL, -1); + &t, /*tp=*/NULL, -1, depth); /* t↓ */ update_tree_entry(&t); @@ -550,7 +557,7 @@ static struct combine_diff_path *ll_diff_tree_paths( } p = emit_path(p, base, opt, nparent, - /*t=*/NULL, tp, imin); + /*t=*/NULL, tp, imin, depth); skip_emit_tp: /* ∀ pi=p[imin] pi↓ */ @@ -572,7 +579,7 @@ struct combine_diff_path *diff_tree_paths( const struct object_id **parents_oid, int nparent, struct strbuf *base, struct diff_options *opt) { - p = ll_diff_tree_paths(p, oid, parents_oid, nparent, base, opt); + p = ll_diff_tree_paths(p, oid, parents_oid, nparent, base, opt, 0); /* * free pre-allocated last element, if any diff --git a/tree-walk.c b/tree-walk.c index 29ead71be1..b517792ba2 100644 --- a/tree-walk.c +++ b/tree-walk.c @@ -9,6 +9,7 @@ #include "tree.h" #include "pathspec.h" #include "json-writer.h" +#include "environment.h" static const char *get_mode(const char *str, unsigned int *modep) { @@ -441,22 +442,25 @@ int traverse_trees(struct index_state *istate, int n, struct tree_desc *t, struct traverse_info *info) { - int error = 0; - struct name_entry entry[MAX_TRAVERSE_TREES]; + int ret = 0; + struct name_entry *entry; int i; - struct tree_desc_x tx[ARRAY_SIZE(entry)]; + struct tree_desc_x *tx; struct strbuf base = STRBUF_INIT; int interesting = 1; char *traverse_path; + if (traverse_trees_cur_depth > max_allowed_tree_depth) + return error("exceeded maximum allowed tree depth"); + traverse_trees_count++; traverse_trees_cur_depth++; if (traverse_trees_cur_depth > traverse_trees_max_depth) traverse_trees_max_depth = traverse_trees_cur_depth; - if (n >= ARRAY_SIZE(entry)) - BUG("traverse_trees() called with too many trees (%d)", n); + ALLOC_ARRAY(entry, n); + ALLOC_ARRAY(tx, n); for (i = 0; i < n; i++) { tx[i].d = t[i]; @@ -539,7 +543,7 @@ int traverse_trees(struct index_state *istate, if (interesting) { trees_used = info->fn(n, mask, dirmask, entry, info); if (trees_used < 0) { - error = trees_used; + ret = trees_used; if (!info->show_all_errors) break; } @@ -551,12 +555,14 @@ int traverse_trees(struct index_state *istate, } for (i = 0; i < n; i++) free_extended_entry(tx + i); + free(tx); + free(entry); free(traverse_path); info->traverse_path = NULL; strbuf_release(&base); traverse_trees_cur_depth--; - return error; + return ret; } struct dir_state { diff --git a/tree-walk.h b/tree-walk.h index 74cdceb3fe..a6bfa3da3a 100644 --- a/tree-walk.h +++ b/tree-walk.h @@ -6,8 +6,6 @@ struct index_state; struct repository; -#define MAX_TRAVERSE_TREES 8 - /** * The tree walking API is used to traverse and inspect trees. */ @@ -10,11 +10,13 @@ #include "alloc.h" #include "tree-walk.h" #include "repository.h" +#include "environment.h" const char *tree_type = "tree"; int read_tree_at(struct repository *r, struct tree *tree, struct strbuf *base, + int depth, const struct pathspec *pathspec, read_tree_fn_t fn, void *context) { @@ -24,6 +26,9 @@ int read_tree_at(struct repository *r, int len, oldlen = base->len; enum interesting retval = entry_not_interesting; + if (depth > max_allowed_tree_depth) + return error("exceeded maximum allowed tree depth"); + if (parse_tree(tree)) return -1; @@ -74,7 +79,7 @@ int read_tree_at(struct repository *r, strbuf_add(base, entry.path, len); strbuf_addch(base, '/'); retval = read_tree_at(r, lookup_tree(r, &oid), - base, pathspec, + base, depth + 1, pathspec, fn, context); strbuf_setlen(base, oldlen); if (retval) @@ -89,7 +94,7 @@ int read_tree(struct repository *r, read_tree_fn_t fn, void *context) { struct strbuf sb = STRBUF_INIT; - int ret = read_tree_at(r, tree, &sb, pathspec, fn, context); + int ret = read_tree_at(r, tree, &sb, 0, pathspec, fn, context); strbuf_release(&sb); return ret; } @@ -44,6 +44,7 @@ typedef int (*read_tree_fn_t)(const struct object_id *, struct strbuf *, const c int read_tree_at(struct repository *r, struct tree *tree, struct strbuf *base, + int depth, const struct pathspec *pathspec, read_tree_fn_t fn, void *context); diff --git a/unpack-trees.c b/unpack-trees.c index 87517364dc..a203f9a3d7 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -864,8 +864,8 @@ static int traverse_trees_recursive(int n, unsigned long dirmask, struct unpack_trees_options *o = info->data; int i, ret, bottom; int nr_buf = 0; - struct tree_desc t[MAX_UNPACK_TREES]; - void *buf[MAX_UNPACK_TREES]; + struct tree_desc *t; + void **buf; struct traverse_info newinfo; struct name_entry *p; int nr_entries; @@ -902,6 +902,9 @@ static int traverse_trees_recursive(int n, unsigned long dirmask, newinfo.pathlen = st_add3(newinfo.pathlen, tree_entry_len(p), 1); newinfo.df_conflicts |= df_conflicts; + ALLOC_ARRAY(t, n); + ALLOC_ARRAY(buf, n); + /* * Fetch the tree from the ODB for each peer directory in the * n commits. @@ -937,6 +940,8 @@ static int traverse_trees_recursive(int n, unsigned long dirmask, for (i = 0; i < nr_buf; i++) free(buf[i]); + free(buf); + free(t); return ret; } diff --git a/unpack-trees.h b/unpack-trees.h index 9b827c307f..5867e26e17 100644 --- a/unpack-trees.h +++ b/unpack-trees.h @@ -7,7 +7,7 @@ #include "string-list.h" #include "tree-walk.h" -#define MAX_UNPACK_TREES MAX_TRAVERSE_TREES +#define MAX_UNPACK_TREES 8 struct cache_entry; struct unpack_trees_options; diff --git a/wt-status.c b/wt-status.c index d03dfab9e4..9f45bf6949 100644 --- a/wt-status.c +++ b/wt-status.c @@ -739,7 +739,7 @@ static void wt_status_collect_changes_initial(struct wt_status *s) ps.max_depth = -1; strbuf_add(&base, ce->name, ce->ce_namelen); - read_tree_at(istate->repo, tree, &base, &ps, + read_tree_at(istate->repo, tree, &base, 0, &ps, add_file_to_list, s); continue; } |