Skip to content

Commit 27ab478

Browse files
pks-tgitster
authored andcommitted
fsck: implement checks for gitattributes
Recently, a vulnerability was reported that can lead to an out-of-bounds write when reading an unreasonably large gitattributes file. The root cause of this error are multiple integer overflows in different parts of the code when there are either too many lines, when paths are too long, when attribute names are too long, or when there are too many attributes declared for a pattern. As all of these are related to size, it seems reasonable to restrict the size of the gitattributes file via git-fsck(1). This allows us to both stop distributing known-vulnerable objects via common hosting platforms that have fsck enabled, and users to protect themselves by enabling the `fetch.fsckObjects` config. There are basically two checks: 1. We verify that size of the gitattributes file is smaller than 100MB. 2. We verify that the maximum line length does not exceed 2048 bytes. With the preceding commits, both of these conditions would cause us to either ignore the complete gitattributes file or blob in the first case, or the specific line in the second case. Now with these consistency checks added, we also grow the ability to stop distributing such files in the first place when `receive.fsckObjects` is enabled. Signed-off-by: Patrick Steinhardt <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent f8587c3 commit 27ab478

File tree

3 files changed

+73
-1
lines changed

3 files changed

+73
-1
lines changed

fsck.c

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#include "object-store.h"
33
#include "repository.h"
44
#include "object.h"
5+
#include "attr.h"
56
#include "blob.h"
67
#include "tree.h"
78
#include "tree-walk.h"
@@ -615,7 +616,10 @@ static int fsck_tree(const struct object_id *tree_oid,
615616
}
616617

617618
if (is_hfs_dotgitattributes(name) || is_ntfs_dotgitattributes(name)) {
618-
if (S_ISLNK(mode))
619+
if (!S_ISLNK(mode))
620+
oidset_insert(&options->gitattributes_found,
621+
entry_oid);
622+
else
619623
retval += report(options, tree_oid, OBJ_TREE,
620624
FSCK_MSG_GITATTRIBUTES_SYMLINK,
621625
".gitattributes is a symlink");
@@ -1206,6 +1210,35 @@ static int fsck_blob(const struct object_id *oid, const char *buf,
12061210
ret |= data.ret;
12071211
}
12081212

1213+
if (oidset_contains(&options->gitattributes_found, oid)) {
1214+
const char *ptr;
1215+
1216+
oidset_insert(&options->gitattributes_done, oid);
1217+
1218+
if (!buf || size > ATTR_MAX_FILE_SIZE) {
1219+
/*
1220+
* A missing buffer here is a sign that the caller found the
1221+
* blob too gigantic to load into memory. Let's just consider
1222+
* that an error.
1223+
*/
1224+
return report(options, oid, OBJ_BLOB,
1225+
FSCK_MSG_GITATTRIBUTES_LARGE,
1226+
".gitattributes too large to parse");
1227+
}
1228+
1229+
for (ptr = buf; *ptr; ) {
1230+
const char *eol = strchrnul(ptr, '\n');
1231+
if (eol - ptr >= ATTR_MAX_LINE_LENGTH) {
1232+
ret |= report(options, oid, OBJ_BLOB,
1233+
FSCK_MSG_GITATTRIBUTES_LINE_LENGTH,
1234+
".gitattributes has too long lines to parse");
1235+
break;
1236+
}
1237+
1238+
ptr = *eol ? eol + 1 : eol;
1239+
}
1240+
}
1241+
12091242
return ret;
12101243
}
12111244

@@ -1293,6 +1326,9 @@ int fsck_finish(struct fsck_options *options)
12931326
ret |= fsck_blobs(&options->gitmodules_found, &options->gitmodules_done,
12941327
FSCK_MSG_GITMODULES_MISSING, FSCK_MSG_GITMODULES_BLOB,
12951328
options, ".gitmodules");
1329+
ret |= fsck_blobs(&options->gitattributes_found, &options->gitattributes_done,
1330+
FSCK_MSG_GITATTRIBUTES_MISSING, FSCK_MSG_GITATTRIBUTES_BLOB,
1331+
options, ".gitattributes");
12961332

