Skip to content

Commit a33fea0

Browse files
committed
fsck: warn about symlink pointing inside a gitdir
In the wake of fixing a vulnerability where `git clone` mistakenly followed a symbolic link that it had just written while checking out files, writing into a gitdir, let's add some defense-in-depth by teaching `git fsck` to report symbolic links stored in its trees that point inside `.git/`. Even though the Git project never made any promises about the exact shape of the `.git/` directory's contents, there are likely repositories out there containing symbolic links that point inside the gitdir. For that reason, let's only report these as warnings, not as errors. Security-conscious users are encouraged to configure `fsck.symlinkPointsToGitDir = error`. Signed-off-by: Johannes Schindelin <[email protected]>
1 parent 20f3588 commit a33fea0

File tree

4 files changed

+117
-0
lines changed

4 files changed

+117
-0
lines changed

Documentation/fsck-msgids.txt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,18 @@
157157
`nullSha1`::
158158
(WARN) Tree contains entries pointing to a null sha1.
159159

160+
`symlinkPointsToGitDir`::
161+
(WARN) Symbolic link points inside a gitdir.
162+
163+
`symlinkTargetBlob`::
164+
(ERROR) A non-blob found instead of a symbolic link's target.
165+
166+
`symlinkTargetLength`::
167+
(WARN) Symbolic link target longer than maximum path length.
168+
169+
`symlinkTargetMissing`::
170+
(ERROR) Unable to read symbolic link target's blob.
171+
160172
`treeNotSorted`::
161173
(ERROR) A tree is not properly sorted.
162174

fsck.c

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -636,6 +636,8 @@ static int fsck_tree(const struct object_id *tree_oid,
636636
retval += report(options, tree_oid, OBJ_TREE,
637637
FSCK_MSG_MAILMAP_SYMLINK,
638638
".mailmap is a symlink");
639+
oidset_insert(&options->symlink_targets_found,
640+
entry_oid);
639641
}
640642

641643
if ((backslash = strchr(name, '\\'))) {
@@ -1228,6 +1230,56 @@ static int fsck_blob(const struct object_id *oid, const char *buf,
12281230
}
12291231
}
12301232

1233+
if (oidset_contains(&options->symlink_targets_found, oid)) {
1234+
const char *ptr = buf;
1235+
const struct object_id *reported = NULL;
1236+
1237+
oidset_insert(&options->symlink_targets_done, oid);
1238+
1239+
if (!buf || size > PATH_MAX) {
1240+
/*
1241+
* A missing buffer here is a sign that the caller found the
1242+
* blob too gigantic to load into memory. Let's just consider
1243+
* that an error.
1244+
*/
1245+
return report(options, oid, OBJ_BLOB,
1246+
FSCK_MSG_SYMLINK_TARGET_LENGTH,
1247+
"symlink target too long");
1248+
}
1249+
1250+
while (!reported && ptr) {
1251+
const char *p = ptr;
1252+
char c, *slash = strchrnul(ptr, '/');
1253+
char *backslash = memchr(ptr, '\\', slash - ptr);
1254+
1255+
c = *slash;
1256+
*slash = '\0';
1257+
1258+
while (!reported && backslash) {
1259+
*backslash = '\0';
1260+
if (is_ntfs_dotgit(p))
1261+
ret |= report(options, reported = oid, OBJ_BLOB,
1262+
FSCK_MSG_SYMLINK_POINTS_TO_GIT_DIR,
1263+
"symlink target points to git dir");
1264+
*backslash = '\\';
1265+
p = backslash + 1;
1266+
backslash = memchr(p, '\\', slash - p);
1267+
}
1268+
if (!reported && is_ntfs_dotgit(p))
1269+
ret |= report(options, reported = oid, OBJ_BLOB,
1270+
FSCK_MSG_SYMLINK_POINTS_TO_GIT_DIR,
1271+
"symlink target points to git dir");
1272+
1273+
if (!reported && is_hfs_dotgit(ptr))
1274+
ret |= report(options, reported = oid, OBJ_BLOB,
1275+
FSCK_MSG_SYMLINK_POINTS_TO_GIT_DIR,
1276+
"symlink target points to git dir");
1277+
1278+
*slash = c;
1279+
ptr = c ? slash + 1 : NULL;
1280+
}
1281+
}
1282+
12311283
return ret;
12321284
}
12331285

@@ -1319,6 +1371,10 @@ int fsck_finish(struct fsck_options *options)
13191371
FSCK_MSG_GITATTRIBUTES_MISSING, FSCK_MSG_GITATTRIBUTES_BLOB,
13201372
options, ".gitattributes");
13211373

1374+
ret |= fsck_blobs(&options->symlink_targets_found, &options->symlink_targets_done,
1375+
FSCK_MSG_SYMLINK_TARGET_MISSING, FSCK_MSG_SYMLINK_TARGET_BLOB,
1376+
options, "<symlink-target>");
1377+
13221378
return ret;
13231379
}
13241380

