Merge branch 'jk/tree-name-and-depth-limit'

We now limit depth of the tree objects and maximum length of
pathnames recorded in tree objects.

* jk/tree-name-and-depth-limit:
  lower core.maxTreeDepth default to 2048
  tree-diff: respect max_allowed_tree_depth
  list-objects: respect max_allowed_tree_depth
  read_tree(): respect max_allowed_tree_depth
  traverse_trees(): respect max_allowed_tree_depth
  add core.maxTreeDepth config
  fsck: detect very large tree pathnames
  tree-walk: rename "error" variable
  tree-walk: drop MAX_TRAVERSE_TREES macro
  tree-walk: reduce stack size for recursive functions
This commit is contained in:
Junio C Hamano
2023-09-14 11:16:59 -07:00
19 changed files with 201 additions and 25 deletions

View File

@ -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.

View File

@ -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.

View File

@ -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);
}

View File

@ -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

View File

@ -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
fsck.c
View File

@ -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;
}

1
fsck.h
View File

@ -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) \

View File

@ -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;
}

View File

@ -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 */

View File

@ -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}

93
t/t6700-tree-depth.sh Executable file
View File

@ -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

View File

@ -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

View File

@ -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 {

View File

@ -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.
*/

9
tree.c
View File

@ -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;
}

1
tree.h
View File

@ -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);

View File

@ -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;
}

View File

@ -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;

View File

@ -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;
}