12971333
return ret;
12981334
}

fsck.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ enum fsck_msg_type {
5555
FUNC(GITMODULES_URL, ERROR) \
5656
FUNC(GITMODULES_PATH, ERROR) \
5757
FUNC(GITMODULES_UPDATE, ERROR) \
58+
FUNC(GITATTRIBUTES_MISSING, ERROR) \
59+
FUNC(GITATTRIBUTES_LARGE, ERROR) \
60+
FUNC(GITATTRIBUTES_LINE_LENGTH, ERROR) \
61+
FUNC(GITATTRIBUTES_BLOB, ERROR) \
5862
/* warnings */ \
5963
FUNC(BAD_FILEMODE, WARN) \
6064
FUNC(EMPTY_NAME, WARN) \
@@ -129,25 +133,33 @@ struct fsck_options {
129133
struct oidset skiplist;
130134
struct oidset gitmodules_found;
131135
struct oidset gitmodules_done;
136+
struct oidset gitattributes_found;
137+
struct oidset gitattributes_done;
132138
kh_oid_map_t *object_names;
133139
};
134140

135141
#define FSCK_OPTIONS_DEFAULT { \
136142
.skiplist = OIDSET_INIT, \
137143
.gitmodules_found = OIDSET_INIT, \
138144
.gitmodules_done = OIDSET_INIT, \
145+
.gitattributes_found = OIDSET_INIT, \
146+
.gitattributes_done = OIDSET_INIT, \
139147
.error_func = fsck_error_function \
140148
}
141149
#define FSCK_OPTIONS_STRICT { \
142150
.strict = 1, \
143151
.gitmodules_found = OIDSET_INIT, \
144152
.gitmodules_done = OIDSET_INIT, \
153+
.gitattributes_found = OIDSET_INIT, \
154+
.gitattributes_done = OIDSET_INIT, \
145155
.error_func = fsck_error_function, \
146156
}
147157
#define FSCK_OPTIONS_MISSING_GITMODULES { \
148158
.strict = 1, \
149159
.gitmodules_found = OIDSET_INIT, \
150160
.gitmodules_done = OIDSET_INIT, \
161+
.gitattributes_found = OIDSET_INIT, \
162+
.gitattributes_done = OIDSET_INIT, \
151163
.error_func = fsck_error_cb_print_missing_gitmodules, \
152164
}
153165

t/t1450-fsck.sh

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -865,4 +865,28 @@ test_expect_success 'detect corrupt index file in fsck' '
865865
test_i18ngrep "bad index file" errors
866866
'
867867

868+
test_expect_success 'fsck error on gitattributes with excessive line lengths' '
869+
blob=$(printf "pattern %02048d" 1 | git hash-object -w --stdin) &&
870+
test_when_finished "remove_object $blob" &&
871+
tree=$(printf "100644 blob %s\t%s\n" $blob .gitattributes | git mktree) &&
872+
test_when_finished "remove_object $tree" &&
873+
cat >expected <<-EOF &&
874+
error in blob $blob: gitattributesLineLength: .gitattributes has too long lines to parse
875+
EOF
876+
test_must_fail git fsck --no-dangling >actual 2>&1 &&
877+
test_cmp expected actual
878+
'
879+
880+
test_expect_success 'fsck error on gitattributes with excessive size' '
881+
blob=$(test-tool genzeros $((100 * 1024 * 1024 + 1)) | git hash-object -w --stdin) &&
882+
test_when_finished "remove_object $blob" &&
883+
tree=$(printf "100644 blob %s\t%s\n" $blob .gitattributes | git mktree) &&
884+
test_when_finished "remove_object $tree" &&
885+
cat >expected <<-EOF &&
886+
error in blob $blob: gitattributesLarge: .gitattributes too large to parse
887+
EOF
888+
test_must_fail git fsck --no-dangling >actual 2>&1 &&
889+
test_cmp expected actual
890+
'
891+
868892
test_done

0 commit comments

Comments
 (0)