fsck: detect very large tree pathnames

In general, Git tries not to arbitrarily limit what it will store, and there are currently no limits at all on the size of the path we find in a tree. In theory you could have one that is gigabytes long. But in practice this freedom is not really helping anybody, and is potentially harmful: 1. Most operating systems have much lower limits for the size of a single pathname component (e.g., on Linux you'll generally get ENAMETOOLONG for anything over 255 bytes). And while you _can_ use Git in a way that never touches the filesystem (manipulating the index and trees directly), it's still probably not a good idea to have gigantic tree names. Many operations load and traverse them, so any clever Git-as-a-database scheme is likely to perform poorly in that case. 2. We still have a lot of code which assumes strings are reasonably sized, and I won't be at all surprised if you can trigger some interesting integer overflows with gigantic pathnames. Stopping malicious trees from entering the repository provides an extra line of defense, protecting downstream code. This patch implements an fsck check so that such trees can be rejected by transfer.fsckObjects. I've picked a reasonably high maximum depth here (4096) that hopefully should not bother anybody in practice. I've also made it configurable, as an escape hatch. Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Jeff King committed Aug 31, 2023 at 02:20 UTC 0fbcaef6b49250eb92b08e70f815962c729d5615
4 files changed +41 -1
Documentation/fsck-msgids.txt
+7
@@ -103,6 +103,13 @@
103 `hasDotgit`::
104 (WARN) A tree contains an entry named `.git`.
105
106 +`largePathname`::
107 + (WARN) A tree contains an entry with a very long path name. If
108 + the value of `fsck.largePathname` contains a colon, that value
109 + is used as the maximum allowable length (e.g., "warn:10" would
110 + complain about any path component of 11 or more bytes). The
111 + default value is 4096.
112 +
113 `mailmapSymlink`::
114 (INFO) `.mailmap` is a symlink.
115
fsck.c
+23 -1
@@ -24,6 +24,8 @@
24 #include "credential.h"
25 #include "help.h"
26
27 +static ssize_t max_tree_entry_len = 4096;
28 +
29 #define STR(x) #x
30 #define MSG_ID(id, msg_type) { STR(id), NULL, NULL, FSCK_##msg_type },
31 static struct {
@@ -154,15 +156,29 @@ void fsck_set_msg_type(struct fsck_options *options,
156 const char *msg_id_str, const char *msg_type_str)
157 {
158 int msg_id = parse_msg_id(msg_id_str);
157 - enum fsck_msg_type msg_type = parse_msg_type(msg_type_str);
159 + char *to_free = NULL;
160 + enum fsck_msg_type msg_type;
161
162 if (msg_id < 0)
163 die("Unhandled message id: %s", msg_id_str);
164
165 + if (msg_id == FSCK_MSG_LARGE_PATHNAME) {
166 + const char *colon = strchr(msg_type_str, ':');
167 + if (colon) {
168 + msg_type_str = to_free =
169 + xmemdupz(msg_type_str, colon - msg_type_str);
170 + colon++;
171 + if (!git_parse_ssize_t(colon, &max_tree_entry_len))
172 + die("unable to parse max tree entry len: %s", colon);
173 + }
174 + }
175 + msg_type = parse_msg_type(msg_type_str);
176 +
177 if (msg_type != FSCK_ERROR && msg_id_info[msg_id].msg_type == FSCK_FATAL)
178 die("Cannot demote %s to %s", msg_id_str, msg_type_str);
179
180 fsck_set_msg_type_from_ids(options, msg_id, msg_type);
181 + free(to_free);
182 }
183
184 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,
594 int has_bad_modes = 0;
595 int has_dup_entries = 0;
596 int not_properly_sorted = 0;
597 + int has_large_name = 0;
598 struct tree_desc desc;
599 unsigned o_mode;
600 const char *o_name;
@@ -607,6 +624,7 @@ static int fsck_tree(const struct object_id *tree_oid,
624 has_dotdot |= !strcmp(name, "..");
625 has_dotgit |= is_hfs_dotgit(name) || is_ntfs_dotgit(name);
626 has_zero_pad |= *(char *)desc.buffer == '0';
627 + has_large_name |= tree_entry_len(&desc.entry) > max_tree_entry_len;
628
629 if (is_hfs_dotgitmodules(name) || is_ntfs_dotgitmodules(name)) {
630 if (!S_ISLNK(mode))
@@ -749,6 +767,10 @@ static int fsck_tree(const struct object_id *tree_oid,
767 retval += report(options, tree_oid, OBJ_TREE,
768 FSCK_MSG_TREE_NOT_SORTED,
769 "not properly sorted");
770 + if (has_large_name)
771 + retval += report(options, tree_oid, OBJ_TREE,
772 + FSCK_MSG_LARGE_PATHNAME,
773 + "contains excessively large pathname");
774 return retval;
775 }
776
fsck.h
+1
@@ -73,6 +73,7 @@ enum fsck_msg_type {
73 FUNC(NULL_SHA1, WARN) \
74 FUNC(ZERO_PADDED_FILEMODE, WARN) \
75 FUNC(NUL_IN_COMMIT, WARN) \
76 + FUNC(LARGE_PATHNAME, WARN) \
77 /* infos (reported as warnings, but ignored by default) */ \
78 FUNC(BAD_FILEMODE, INFO) \
79 FUNC(GITMODULES_PARSE, INFO) \
t/t1450-fsck.sh
+10
@@ -589,6 +589,16 @@ test_expect_success 'fsck notices submodule entry pointing to null sha1' '
589 )
590 '
591
592 +test_expect_success 'fsck notices excessively large tree entry name' '
593 + git init large-name &&
594 + (
595 + cd large-name &&
596 + test_commit a-long-name &&
597 + git -c fsck.largePathname=warn:10 fsck 2>out &&
598 + grep "warning.*large pathname" out
599 + )
600 +'
601 +
602 while read name path pretty; do
603 while read mode type; do
604 : ${pretty:=$path}