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