Git fork
at reftables-rust 408 lines 11 kB view raw
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