Git fork
1#!/bin/sh
2
3test_description='check broken or malicious patterns in .git* files
4
5Such as:
6
7 - presence of .. in submodule names;
8 Exercise the name-checking function on a variety of names, and then give a
9 real-world setup that confirms we catch this in practice.
10
11 - nested submodule names
12
13 - symlinked .gitmodules, etc
14'
15
16. ./test-lib.sh
17. "$TEST_DIRECTORY"/lib-pack.sh
18
19test_expect_success 'setup' '
20 git config --global protocol.file.allow always
21'
22
23test_expect_success 'check names' '
24 cat >expect <<-\EOF &&
25 valid
26 valid/with/paths
27 EOF
28
29 test-tool submodule check-name >actual <<-\EOF &&
30 valid
31 valid/with/paths
32
33 ../foo
34 /../foo
35 ..\foo
36 \..\foo
37 foo/..
38 foo/../
39 foo\..
40 foo\..\
41 foo/../bar
42 EOF
43
44 test_cmp expect actual
45'
46
47test_expect_success 'check urls' '
48 cat >expect <<-\EOF &&
49 ./bar/baz/foo.git
50 https://example.com/foo.git
51 http://example.com:80/deeper/foo.git
52 EOF
53
54 test-tool submodule check-url >actual <<-\EOF &&
55 ./bar/baz/foo.git
56 https://example.com/foo.git
57 http://example.com:80/deeper/foo.git
58 -a./foo
59 ../../..//test/foo.git
60 ../../../../../:localhost:8080/foo.git
61 ..\../.\../:example.com/foo.git
62 ./%0ahost=example.com/foo.git
63 https://one.example.com/evil?%0ahost=two.example.com
64 https:///example.com/foo.git
65 http://example.com:test/foo.git
66 https::example.com/foo.git
67 http:::example.com/foo.git
68 EOF
69
70 test_cmp expect actual
71'
72
73test_expect_success 'create innocent subrepo' '
74 git init innocent &&
75 git -C innocent commit --allow-empty -m foo
76'
77
78test_expect_success 'submodule add refuses invalid names' '
79 test_must_fail \
80 git submodule add --name ../../modules/evil "$PWD/innocent" evil
81'
82
83test_expect_success 'add evil submodule' '
84 git submodule add "$PWD/innocent" evil &&
85
86 mkdir modules &&
87 cp -r .git/modules/evil modules &&
88 write_script modules/evil/hooks/post-checkout <<-\EOF &&
89 echo >&2 "RUNNING POST CHECKOUT"
90 EOF
91
92 git config -f .gitmodules submodule.evil.update checkout &&
93 git config -f .gitmodules --rename-section \
94 submodule.evil submodule.../../modules/evil &&
95 git add modules &&
96 git commit -am evil
97'
98
99# This step seems like it shouldn't be necessary, since the payload is
100# contained entirely in the evil submodule. But due to the vagaries of the
101# submodule code, checking out the evil module will fail unless ".git/modules"
102# exists. Adding another submodule (with a name that sorts before "evil") is an
103# easy way to make sure this is the case in the victim clone.
104test_expect_success 'add other submodule' '
105 git submodule add "$PWD/innocent" another-module &&
106 git add another-module &&
107 git commit -am another
108'
109
110test_expect_success 'clone evil superproject' '
111 git clone --recurse-submodules . victim >output 2>&1 &&
112 ! grep "RUNNING POST CHECKOUT" output
113'
114
115test_expect_success 'fsck detects evil superproject' '
116 test_must_fail git fsck
117'
118
119test_expect_success 'transfer.fsckObjects detects evil superproject (unpack)' '
120 rm -rf dst.git &&
121 git init --bare dst.git &&
122 git -C dst.git config transfer.fsckObjects true &&
123 test_must_fail git push dst.git HEAD
124'
125
126test_expect_success 'transfer.fsckObjects detects evil superproject (index)' '
127 rm -rf dst.git &&
128 git init --bare dst.git &&
129 git -C dst.git config transfer.fsckObjects true &&
130 git -C dst.git config transfer.unpackLimit 1 &&
131 test_must_fail git push dst.git HEAD
132'
133
134# Normally our packs contain commits followed by trees followed by blobs. This
135# reverses the order, which requires backtracking to find the context of a
136# blob. We'll start with a fresh gitmodules-only tree to make it simpler.
137test_expect_success 'create oddly ordered pack' '
138 git checkout --orphan odd &&
139 git rm -rf --cached . &&
140 git add .gitmodules &&
141 git commit -m odd &&
142 {
143 pack_header 3 &&
144 pack_obj $(git rev-parse HEAD:.gitmodules) &&
145 pack_obj $(git rev-parse HEAD^{tree}) &&
146 pack_obj $(git rev-parse HEAD)
147 } >odd.pack &&
148 pack_trailer odd.pack
149'
150
151test_expect_success 'transfer.fsckObjects handles odd pack (unpack)' '
152 rm -rf dst.git &&
153 git init --bare dst.git &&
154 test_must_fail git -C dst.git unpack-objects --strict <odd.pack
155'
156
157test_expect_success 'transfer.fsckObjects handles odd pack (index)' '
158 rm -rf dst.git &&
159 git init --bare dst.git &&
160 test_must_fail git -C dst.git index-pack --strict --stdin <odd.pack
161'
162
163test_expect_success 'index-pack --strict works for non-repo pack' '
164 rm -rf dst.git &&
165 git init --bare dst.git &&
166 cp odd.pack dst.git &&
167 test_must_fail git -C dst.git index-pack --strict odd.pack 2>output &&
168 # Make sure we fail due to bad gitmodules content, not because we
169 # could not read the blob in the first place.
170 grep gitmodulesName output
171'
172
173check_dotx_symlink () {
174 fsck_must_fail=test_must_fail
175 fsck_prefix=error
176 refuse_index=t
177 case "$1" in
178 --warning)
179 fsck_must_fail=
180 fsck_prefix=warning
181 refuse_index=
182 shift
183 ;;
184 esac
185
186 name=$1
187 type=$2
188 path=$3
189 dir=symlink-$name-$type
190
191 test_expect_success "set up repo with symlinked $name ($type)" '
192 git init $dir &&
193 (
194 cd $dir &&
195
196 # Make the tree directly to avoid index restrictions.
197 #
198 # Because symlinks store the target as a blob, choose
199 # a pathname that could be parsed as a .gitmodules file
200 # to trick naive non-symlink-aware checking.
201 tricky="[foo]bar=true" &&
202 content=$(git hash-object -w ../.gitmodules) &&
203 target=$(printf "$tricky" | git hash-object -w --stdin) &&
204 {
205 printf "100644 blob $content\t$tricky\n" &&
206 printf "120000 blob $target\t$path\n"
207 } >bad-tree
208 ) &&
209 tree=$(git -C $dir mktree <$dir/bad-tree)
210 '
211
212 test_expect_success "fsck detects symlinked $name ($type)" '
213 (
214 cd $dir &&
215
216 # Check not only that we fail, but that it is due to the
217 # symlink detector
218 $fsck_must_fail git fsck 2>output &&
219 grep "$fsck_prefix.*tree $tree: ${name}Symlink" output
220 )
221 '
222
223 test -n "$refuse_index" &&
224 test_expect_success "refuse to load symlinked $name into index ($type)" '
225 test_must_fail \
226 git -C $dir \
227 -c core.protectntfs \
228 -c core.protecthfs \
229 read-tree $tree 2>err &&
230 grep "invalid path.*$name" err &&
231 git -C $dir ls-files -s >out &&
232 test_must_be_empty out
233 '
234}
235
236check_dotx_symlink gitmodules vanilla .gitmodules
237check_dotx_symlink gitmodules ntfs ".gitmodules ."
238check_dotx_symlink gitmodules hfs ".${u200c}gitmodules"
239
240check_dotx_symlink --warning gitattributes vanilla .gitattributes
241check_dotx_symlink --warning gitattributes ntfs ".gitattributes ."
242check_dotx_symlink --warning gitattributes hfs ".${u200c}gitattributes"
243
244check_dotx_symlink --warning gitignore vanilla .gitignore
245check_dotx_symlink --warning gitignore ntfs ".gitignore ."
246check_dotx_symlink --warning gitignore hfs ".${u200c}gitignore"
247
248check_dotx_symlink --warning mailmap vanilla .mailmap
249check_dotx_symlink --warning mailmap ntfs ".mailmap ."
250check_dotx_symlink --warning mailmap hfs ".${u200c}mailmap"
251
252test_expect_success 'fsck detects non-blob .gitmodules' '
253 git init non-blob &&
254 (
255 cd non-blob &&
256
257 # As above, make the funny tree directly to avoid index
258 # restrictions.
259 mkdir subdir &&
260 cp ../.gitmodules subdir/file &&
261 git add subdir/file &&
262 git commit -m ok &&
263 git ls-tree HEAD | sed s/subdir/.gitmodules/ | git mktree &&
264
265 test_must_fail git fsck 2>output &&
266 test_grep gitmodulesBlob output
267 )
268'
269
270test_expect_success 'fsck detects corrupt .gitmodules' '
271 git init corrupt &&
272 (
273 cd corrupt &&
274
275 echo "[broken" >.gitmodules &&
276 git add .gitmodules &&
277 git commit -m "broken gitmodules" &&
278
279 git fsck 2>output &&
280 test_grep gitmodulesParse output &&
281 test_grep ! "bad config" output
282 )
283'
284
285test_expect_success WINDOWS 'prevent git~1 squatting on Windows' '
286 git init squatting &&
287 (
288 cd squatting &&
289 mkdir a &&
290 touch a/..git &&
291 git add a/..git &&
292 test_tick &&
293 git commit -m initial &&
294
295 modules="$(test_write_lines \
296 "[submodule \"b.\"]" "url = ." "path = c" \
297 "[submodule \"b\"]" "url = ." "path = d\\\\a" |
298 git hash-object -w --stdin)" &&
299 rev="$(git rev-parse --verify HEAD)" &&
300 hash="$(echo x | git hash-object -w --stdin)" &&
301 test_must_fail git update-index --add \
302 --cacheinfo 160000,$rev,d\\a 2>err &&
303 test_grep "Invalid path" err &&
304 git -c core.protectNTFS=false update-index --add \
305 --cacheinfo 100644,$modules,.gitmodules \
306 --cacheinfo 160000,$rev,c \
307 --cacheinfo 160000,$rev,d\\a \
308 --cacheinfo 100644,$hash,d./a/x \
309 --cacheinfo 100644,$hash,d./a/..git &&
310 test_tick &&
311 git -c core.protectNTFS=false commit -m "module"
312 ) &&
313 if test_have_prereq MINGW
314 then
315 test_must_fail git -c core.protectNTFS=false \
316 clone --recurse-submodules squatting squatting-clone 2>err &&
317 test_grep -e "directory not empty" -e "not an empty directory" err &&
318 ! grep gitdir squatting-clone/d/a/git~2
319 fi
320'
321
322test_expect_success 'setup submodules with nested git dirs' '
323 git init nested &&
324 test_commit -C nested nested &&
325 (
326 cd nested &&
327 cat >.gitmodules <<-EOF &&
328 [submodule "hippo"]
329 url = .
330 path = thing1
331 [submodule "hippo/hooks"]
332 url = .
333 path = thing2
334 EOF
335 git clone . thing1 &&
336 git clone . thing2 &&
337 git add .gitmodules thing1 thing2 &&
338 test_tick &&
339 git commit -m nested
340 )
341'
342
343test_expect_success 'git dirs of sibling submodules must not be nested' '
344 test_must_fail git clone --recurse-submodules nested clone 2>err &&
345 test_grep "is inside git dir" err
346'
347
348test_expect_success 'submodule git dir nesting detection must work with parallel cloning' '
349 test_must_fail git clone --recurse-submodules --jobs=2 nested clone_parallel 2>err &&
350 cat err &&
351 grep -E "(already exists|is inside git dir|not a git repository)" err &&
352 {
353 test_path_is_missing .git/modules/hippo/HEAD ||
354 test_path_is_missing .git/modules/hippo/hooks/HEAD
355 }
356'
357
358test_expect_success 'checkout -f --recurse-submodules must not use a nested gitdir' '
359 git clone nested nested_checkout &&
360 (
361 cd nested_checkout &&
362 git submodule init &&
363 git submodule update thing1 &&
364 mkdir -p .git/modules/hippo/hooks/refs &&
365 mkdir -p .git/modules/hippo/hooks/objects/info &&
366 echo "../../../../objects" >.git/modules/hippo/hooks/objects/info/alternates &&
367 echo "ref: refs/heads/master" >.git/modules/hippo/hooks/HEAD
368 ) &&
369 test_must_fail git -C nested_checkout checkout -f --recurse-submodules HEAD 2>err &&
370 cat err &&
371 grep "is inside git dir" err &&
372 test_path_is_missing nested_checkout/thing2/.git
373'
374
375test_expect_success SYMLINKS,!WINDOWS,!MINGW 'submodule must not checkout into different directory' '
376 test_when_finished "rm -rf sub repo bad-clone" &&
377
378 git init sub &&
379 write_script sub/post-checkout <<-\EOF &&
380 touch "$PWD/foo"
381 EOF
382 git -C sub add post-checkout &&
383 git -C sub commit -m hook &&
384
385 git init repo &&
386 git -C repo -c protocol.file.allow=always submodule add "$PWD/sub" sub &&
387 git -C repo mv sub $(printf "sub\r") &&
388
389 # Ensure config values containing CR are wrapped in quotes.
390 git config unset -f repo/.gitmodules submodule.sub.path &&
391 printf "\tpath = \"sub\r\"\n" >>repo/.gitmodules &&
392
393 git config unset -f repo/.git/modules/sub/config core.worktree &&
394 {
395 printf "[core]\n" &&
396 printf "\tworktree = \"../../../sub\r\"\n"
397 } >>repo/.git/modules/sub/config &&
398
399 ln -s .git/modules/sub/hooks repo/sub &&
400 git -C repo add -A &&
401 git -C repo commit -m submodule &&
402
403 git -c protocol.file.allow=always clone --recurse-submodules repo bad-clone &&
404 ! test -f "$PWD/bad-clone/sub/foo" &&
405 test -f $(printf "bad-clone/sub\r/post-checkout")
406'
407
408test_done