add core.maxTreeDepth config

Most of our tree traversal algorithms use recursion to visit sub-trees.
For pathologically large trees, this can cause us to run out of stack
space and abort in an uncontrolled way. Let's put our own limit here so
that we can fail gracefully rather than segfaulting.

In similar cases where we recursed along the commit graph, we rewrote
the algorithms to avoid recursion and keep any stack data on the heap.
But the commit graph is meant to grow without bound, whereas it's not an
imposition to put a limit on the maximum size of tree we'll handle.

And this has a bonus side effect: coupled with a limit on individual
tree entry names, this limits the total size of a path we may encounter.
This gives us an extra protection against code handling long path names
which may suffer from integer overflows in the size (which could then be
exploited by malicious trees).

The default of 4096 is set to be much longer than anybody would care
about in the real world. Even with single-letter interior tree names
(like "a/b/c"), such a path is at least 8191 bytes. While most operating
systems will let you create such a path incrementally, trying to
reference the whole thing in a system call (as Git would do when
actually trying to access it) will result in ENAMETOOLONG. Coupled with
the recent fsck.largePathname warning, the maximum total pathname Git
will handle is (by default) 16MB.

This config option doesn't do anything yet; future patches will convert
various algorithms to respect the limit.

Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Jeff King 2023-08-31 02:21:00 -04:00 committed by Junio C Hamano
parent 0fbcaef6b4
commit be20128bfa
4 changed files with 13 additions and 0 deletions

View File

@ -736,3 +736,9 @@ core.abbrev::
If set to "no", no abbreviation is made and the object names If set to "no", no abbreviation is made and the object names
are shown in their full length. are shown in their full length.
The minimum length is 4. 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

@ -1801,6 +1801,11 @@ static int git_default_core_config(const char *var, const char *value,
return 0; 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. */ /* Add other config variables here and to Documentation/config.txt. */
return platform_core_config(var, value, ctx, cb); 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() */ int precomposed_unicode = -1; /* see probe_utf8_pathname_composition() */
unsigned long pack_size_limit_cfg; unsigned long pack_size_limit_cfg;
enum log_refs_config log_all_ref_updates = LOG_REFS_UNSET; enum log_refs_config log_all_ref_updates = LOG_REFS_UNSET;
int max_allowed_tree_depth = 4096;
#ifndef PROTECT_HFS_DEFAULT #ifndef PROTECT_HFS_DEFAULT
#define PROTECT_HFS_DEFAULT 0 #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 size_t delta_base_cache_limit;
extern unsigned long big_file_threshold; extern unsigned long big_file_threshold;
extern unsigned long pack_size_limit_cfg; extern unsigned long pack_size_limit_cfg;
extern int max_allowed_tree_depth;
/* /*
* Accessors for the core.sharedrepository config which lazy-load the value * Accessors for the core.sharedrepository config which lazy-load the value