64eb14d310
We added an fsck check ined8b10f631
(fsck: check .gitmodules content, 2018-05-02) as a defense against the vulnerability from0383bbb901
(submodule-config: verify submodule names as paths, 2018-04-30). With the idea that up-to-date hosting sites could protect downstream unpatched clients that fetch from them. As part of that defense, we reject any ".gitmodules" entry that is not syntactically valid. The theory is that if we cannot even parse the file, we cannot accurately check it for vulnerabilities. And anybody with a broken .gitmodules file would eventually want to know anyway. But there are a few reasons this is a bad tradeoff in practice: - for this particular vulnerability, the client has to be able to parse the file. So you cannot sneak an attack through using a broken file, assuming the config parsers for the process running fsck and the eventual victim are functionally equivalent. - a broken .gitmodules file is not necessarily a problem. Our fsck check detects .gitmodules in _any_ tree, not just at the root. And the presence of a .gitmodules file does not necessarily mean it will be used; you'd have to also have gitlinks in the tree. The cgit repository, for example, has a file named .gitmodules from a pre-submodule attempt at sharing code, but does not actually have any gitlinks. - when the fsck check is used to reject a push, it's often hard to work around. The pusher may not have full control over the destination repository (e.g., if it's on a hosting server, they may need to contact the hosting site's support). And the broken .gitmodules may be too far back in history for rewriting to be feasible (again, this is an issue for cgit). So we're being unnecessarily restrictive without actually improving the security in a meaningful way. It would be more convenient to downgrade this check to "info", which means we'd still comment on it, but not reject a push. Site admins can already do this via config, but we should ship sensible defaults. There are a few counterpoints to consider in favor of keeping the check as an error: - the first point above assumes that the config parsers for the victim and the fsck process are equivalent. This is pretty true now, but as time goes on will become less so. Hosting sites are likely to upgrade their version of Git, whereas vulnerable clients will be stagnant (if they did upgrade, they'd cease to be vulnerable!). So in theory we may see drift over time between what two config parsers will accept. In practice, this is probably OK. The config format is pretty established at this point and shouldn't change a lot. And the farther we get from the announcement of the vulnerability, the less interesting this extra layer of protection becomes. I.e., it was _most_ valuable on day 0, when everybody's client was still vulnerable and hosting sites could protect people. But as time goes on and people upgrade, the population of vulnerable clients becomes smaller and smaller. - In theory this could protect us from other vulnerabilities in the future. E.g., .gitmodules are the only way for a malicious repository to feed data to the config parser, so this check could similarly protect clients from a future (to-be-found) bug there. But that's trading a hypothetical case for real-world pain today. If we do find such a bug, the hosting site would need to be updated to fix it, too. At which point we could figure out whether it's possible to detect _just_ the malicious case without hurting existing broken-but-not-evil cases. - Until recently, we hadn't made any restrictions on .gitmodules content. So now in tightening that we're hitting cases where certain things used to work, but don't anymore. There's some moderate pain now. But as time goes on, we'll see more (and more varied) cases that will make tightening harder in the future. So there's some argument for putting rules in place _now_, before users grow more cases that violate them. Again, this is trading pain now for hypothetical benefit in the future. And if we try hard in the future to keep our tightening to a minimum (i.e., rejecting true maliciousness without hurting broken-but-not-evil repos), then that reduces even the hypothetical benefit. Considering both sets of arguments, it makes sense to loosen this check for now. Note that we have to tweak the test in t7415 since fsck will no longer consider this a fatal error. But we still check that it reports the warning, and that we don't get the spurious error from the config code. Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>
195 lines
5.2 KiB
Bash
Executable File
195 lines
5.2 KiB
Bash
Executable File
#!/bin/sh
|
|
|
|
test_description='check handling of .. in submodule names
|
|
|
|
Exercise the name-checking function on a variety of names, and then give a
|
|
real-world setup that confirms we catch this in practice.
|
|
'
|
|
. ./test-lib.sh
|
|
. "$TEST_DIRECTORY"/lib-pack.sh
|
|
|
|
test_expect_success 'check names' '
|
|
cat >expect <<-\EOF &&
|
|
valid
|
|
valid/with/paths
|
|
EOF
|
|
|
|
git submodule--helper check-name >actual <<-\EOF &&
|
|
valid
|
|
valid/with/paths
|
|
|
|
../foo
|
|
/../foo
|
|
..\foo
|
|
\..\foo
|
|
foo/..
|
|
foo/../
|
|
foo\..
|
|
foo\..\
|
|
foo/../bar
|
|
EOF
|
|
|
|
test_cmp expect actual
|
|
'
|
|
|
|
test_expect_success 'create innocent subrepo' '
|
|
git init innocent &&
|
|
git -C innocent commit --allow-empty -m foo
|
|
'
|
|
|
|
test_expect_success 'submodule add refuses invalid names' '
|
|
test_must_fail \
|
|
git submodule add --name ../../modules/evil "$PWD/innocent" evil
|
|
'
|
|
|
|
test_expect_success 'add evil submodule' '
|
|
git submodule add "$PWD/innocent" evil &&
|
|
|
|
mkdir modules &&
|
|
cp -r .git/modules/evil modules &&
|
|
write_script modules/evil/hooks/post-checkout <<-\EOF &&
|
|
echo >&2 "RUNNING POST CHECKOUT"
|
|
EOF
|
|
|
|
git config -f .gitmodules submodule.evil.update checkout &&
|
|
git config -f .gitmodules --rename-section \
|
|
submodule.evil submodule.../../modules/evil &&
|
|
git add modules &&
|
|
git commit -am evil
|
|
'
|
|
|
|
# This step seems like it shouldn't be necessary, since the payload is
|
|
# contained entirely in the evil submodule. But due to the vagaries of the
|
|
# submodule code, checking out the evil module will fail unless ".git/modules"
|
|
# exists. Adding another submodule (with a name that sorts before "evil") is an
|
|
# easy way to make sure this is the case in the victim clone.
|
|
test_expect_success 'add other submodule' '
|
|
git submodule add "$PWD/innocent" another-module &&
|
|
git add another-module &&
|
|
git commit -am another
|
|
'
|
|
|
|
test_expect_success 'clone evil superproject' '
|
|
git clone --recurse-submodules . victim >output 2>&1 &&
|
|
! grep "RUNNING POST CHECKOUT" output
|
|
'
|
|
|
|
test_expect_success 'fsck detects evil superproject' '
|
|
test_must_fail git fsck
|
|
'
|
|
|
|
test_expect_success 'transfer.fsckObjects detects evil superproject (unpack)' '
|
|
rm -rf dst.git &&
|
|
git init --bare dst.git &&
|
|
git -C dst.git config transfer.fsckObjects true &&
|
|
test_must_fail git push dst.git HEAD
|
|
'
|
|
|
|
test_expect_success 'transfer.fsckObjects detects evil superproject (index)' '
|
|
rm -rf dst.git &&
|
|
git init --bare dst.git &&
|
|
git -C dst.git config transfer.fsckObjects true &&
|
|
git -C dst.git config transfer.unpackLimit 1 &&
|
|
test_must_fail git push dst.git HEAD
|
|
'
|
|
|
|
# Normally our packs contain commits followed by trees followed by blobs. This
|
|
# reverses the order, which requires backtracking to find the context of a
|
|
# blob. We'll start with a fresh gitmodules-only tree to make it simpler.
|
|
test_expect_success 'create oddly ordered pack' '
|
|
git checkout --orphan odd &&
|
|
git rm -rf --cached . &&
|
|
git add .gitmodules &&
|
|
git commit -m odd &&
|
|
{
|
|
pack_header 3 &&
|
|
pack_obj $(git rev-parse HEAD:.gitmodules) &&
|
|
pack_obj $(git rev-parse HEAD^{tree}) &&
|
|
pack_obj $(git rev-parse HEAD)
|
|
} >odd.pack &&
|
|
pack_trailer odd.pack
|
|
'
|
|
|
|
test_expect_success 'transfer.fsckObjects handles odd pack (unpack)' '
|
|
rm -rf dst.git &&
|
|
git init --bare dst.git &&
|
|
test_must_fail git -C dst.git unpack-objects --strict <odd.pack
|
|
'
|
|
|
|
test_expect_success 'transfer.fsckObjects handles odd pack (index)' '
|
|
rm -rf dst.git &&
|
|
git init --bare dst.git &&
|
|
test_must_fail git -C dst.git index-pack --strict --stdin <odd.pack
|
|
'
|
|
|
|
test_expect_success 'index-pack --strict works for non-repo pack' '
|
|
rm -rf dst.git &&
|
|
git init --bare dst.git &&
|
|
cp odd.pack dst.git &&
|
|
test_must_fail git -C dst.git index-pack --strict odd.pack 2>output &&
|
|
# Make sure we fail due to bad gitmodules content, not because we
|
|
# could not read the blob in the first place.
|
|
grep gitmodulesName output
|
|
'
|
|
|
|
test_expect_success 'fsck detects symlinked .gitmodules file' '
|
|
git init symlink &&
|
|
(
|
|
cd symlink &&
|
|
|
|
# Make the tree directly to avoid index restrictions.
|
|
#
|
|
# Because symlinks store the target as a blob, choose
|
|
# a pathname that could be parsed as a .gitmodules file
|
|
# to trick naive non-symlink-aware checking.
|
|
tricky="[foo]bar=true" &&
|
|
content=$(git hash-object -w ../.gitmodules) &&
|
|
target=$(printf "$tricky" | git hash-object -w --stdin) &&
|
|
{
|
|
printf "100644 blob $content\t$tricky\n" &&
|
|
printf "120000 blob $target\t.gitmodules\n"
|
|
} | git mktree &&
|
|
|
|
# Check not only that we fail, but that it is due to the
|
|
# symlink detector; this grep string comes from the config
|
|
# variable name and will not be translated.
|
|
test_must_fail git fsck 2>output &&
|
|
grep gitmodulesSymlink output
|
|
)
|
|
'
|
|
|
|
test_expect_success 'fsck detects non-blob .gitmodules' '
|
|
git init non-blob &&
|
|
(
|
|
cd non-blob &&
|
|
|
|
# As above, make the funny tree directly to avoid index
|
|
# restrictions.
|
|
mkdir subdir &&
|
|
cp ../.gitmodules subdir/file &&
|
|
git add subdir/file &&
|
|
git commit -m ok &&
|
|
git ls-tree HEAD | sed s/subdir/.gitmodules/ | git mktree &&
|
|
|
|
test_must_fail git fsck 2>output &&
|
|
grep gitmodulesBlob output
|
|
)
|
|
'
|
|
|
|
test_expect_success 'fsck detects corrupt .gitmodules' '
|
|
git init corrupt &&
|
|
(
|
|
cd corrupt &&
|
|
|
|
echo "[broken" >.gitmodules &&
|
|
git add .gitmodules &&
|
|
git commit -m "broken gitmodules" &&
|
|
|
|
git fsck 2>output &&
|
|
grep gitmodulesParse output &&
|
|
test_i18ngrep ! "bad config" output
|
|
)
|
|
'
|
|
|
|
test_done
|