github.com/apcera/util@v0.0.0-20180322191801-7a50bc84ee48/tarhelper/untar_test.go (about) 1 // Copyright 2012-2016 Apcera Inc. All rights reserved. 2 3 package tarhelper 4 5 import ( 6 "archive/tar" 7 "bytes" 8 "io" 9 "io/ioutil" 10 "os" 11 "os/user" 12 "path" 13 "path/filepath" 14 "strconv" 15 "strings" 16 "syscall" 17 "testing" 18 "time" 19 20 tt "github.com/apcera/util/testtool" 21 ) 22 23 func TestUntarResolveDestinations(t *testing.T) { 24 testHelper := tt.StartTest(t) 25 defer testHelper.FinishTest() 26 27 u := new(Untar) 28 u.resolvedLinks = make([]resolvedLink, 0) 29 30 makeTestDir(t) 31 32 runTest := func(p, e string) { 33 dst, err := u.resolveDestination(p) 34 tt.TestExpectSuccess(t, err) 35 tt.TestEqual(t, dst, e) 36 } 37 38 runTest("a", "a") 39 runTest("a/b", "a/b") 40 runTest("a/b/c", "a/b/c") 41 runTest("a/b/c/d", "a/b/c/d") 42 runTest("a/b/c/d/e", "a/b/c/d/e") 43 runTest("a/b/c/f", "a/b/c/f") 44 runTest("a/b/c/l", "a/b/i") 45 runTest("a/b/c/l/j", "a/b/i/j") 46 runTest("a/b/c/l/j/k", "a/b/i/j/k") 47 runTest("a/b/c/l/j/l", "a/b/i/j/k") 48 runTest("a/b/c/l/j/m", "a/b/g") 49 runTest("a/b/g", "a/b/g") 50 runTest("a/b/h", "a/b/g") 51 runTest("a/b/i", "a/b/i") 52 runTest("a/b/i/j", "a/b/i/j") 53 runTest("a/b/i/j/k", "a/b/i/j/k") 54 runTest("a/b/i/j/l", "a/b/i/j/k") 55 runTest("a/b/i/j/m", "a/b/g") 56 57 // resolve an absolute path symlink relative to the root 58 u.AbsoluteRoot = "/" 59 runTest("a/b/bash", "/bin/bash") 60 61 // now resolve it relative to some other arbituary path 62 u.AbsoluteRoot = "/some/path/elsewhere" 63 runTest("a/b/bash", "/some/path/elsewhere/bin/bash") 64 } 65 66 func TestUntarExtractFollowingSymlinks(t *testing.T) { 67 testHelper := tt.StartTest(t) 68 defer testHelper.FinishTest() 69 70 // create a buffer and tar.Writer 71 buffer := bytes.NewBufferString("") 72 archive := tar.NewWriter(buffer) 73 74 writeDirectory := func(name string) { 75 header := new(tar.Header) 76 header.Name = name + "/" 77 header.Typeflag = tar.TypeDir 78 header.Mode = 0755 79 header.Mode |= c_ISDIR 80 header.ModTime = time.Now() 81 tt.TestExpectSuccess(t, archive.WriteHeader(header)) 82 } 83 84 writeFile := func(name, contents string) { 85 b := []byte(contents) 86 header := new(tar.Header) 87 header.Name = name 88 header.Typeflag = tar.TypeReg 89 header.Mode = 0644 90 header.Mode |= c_ISREG 91 header.ModTime = time.Now() 92 header.Size = int64(len(b)) 93 94 tt.TestExpectSuccess(t, archive.WriteHeader(header)) 95 _, err := archive.Write(b) 96 tt.TestExpectSuccess(t, err) 97 tt.TestExpectSuccess(t, archive.Flush()) 98 } 99 100 writeSymlink := func(name, link string) { 101 header := new(tar.Header) 102 header.Name = name 103 header.Linkname = link 104 header.Typeflag = tar.TypeSymlink 105 header.Mode = 0644 106 header.Mode |= c_ISLNK 107 header.ModTime = time.Now() 108 tt.TestExpectSuccess(t, archive.WriteHeader(header)) 109 } 110 111 // generate the mock tar 112 writeDirectory(".") 113 writeFile("./foo", "foo") 114 writeDirectory("./usr") 115 writeDirectory("./usr/bin") 116 writeFile("./usr/bin/bash", "bash") 117 writeSymlink("./usr/bin/sh", "bash") 118 119 // now write a symlink that is an absolute path and then a file in it 120 writeSymlink("./etc", "/realetc") 121 writeFile("./etc/zz", "zz") 122 archive.Close() 123 124 // create temp folder to extract to 125 tempDir := testHelper.TempDir() 126 extractionPath := path.Join(tempDir, "pkg") 127 err := os.MkdirAll(extractionPath, 0755) 128 tt.TestExpectSuccess(t, err) 129 err = os.MkdirAll(path.Join(tempDir, "realetc"), 0755) 130 tt.TestExpectSuccess(t, err) 131 132 // extract 133 r := bytes.NewReader(buffer.Bytes()) 134 u := NewUntar(r, extractionPath) 135 u.AbsoluteRoot = tempDir 136 tt.TestExpectSuccess(t, u.Extract()) 137 138 fileExists := func(name string) { 139 _, err := os.Stat(path.Join(tempDir, name)) 140 tt.TestExpectSuccess(t, err) 141 } 142 143 fileContents := func(name, contents string) { 144 b, err := ioutil.ReadFile(path.Join(tempDir, name)) 145 tt.TestExpectSuccess(t, err) 146 tt.TestEqual(t, string(b), contents) 147 } 148 149 fileSymlinks := func(name, link string) { 150 l, err := os.Readlink(path.Join(tempDir, name)) 151 tt.TestExpectSuccess(t, err) 152 tt.TestEqual(t, l, link) 153 } 154 155 fileExists("./pkg/foo") 156 fileContents("./pkg/foo", "foo") 157 fileExists("./pkg/usr") 158 fileExists("./pkg/usr/bin") 159 fileExists("./pkg/usr/bin/bash") 160 fileContents("./pkg/usr/bin/bash", "bash") 161 fileSymlinks("./pkg/usr/bin/sh", "bash") 162 163 // now validate the symlink and file in the symlinked dir that was outside 164 // the symlink should still be absolute to /realetc 165 // but the file should be in ./realetc/zz within the tempDir and not the 166 // system's root... so Untar follows how it knows it should resolve and not 167 // follow the real symlink 168 fileSymlinks("./pkg/etc", "/realetc") 169 fileExists("./realetc/zz") 170 fileContents("./realetc/zz", "zz") 171 } 172 173 func TestUntarCreatesDeeperPathsIfNotMentioned(t *testing.T) { 174 testHelper := tt.StartTest(t) 175 defer testHelper.FinishTest() 176 177 // create a buffer and tar.Writer 178 buffer := bytes.NewBufferString("") 179 archive := tar.NewWriter(buffer) 180 181 writeFile := func(name, contents string) { 182 b := []byte(contents) 183 header := new(tar.Header) 184 header.Name = name 185 header.Typeflag = tar.TypeReg 186 header.Mode = 0644 187 header.Mode |= c_ISREG 188 header.ModTime = time.Now() 189 header.Size = int64(len(b)) 190 191 tt.TestExpectSuccess(t, archive.WriteHeader(header)) 192 _, err := archive.Write(b) 193 tt.TestExpectSuccess(t, err) 194 tt.TestExpectSuccess(t, archive.Flush()) 195 } 196 197 // generate the mock tar... this will write to a file in a directory that 198 // isn't already created within the tar 199 writeFile("./a_directory/file", "foo") 200 archive.Close() 201 202 // create temp folder to extract to 203 tempDir := testHelper.TempDir() 204 extractionPath := path.Join(tempDir, "pkg") 205 err := os.MkdirAll(extractionPath, 0755) 206 tt.TestExpectSuccess(t, err) 207 208 // extract 209 r := bytes.NewReader(buffer.Bytes()) 210 u := NewUntar(r, extractionPath) 211 u.AbsoluteRoot = tempDir 212 tt.TestExpectSuccess(t, u.Extract()) 213 214 fileExists := func(name string) { 215 _, err := os.Stat(path.Join(tempDir, name)) 216 tt.TestExpectSuccess(t, err) 217 } 218 219 fileContents := func(name, contents string) { 220 b, err := ioutil.ReadFile(path.Join(tempDir, name)) 221 tt.TestExpectSuccess(t, err) 222 tt.TestEqual(t, string(b), contents) 223 } 224 225 fileExists("./pkg/a_directory/file") 226 fileContents("./pkg/a_directory/file", "foo") 227 } 228 229 func TestUntarExtractOverwriting(t *testing.T) { 230 testHelper := tt.StartTest(t) 231 defer testHelper.FinishTest() 232 233 // create a buffer and tar.Writer 234 buffer := bytes.NewBufferString("") 235 archive := tar.NewWriter(buffer) 236 237 writeDirectory := func(name string) { 238 header := new(tar.Header) 239 header.Name = name + "/" 240 header.Typeflag = tar.TypeDir 241 header.Mode = 0755 242 header.Mode |= c_ISDIR 243 header.ModTime = time.Now() 244 tt.TestExpectSuccess(t, archive.WriteHeader(header)) 245 } 246 247 writeFile := func(name, contents string) { 248 b := []byte(contents) 249 header := new(tar.Header) 250 header.Name = name 251 header.Typeflag = tar.TypeReg 252 header.Mode = 0644 253 header.Mode |= c_ISREG 254 header.ModTime = time.Now() 255 header.Size = int64(len(b)) 256 257 tt.TestExpectSuccess(t, archive.WriteHeader(header)) 258 _, err := archive.Write(b) 259 tt.TestExpectSuccess(t, err) 260 tt.TestExpectSuccess(t, archive.Flush()) 261 } 262 263 writeSymlink := func(name, link string) { 264 header := new(tar.Header) 265 header.Name = name 266 header.Linkname = link 267 header.Typeflag = tar.TypeSymlink 268 header.Mode = 0644 269 header.Mode |= c_ISLNK 270 header.ModTime = time.Now() 271 tt.TestExpectSuccess(t, archive.WriteHeader(header)) 272 } 273 274 // create temp folder to extract to 275 tempDir := testHelper.TempDir() 276 277 fileExists := func(name string) { 278 _, err := os.Stat(path.Join(tempDir, name)) 279 tt.TestExpectSuccess(t, err) 280 } 281 282 fileContents := func(name, contents string) { 283 b, err := ioutil.ReadFile(path.Join(tempDir, name)) 284 tt.TestExpectSuccess(t, err) 285 tt.TestEqual(t, string(b), contents) 286 } 287 288 fileSymlinks := func(name, link string) { 289 l, err := os.Readlink(path.Join(tempDir, name)) 290 tt.TestExpectSuccess(t, err) 291 tt.TestEqual(t, l, link) 292 } 293 294 // generate the mock tar 295 writeDirectory(".") 296 writeFile("./foo", "foo") 297 writeDirectory("./usr") 298 writeDirectory("./usr/bin") 299 writeFile("./usr/bin/bash", "bash") 300 writeSymlink("./usr/bin/sh", "bash") 301 writeDirectory("./etc") 302 writeFile("./etc/awesome", "awesome") 303 writeFile("./var", "vvv") 304 archive.Close() 305 306 // extract 307 r := bytes.NewReader(buffer.Bytes()) 308 u := NewUntar(r, tempDir) 309 tt.TestExpectSuccess(t, u.Extract()) 310 311 // validate the first tar 312 fileExists("./foo") 313 fileContents("./foo", "foo") 314 fileExists("./usr") 315 fileExists("./usr/bin") 316 fileExists("./usr/bin/bash") 317 fileContents("./usr/bin/bash", "bash") 318 fileSymlinks("./usr/bin/sh", "bash") 319 fileExists("./etc/awesome") 320 fileContents("./etc/awesome", "awesome") 321 fileExists("./var") 322 fileContents("./var", "vvv") 323 324 // create another tar and then extract it 325 buffer2 := bytes.NewBufferString("") 326 archive = tar.NewWriter(buffer2) 327 328 // write the 2nd tar 329 writeDirectory(".") 330 writeFile("./foo", "bar") 331 writeDirectory("./usr") 332 writeDirectory("./usr/bin") 333 writeFile("./usr/bin/zsh", "zsh") 334 writeSymlink("./usr/bin/sh", "zsh") 335 writeFile("./etc", "etc") // replace the directory with a file 336 writeDirectory("./var") // replace the file with a directory 337 writeFile("./var/lib", "lll") 338 archive.Close() 339 340 // extract the 2nd tar 341 r = bytes.NewReader(buffer2.Bytes()) 342 u = NewUntar(r, tempDir) 343 tt.TestExpectSuccess(t, u.Extract()) 344 345 // verify the contents were overwritten as expected 346 fileContents("./foo", "bar") 347 fileContents("./usr/bin/zsh", "zsh") 348 fileSymlinks("./usr/bin/sh", "zsh") 349 fileContents("./etc", "etc") 350 fileContents("./var/lib", "lll") 351 } 352 353 func TestUntarIDMappings(t *testing.T) { 354 testHelper := tt.StartTest(t) 355 defer testHelper.FinishTest() 356 357 // create a buffer and tar.Writer 358 buffer := bytes.NewBufferString("") 359 archive := tar.NewWriter(buffer) 360 361 writeDirectoryWithOwners := func(name string, uid, gid int) { 362 header := new(tar.Header) 363 header.Name = name + "/" 364 header.Typeflag = tar.TypeDir 365 header.Mode = 0755 366 header.Mode |= c_ISDIR 367 header.ModTime = time.Now() 368 header.Uid = uid 369 header.Gid = gid 370 tt.TestExpectSuccess(t, archive.WriteHeader(header)) 371 } 372 373 writeFileWithOwners := func(name, contents string, uid, gid int) { 374 b := []byte(contents) 375 header := new(tar.Header) 376 header.Name = name 377 header.Typeflag = tar.TypeReg 378 header.Mode = 0644 379 header.Mode |= c_ISREG 380 header.ModTime = time.Now() 381 header.Size = int64(len(b)) 382 header.Uid = uid 383 header.Gid = gid 384 385 tt.TestExpectSuccess(t, archive.WriteHeader(header)) 386 _, err := archive.Write(b) 387 tt.TestExpectSuccess(t, err) 388 tt.TestExpectSuccess(t, archive.Flush()) 389 } 390 391 writeDirectoryWithOwners(".", 0, 0) 392 writeFileWithOwners("./foo", "foo", 0, 0) 393 archive.Close() 394 395 // setup our mapping func 396 usr, err := user.Current() 397 tt.TestExpectSuccess(t, err) 398 myUid, err := strconv.Atoi(usr.Uid) 399 tt.TestExpectSuccess(t, err) 400 myGid, err := strconv.Atoi(usr.Gid) 401 tt.TestExpectSuccess(t, err) 402 uidFuncCalled := false 403 gidFuncCalled := false 404 uidMappingFunc := func(uid int) (int, error) { 405 uidFuncCalled = true 406 tt.TestEqual(t, uid, 0) 407 return myUid, nil 408 } 409 gidMappingFunc := func(gid int) (int, error) { 410 gidFuncCalled = true 411 tt.TestEqual(t, gid, 0) 412 return myGid, nil 413 } 414 415 // extract 416 tempDir := testHelper.TempDir() 417 r := bytes.NewReader(buffer.Bytes()) 418 u := NewUntar(r, tempDir) 419 u.PreserveOwners = true 420 u.OwnerMappingFunc = uidMappingFunc 421 u.GroupMappingFunc = gidMappingFunc 422 tt.TestExpectSuccess(t, u.Extract()) 423 424 // verify it was called 425 tt.TestEqual(t, uidFuncCalled, true) 426 tt.TestEqual(t, gidFuncCalled, true) 427 428 // verify the file 429 stat, err := os.Stat(path.Join(tempDir, "foo")) 430 tt.TestExpectSuccess(t, err) 431 sys := stat.Sys().(*syscall.Stat_t) 432 tt.TestEqual(t, sys.Uid, uint32(myUid)) 433 tt.TestEqual(t, sys.Gid, uint32(myGid)) 434 } 435 436 func TestUntarFailures(t *testing.T) { 437 testHelper := tt.StartTest(t) 438 defer testHelper.FinishTest() 439 440 // Bad compression type. 441 u := NewUntar(strings.NewReader("bad"), "/tmp") 442 u.Compression = Compression(-1) 443 tt.TestExpectError(t, u.Extract()) 444 445 // FIXME(brady): add more cases here! 446 } 447 448 func TestCannotDetectCompression(t *testing.T) { 449 testHelper := tt.StartTest(t) 450 defer testHelper.FinishTest() 451 452 u := NewUntar(strings.NewReader("bad"), "/tmp") 453 u.Compression = DETECT 454 455 tt.TestExpectError(t, u.Extract()) 456 } 457 458 func TestUntarWhitelist(t *testing.T) { 459 testHelper := tt.StartTest(t) 460 defer testHelper.FinishTest() 461 462 // create a buffer and tar.Writer 463 buffer := bytes.NewBufferString("") 464 archive := tar.NewWriter(buffer) 465 466 writeDirectory := func(name string) { 467 header := new(tar.Header) 468 header.Name = name + "/" 469 header.Typeflag = tar.TypeDir 470 header.Mode = 0755 471 header.Mode |= c_ISDIR 472 header.ModTime = time.Now() 473 tt.TestExpectSuccess(t, archive.WriteHeader(header)) 474 } 475 476 writeFile := func(name, contents string) { 477 b := []byte(contents) 478 header := new(tar.Header) 479 header.Name = name 480 header.Typeflag = tar.TypeReg 481 header.Mode = 0644 482 header.Mode |= c_ISREG 483 header.ModTime = time.Now() 484 header.Size = int64(len(b)) 485 486 tt.TestExpectSuccess(t, archive.WriteHeader(header)) 487 _, err := archive.Write(b) 488 tt.TestExpectSuccess(t, err) 489 tt.TestExpectSuccess(t, archive.Flush()) 490 } 491 492 writeDirectory(".") 493 writeFile("./foo", "foo") 494 writeFile("./foobar", "foobar") 495 writeFile("./doesntexist", "foo") 496 writeDirectory("./usr") 497 writeDirectory("./usr/bin") 498 writeFile("./usr/bin/bash", "bash") 499 writeDirectory("./usr/bin/other") 500 writeFile("./usr/bin/other/sh", "sh") 501 writeDirectory("./usr/nope") 502 writeFile("./usr/nope/not", "notthere") 503 writeDirectory("./usr/justdir") 504 writeFile("./usr/justdir/not", "notthere") 505 writeDirectory("./etc") 506 writeFile("./etc/not", "notthere") 507 508 archive.Close() 509 510 // create temp folder to extract to 511 tempDir := testHelper.TempDir() 512 513 // extract 514 r := bytes.NewReader(buffer.Bytes()) 515 u := NewUntar(r, tempDir) 516 u.PathWhitelist = []string{ 517 "/foo", 518 "/usr/bin/", 519 "/usr/justdir", 520 } 521 tt.TestExpectSuccess(t, u.Extract()) 522 523 fileExists := func(name string) { 524 _, err := os.Stat(path.Join(tempDir, name)) 525 tt.TestExpectSuccess(t, err) 526 } 527 528 fileNotExists := func(name string) { 529 _, err := os.Stat(path.Join(tempDir, name)) 530 tt.TestExpectError(t, err) 531 } 532 533 fileExists("/foo") 534 fileExists("/usr/bin/bash") 535 fileExists("/usr/bin/other/sh") 536 fileExists("/usr/justdir") 537 fileNotExists("/foobar") 538 fileNotExists("/doesntexist") 539 fileNotExists("/usr/nope/not") 540 fileNotExists("/usr/justdir/not") 541 fileNotExists("/etc/not") 542 } 543 544 func TestUntarCustomHandler(t *testing.T) { 545 testHelper := tt.StartTest(t) 546 defer testHelper.FinishTest() 547 548 // create a buffer and tar.Writer 549 buffer := bytes.NewBufferString("") 550 archive := tar.NewWriter(buffer) 551 552 writeDirectory := func(name string) { 553 header := new(tar.Header) 554 header.Name = name + "/" 555 header.Typeflag = tar.TypeDir 556 header.Mode = 0755 557 header.Mode |= c_ISDIR 558 header.ModTime = time.Now() 559 tt.TestExpectSuccess(t, archive.WriteHeader(header)) 560 } 561 562 writeFile := func(name, contents string) { 563 b := []byte(contents) 564 header := new(tar.Header) 565 header.Name = name 566 header.Typeflag = tar.TypeReg 567 header.Mode = 0644 568 header.Mode |= c_ISREG 569 header.ModTime = time.Now() 570 header.Size = int64(len(b)) 571 572 tt.TestExpectSuccess(t, archive.WriteHeader(header)) 573 _, err := archive.Write(b) 574 tt.TestExpectSuccess(t, err) 575 tt.TestExpectSuccess(t, archive.Flush()) 576 } 577 578 writeDirectory(".") 579 writeFile("./foo", "foo") 580 writeFile("./foobar", "foobar") 581 582 archive.Close() 583 584 // create temp folder to extract to 585 tempDir := testHelper.TempDir() 586 587 // extract 588 r := bytes.NewReader(buffer.Bytes()) 589 u := NewUntar(r, tempDir) 590 u.CustomHandlers = []UntarCustomHandler{ 591 func(rootpath string, header *tar.Header, reader io.Reader) (bool, error) { 592 if filepath.Clean(header.Name) != "foobar" { 593 return false, nil 594 } 595 596 f, err := os.Create(filepath.Join(rootpath, "foobar2")) 597 if err != nil { 598 return false, err 599 } 600 defer f.Close() 601 if _, err := io.Copy(f, reader); err != nil { 602 return false, err 603 } 604 return true, nil 605 }, 606 } 607 tt.TestExpectSuccess(t, u.Extract()) 608 609 fileExists := func(name string) { 610 _, err := os.Stat(path.Join(tempDir, name)) 611 tt.TestExpectSuccess(t, err) 612 } 613 614 fileNotExists := func(name string) { 615 _, err := os.Stat(path.Join(tempDir, name)) 616 tt.TestExpectError(t, err) 617 } 618 619 fileExists("/foo") 620 fileNotExists("/foobar") 621 fileExists("/foobar2") 622 }