Raw
1 #!/bin/sh
2
3 test_description='check broken or malicious patterns in .git* files
4
5 Such 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
19 test_expect_success 'setup' '
20 git config --global protocol.file.allow always
21 '
22
23 test_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
47 test_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
73 test_expect_success 'create innocent subrepo' '
74 git init innocent &&
75 git -C innocent commit --allow-empty -m foo
76 '
77
78 test_expect_success 'submodule add refuses invalid names' '
79 test_must_fail \
80 git submodule add --name ../../modules/evil "$PWD/innocent" evil
81 '
82
83 test_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.
104 test_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
110 test_expect_success 'clone evil superproject' '
111 git clone --recurse-submodules . victim >output 2>&1 &&
112 ! grep "RUNNING POST CHECKOUT" output
113 '
114
115 test_expect_success 'fsck detects evil superproject' '
116 test_must_fail git fsck
117 '
118
119 test_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
126 test_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.
137 test_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
151 test_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
157 test_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
163 test_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
173 check_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 if test -n "$refuse_index"
224 then
225 test_expect_success "refuse to load symlinked $name into index ($type)" '
226 test_must_fail \
227 git -C $dir \
228 -c core.protectntfs \
229 -c core.protecthfs \
230 read-tree $tree 2>err &&
231 grep "invalid path.*$name" err &&
232 git -C $dir ls-files -s >out &&
233 test_must_be_empty out
234 '
235 fi
236 }
237
238 check_dotx_symlink gitmodules vanilla .gitmodules
239 check_dotx_symlink gitmodules ntfs ".gitmodules ."
240 check_dotx_symlink gitmodules hfs ".${u200c}gitmodules"
241
242 check_dotx_symlink --warning gitattributes vanilla .gitattributes
243 check_dotx_symlink --warning gitattributes ntfs ".gitattributes ."
244 check_dotx_symlink --warning gitattributes hfs ".${u200c}gitattributes"
245
246 check_dotx_symlink --warning gitignore vanilla .gitignore
247 check_dotx_symlink --warning gitignore ntfs ".gitignore ."
248 check_dotx_symlink --warning gitignore hfs ".${u200c}gitignore"
249
250 check_dotx_symlink --warning mailmap vanilla .mailmap
251 check_dotx_symlink --warning mailmap ntfs ".mailmap ."
252 check_dotx_symlink --warning mailmap hfs ".${u200c}mailmap"
253
254 test_expect_success 'fsck detects non-blob .gitmodules' '
255 git init non-blob &&
256 (
257 cd non-blob &&
258
259 # As above, make the funny tree directly to avoid index
260 # restrictions.
261 mkdir subdir &&
262 cp ../.gitmodules subdir/file &&
263 git add subdir/file &&
264 git commit -m ok &&
265 git ls-tree HEAD | sed s/subdir/.gitmodules/ | git mktree &&
266
267 test_must_fail git fsck 2>output &&
268 test_grep gitmodulesBlob output
269 )
270 '
271
272 test_expect_success 'fsck detects corrupt .gitmodules' '
273 git init corrupt &&
274 (
275 cd corrupt &&
276
277 echo "[broken" >.gitmodules &&
278 git add .gitmodules &&
279 git commit -m "broken gitmodules" &&
280
281 git fsck 2>output &&
282 test_grep gitmodulesParse output &&
283 test_grep ! "bad config" output
284 )
285 '
286
287 test_expect_success WINDOWS 'prevent git~1 squatting on Windows' '
288 git init squatting &&
289 (
290 cd squatting &&
291 mkdir a &&
292 touch a/..git &&
293 git add a/..git &&
294 test_tick &&
295 git commit -m initial &&
296
297 modules="$(test_write_lines \
298 "[submodule \"b.\"]" "url = ." "path = c" \
299 "[submodule \"b\"]" "url = ." "path = d\\\\a" |
300 git hash-object -w --stdin)" &&
301 rev="$(git rev-parse --verify HEAD)" &&
302 hash="$(echo x | git hash-object -w --stdin)" &&
303 test_must_fail git update-index --add \
304 --cacheinfo 160000,$rev,d\\a 2>err &&
305 test_grep "Invalid path" err &&
306 git -c core.protectNTFS=false update-index --add \
307 --cacheinfo 100644,$modules,.gitmodules \
308 --cacheinfo 160000,$rev,c \
309 --cacheinfo 160000,$rev,d\\a \
310 --cacheinfo 100644,$hash,d./a/x \
311 --cacheinfo 100644,$hash,d./a/..git &&
312 test_tick &&
313 git -c core.protectNTFS=false commit -m "module"
314 ) &&
315 if test_have_prereq MINGW
316 then
317 test_must_fail git -c core.protectNTFS=false \
318 clone --recurse-submodules squatting squatting-clone 2>err &&
319 test_grep -e "directory not empty" -e "not an empty directory" err &&
320 ! grep gitdir squatting-clone/d/a/git~2
321 fi
322 '
323
324 test_expect_success 'setup submodules with nested git dirs' '
325 git init nested &&
326 test_commit -C nested nested &&
327 (
328 cd nested &&
329 cat >.gitmodules <<-EOF &&
330 [submodule "hippo"]
331 url = .
332 path = thing1
333 [submodule "hippo/hooks"]
334 url = .
335 path = thing2
336 EOF
337 git clone . thing1 &&
338 git clone . thing2 &&
339 git add .gitmodules thing1 thing2 &&
340 test_tick &&
341 git commit -m nested
342 )
343 '
344
345 test_expect_success 'git dirs of sibling submodules must not be nested' '
346 test_must_fail git clone --recurse-submodules nested clone 2>err &&
347 test_grep "is inside git dir" err
348 '
349
350 test_expect_success 'submodule git dir nesting detection must work with parallel cloning' '
351 test_must_fail git clone --recurse-submodules --jobs=2 nested clone_parallel 2>err &&
352 cat err &&
353 grep -E "(already exists|is inside git dir|not a git repository)" err &&
354 {
355 test_path_is_missing .git/modules/hippo/HEAD ||
356 test_path_is_missing .git/modules/hippo/hooks/HEAD
357 }
358 '
359
360 test_expect_success 'checkout -f --recurse-submodules must not use a nested gitdir' '
361 git clone nested nested_checkout &&
362 (
363 cd nested_checkout &&
364 git submodule init &&
365 git submodule update thing1 &&
366 mkdir -p .git/modules/hippo/hooks/refs &&
367 mkdir -p .git/modules/hippo/hooks/objects/info &&
368 echo "../../../../objects" >.git/modules/hippo/hooks/objects/info/alternates &&
369 echo "ref: refs/heads/master" >.git/modules/hippo/hooks/HEAD
370 ) &&
371 test_must_fail git -C nested_checkout checkout -f --recurse-submodules HEAD 2>err &&
372 cat err &&
373 grep "is inside git dir" err &&
374 test_path_is_missing nested_checkout/thing2/.git
375 '
376
377 test_expect_success SYMLINKS,!WINDOWS,!MINGW 'submodule must not checkout into different directory' '
378 test_when_finished "rm -rf sub repo bad-clone" &&
379
380 git init sub &&
381 write_script sub/post-checkout <<-\EOF &&
382 touch "$PWD/foo"
383 EOF
384 git -C sub add post-checkout &&
385 git -C sub commit -m hook &&
386
387 git init repo &&
388 git -C repo -c protocol.file.allow=always submodule add "$PWD/sub" sub &&
389 git -C repo mv sub $(printf "sub\r") &&
390
391 # Ensure config values containing CR are wrapped in quotes.
392 git config unset -f repo/.gitmodules submodule.sub.path &&
393 printf "\tpath = \"sub\r\"\n" >>repo/.gitmodules &&
394
395 git config unset -f repo/.git/modules/sub/config core.worktree &&
396 {
397 printf "[core]\n" &&
398 printf "\tworktree = \"../../../sub\r\"\n"
399 } >>repo/.git/modules/sub/config &&
400
401 ln -s .git/modules/sub/hooks repo/sub &&
402 git -C repo add -A &&
403 git -C repo commit -m submodule &&
404
405 git -c protocol.file.allow=always clone --recurse-submodules repo bad-clone &&
406 ! test -f "$PWD/bad-clone/sub/foo" &&
407 test -f $(printf "bad-clone/sub\r/post-checkout")
408 '
409
410 test_done