fsck.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ enum fsck_msg_type {
6363
FUNC(GITATTRIBUTES_LARGE, ERROR) \
6464
FUNC(GITATTRIBUTES_LINE_LENGTH, ERROR) \
6565
FUNC(GITATTRIBUTES_BLOB, ERROR) \
66+
FUNC(SYMLINK_TARGET_MISSING, ERROR) \
67+
FUNC(SYMLINK_TARGET_BLOB, ERROR) \
6668
/* warnings */ \
6769
FUNC(EMPTY_NAME, WARN) \
6870
FUNC(FULL_PATHNAME, WARN) \
@@ -72,6 +74,8 @@ enum fsck_msg_type {
7274
FUNC(NULL_SHA1, WARN) \
7375
FUNC(ZERO_PADDED_FILEMODE, WARN) \
7476
FUNC(NUL_IN_COMMIT, WARN) \
77+
FUNC(SYMLINK_TARGET_LENGTH, WARN) \
78+
FUNC(SYMLINK_POINTS_TO_GIT_DIR, WARN) \
7579
/* infos (reported as warnings, but ignored by default) */ \
7680
FUNC(BAD_FILEMODE, INFO) \
7781
FUNC(GITMODULES_PARSE, INFO) \
@@ -139,6 +143,8 @@ struct fsck_options {
139143
struct oidset gitmodules_done;
140144
struct oidset gitattributes_found;
141145
struct oidset gitattributes_done;
146+
struct oidset symlink_targets_found;
147+
struct oidset symlink_targets_done;
142148
kh_oid_map_t *object_names;
143149
};
144150

@@ -148,6 +154,8 @@ struct fsck_options {
148154
.gitmodules_done = OIDSET_INIT, \
149155
.gitattributes_found = OIDSET_INIT, \
150156
.gitattributes_done = OIDSET_INIT, \
157+
.symlink_targets_found = OIDSET_INIT, \
158+
.symlink_targets_done = OIDSET_INIT, \
151159
.error_func = fsck_error_function \
152160
}
153161
#define FSCK_OPTIONS_STRICT { \
@@ -156,6 +164,8 @@ struct fsck_options {
156164
.gitmodules_done = OIDSET_INIT, \
157165
.gitattributes_found = OIDSET_INIT, \
158166
.gitattributes_done = OIDSET_INIT, \
167+
.symlink_targets_found = OIDSET_INIT, \
168+
.symlink_targets_done = OIDSET_INIT, \
159169
.error_func = fsck_error_function, \
160170
}
161171
#define FSCK_OPTIONS_MISSING_GITMODULES { \
@@ -164,6 +174,8 @@ struct fsck_options {
164174
.gitmodules_done = OIDSET_INIT, \
165175
.gitattributes_found = OIDSET_INIT, \
166176
.gitattributes_done = OIDSET_INIT, \
177+
.symlink_targets_found = OIDSET_INIT, \
178+
.symlink_targets_done = OIDSET_INIT, \
167179
.error_func = fsck_error_cb_print_missing_gitmodules, \
168180
}
169181

t/t1450-fsck.sh

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1023,4 +1023,41 @@ test_expect_success 'fsck error on gitattributes with excessive size' '
10231023
test_cmp expected actual
10241024
'
10251025

1026+
test_expect_success 'fsck warning on symlink target with excessive length' '
1027+
symlink_target=$(printf "pattern %032769d" 1 | git hash-object -w --stdin) &&
1028+
test_when_finished "remove_object $symlink_target" &&
1029+
tree=$(printf "120000 blob %s\t%s\n" $symlink_target symlink | git mktree) &&
1030+
test_when_finished "remove_object $tree" &&
1031+
cat >expected <<-EOF &&
1032+
warning in blob $symlink_target: symlinkTargetLength: symlink target too long
1033+
EOF
1034+
git fsck --no-dangling >actual 2>&1 &&
1035+
test_cmp expected actual
1036+
'
1037+
1038+
test_expect_success 'fsck warning on symlink target pointing inside git dir' '
1039+
gitdir=$(printf ".git" | git hash-object -w --stdin) &&
1040+
ntfs_gitdir=$(printf "GIT~1" | git hash-object -w --stdin) &&
1041+
hfs_gitdir=$(printf ".${u200c}git" | git hash-object -w --stdin) &&
1042+
inside_gitdir=$(printf "nested/.git/config" | git hash-object -w --stdin) &&
1043+
benign_target=$(printf "legit/config" | git hash-object -w --stdin) &&
1044+
tree=$(printf "120000 blob %s\t%s\n" \
1045+
$benign_target benign_target \
1046+
$gitdir gitdir \
1047+
$hfs_gitdir hfs_gitdir \
1048+
$inside_gitdir inside_gitdir \
1049+
$ntfs_gitdir ntfs_gitdir |
1050+
git mktree) &&
1051+
for o in $gitdir $ntfs_gitdir $hfs_gitdir $inside_gitdir $benign_target $tree
1052+
do
1053+
test_when_finished "remove_object $o" || return 1
1054+
done &&
1055+
printf "warning in blob %s: symlinkPointsToGitDir: symlink target points to git dir\n" \
1056+
$gitdir $hfs_gitdir $inside_gitdir $ntfs_gitdir |
1057+
sort >expected &&
1058+
git fsck --no-dangling >actual 2>&1 &&
1059+
sort actual >actual.sorted &&
1060+
test_cmp expected actual.sorted
1061+
'
1062+
10261063
test_done

0 commit comments

Comments
 (0)