github.com/dctrud/umoci@v0.4.3-0.20191016193643-05a1d37de015/oci/layer/tar_extract_test.go (about)

     1  /*
     2   * umoci: Umoci Modifies Open Containers' Images
     3   * Copyright (C) 2016, 2017, 2018 SUSE LLC.
     4   *
     5   * Licensed under the Apache License, Version 2.0 (the "License");
     6   * you may not use this file except in compliance with the License.
     7   * You may obtain a copy of the License at
     8   *
     9   *    http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   */
    17  
    18  package layer
    19  
    20  import (
    21  	"archive/tar"
    22  	"bytes"
    23  	"crypto/rand"
    24  	"io"
    25  	"io/ioutil"
    26  	"os"
    27  	"path/filepath"
    28  	"strings"
    29  	"testing"
    30  	"time"
    31  
    32  	rspec "github.com/opencontainers/runtime-spec/specs-go"
    33  	"github.com/pkg/errors"
    34  	"golang.org/x/sys/unix"
    35  )
    36  
    37  // TODO: Test the parent directory metadata is kept the same when unpacking.
    38  // TODO: Add tests for metadata and consistency.
    39  
    40  // testUnpackEntrySanitiseHelper is a basic helper to check that a tar header
    41  // with the given prefix will resolve to the same path without it during
    42  // unpacking. The "unsafe" version should resolve to the parent directory
    43  // (which will be checked). The rootfs is assumed to be <dir>/rootfs.
    44  func testUnpackEntrySanitiseHelper(t *testing.T, dir, file, prefix string) func(t *testing.T) {
    45  	// We return a function so that we can pass it directly to t.Run(...).
    46  	return func(t *testing.T) {
    47  		hostValue := []byte("host content")
    48  		ctrValue := []byte("container content")
    49  
    50  		rootfs := filepath.Join(dir, "rootfs")
    51  
    52  		// Create a host file that we want to make sure doesn't get overwrittern.
    53  		if err := ioutil.WriteFile(filepath.Join(dir, "file"), hostValue, 0644); err != nil {
    54  			t.Fatal(err)
    55  		}
    56  
    57  		// Create our header. We raw prepend the prefix because we are generating
    58  		// invalid tar headers.
    59  		hdr := &tar.Header{
    60  			Name:       prefix + "/" + filepath.Base(file),
    61  			Uid:        os.Getuid(),
    62  			Gid:        os.Getgid(),
    63  			Mode:       0644,
    64  			Size:       int64(len(ctrValue)),
    65  			Typeflag:   tar.TypeReg,
    66  			ModTime:    time.Now(),
    67  			AccessTime: time.Now(),
    68  			ChangeTime: time.Now(),
    69  		}
    70  
    71  		te := NewTarExtractor(MapOptions{})
    72  		if err := te.UnpackEntry(rootfs, hdr, bytes.NewBuffer(ctrValue)); err != nil {
    73  			t.Fatalf("unexpected UnpackEntry error: %s", err)
    74  		}
    75  
    76  		hostValueGot, err := ioutil.ReadFile(filepath.Join(dir, "file"))
    77  		if err != nil {
    78  			t.Fatalf("unexpected readfile error on host: %s", err)
    79  		}
    80  
    81  		ctrValueGot, err := ioutil.ReadFile(filepath.Join(rootfs, "file"))
    82  		if err != nil {
    83  			t.Fatalf("unexpected readfile error in ctr: %s", err)
    84  		}
    85  
    86  		if !bytes.Equal(ctrValue, ctrValueGot) {
    87  			t.Errorf("ctr path was not updated: expected='%s' got='%s'", string(ctrValue), string(ctrValueGot))
    88  		}
    89  		if !bytes.Equal(hostValue, hostValueGot) {
    90  			t.Errorf("HOST PATH WAS CHANGED! THIS IS A PATH ESCAPE! expected='%s' got='%s'", string(hostValue), string(hostValueGot))
    91  		}
    92  	}
    93  }
    94  
    95  // TestUnpackEntrySanitiseScoping makes sure that path sanitisation is done
    96  // safely with regards to /../../ prefixes in invalid tar archives.
    97  func TestUnpackEntrySanitiseScoping(t *testing.T) {
    98  	// TODO: Modify this to use subtests once Go 1.7 is in enough places.
    99  	func(t *testing.T) {
   100  		for _, test := range []struct {
   101  			name   string
   102  			prefix string
   103  		}{
   104  			{"GarbagePrefix", "/.."},
   105  			{"DotDotPrefix", ".."},
   106  		} {
   107  			dir, err := ioutil.TempDir("", "umoci-TestUnpackEntrySanitiseScoping")
   108  			if err != nil {
   109  				t.Fatal(err)
   110  			}
   111  			defer os.RemoveAll(dir)
   112  
   113  			rootfs := filepath.Join(dir, "rootfs")
   114  			if err := os.Mkdir(rootfs, 0755); err != nil {
   115  				t.Fatal(err)
   116  			}
   117  
   118  			t.Logf("running Test%s", test.name)
   119  			testUnpackEntrySanitiseHelper(t, dir, filepath.Join("/", test.prefix, "file"), test.prefix)(t)
   120  		}
   121  	}(t)
   122  }
   123  
   124  // TestUnpackEntrySymlinkScoping makes sure that path sanitisation is done
   125  // safely with regards to symlinks path components set to /.. and similar
   126  // prefixes in invalid tar archives (a regular tar archive won't contain stuff
   127  // like that).
   128  func TestUnpackEntrySymlinkScoping(t *testing.T) {
   129  	// TODO: Modify this to use subtests once Go 1.7 is in enough places.
   130  	func(t *testing.T) {
   131  		for _, test := range []struct {
   132  			name   string
   133  			prefix string
   134  		}{
   135  			{"RootPrefix", "/"},
   136  			{"GarbagePrefix1", "/../"},
   137  			{"GarbagePrefix2", "/../../../../../../../../../../../../../../../"},
   138  			{"GarbagePrefix3", "/./.././.././.././.././.././.././.././.././../"},
   139  			{"DotDotPrefix", ".."},
   140  		} {
   141  			dir, err := ioutil.TempDir("", "umoci-TestUnpackEntrySymlinkScoping")
   142  			if err != nil {
   143  				t.Fatal(err)
   144  			}
   145  			defer os.RemoveAll(dir)
   146  
   147  			rootfs := filepath.Join(dir, "rootfs")
   148  			if err := os.Mkdir(rootfs, 0755); err != nil {
   149  				t.Fatal(err)
   150  			}
   151  
   152  			// Create the symlink.
   153  			if err := os.Symlink(test.prefix, filepath.Join(rootfs, "link")); err != nil {
   154  				t.Fatal(err)
   155  			}
   156  
   157  			t.Logf("running Test%s", test.name)
   158  			testUnpackEntrySanitiseHelper(t, dir, filepath.Join("/", test.prefix, "file"), "link")(t)
   159  		}
   160  	}(t)
   161  }
   162  
   163  // TestUnpackEntryParentDir ensures that when UnpackEntry hits a path that
   164  // doesn't have its leading directories, we create all of the parent
   165  // directories.
   166  func TestUnpackEntryParentDir(t *testing.T) {
   167  	dir, err := ioutil.TempDir("", "umoci-TestUnpackEntryParentDir")
   168  	if err != nil {
   169  		t.Fatal(err)
   170  	}
   171  	defer os.RemoveAll(dir)
   172  
   173  	rootfs := filepath.Join(dir, "rootfs")
   174  	if err := os.Mkdir(rootfs, 0755); err != nil {
   175  		t.Fatal(err)
   176  	}
   177  
   178  	ctrValue := []byte("creating parentdirs")
   179  
   180  	// Create our header. We raw prepend the prefix because we are generating
   181  	// invalid tar headers.
   182  	hdr := &tar.Header{
   183  		Name:       "a/b/c/file",
   184  		Uid:        os.Getuid(),
   185  		Gid:        os.Getgid(),
   186  		Mode:       0644,
   187  		Size:       int64(len(ctrValue)),
   188  		Typeflag:   tar.TypeReg,
   189  		ModTime:    time.Now(),
   190  		AccessTime: time.Now(),
   191  		ChangeTime: time.Now(),
   192  	}
   193  
   194  	te := NewTarExtractor(MapOptions{})
   195  	if err := te.UnpackEntry(rootfs, hdr, bytes.NewBuffer(ctrValue)); err != nil {
   196  		t.Fatalf("unexpected UnpackEntry error: %s", err)
   197  	}
   198  
   199  	ctrValueGot, err := ioutil.ReadFile(filepath.Join(rootfs, "a/b/c/file"))
   200  	if err != nil {
   201  		t.Fatalf("unexpected readfile error: %s", err)
   202  	}
   203  
   204  	if !bytes.Equal(ctrValue, ctrValueGot) {
   205  		t.Errorf("ctr path was not updated: expected='%s' got='%s'", string(ctrValue), string(ctrValueGot))
   206  	}
   207  }
   208  
   209  // TestUnpackEntryWhiteout checks whether whiteout handling is done correctly,
   210  // as well as ensuring that the metadata of the parent is maintained.
   211  func TestUnpackEntryWhiteout(t *testing.T) {
   212  	// TODO: Modify this to use subtests once Go 1.7 is in enough places.
   213  	func(t *testing.T) {
   214  		for _, test := range []struct {
   215  			name string
   216  			path string
   217  			dir  bool // TODO: Switch to Typeflag
   218  		}{
   219  			{"FileInRoot", "rootpath", false},
   220  			{"HiddenFileInRoot", ".hiddenroot", false},
   221  			{"FileInSubdir", "some/path/file", false},
   222  			{"HiddenFileInSubdir", "another/path/.hiddenfile", false},
   223  			{"DirInRoot", "rootpath", true},
   224  			{"HiddenDirInRoot", ".hiddenroot", true},
   225  			{"DirInSubdir", "some/path/dir", true},
   226  			{"HiddenDirInSubdir", "another/path/.hiddendir", true},
   227  		} {
   228  			t.Logf("running Test%s", test.name)
   229  			testMtime := time.Unix(123, 456)
   230  			testAtime := time.Unix(789, 111)
   231  
   232  			dir, err := ioutil.TempDir("", "umoci-TestUnpackEntryWhiteout")
   233  			if err != nil {
   234  				t.Fatal(err)
   235  			}
   236  			defer os.RemoveAll(dir)
   237  
   238  			rawDir, rawFile := filepath.Split(test.path)
   239  			wh := filepath.Join(rawDir, whPrefix+rawFile)
   240  
   241  			// Create the parent directory.
   242  			if err := os.MkdirAll(filepath.Join(dir, rawDir), 0755); err != nil {
   243  				t.Fatal(err)
   244  			}
   245  
   246  			// Create the path itself.
   247  			if test.dir {
   248  				if err := os.Mkdir(filepath.Join(dir, test.path), 0755); err != nil {
   249  					t.Fatal(err)
   250  				}
   251  				// Make some subfiles and directories.
   252  				if err := ioutil.WriteFile(filepath.Join(dir, test.path, "file1"), []byte("some value"), 0644); err != nil {
   253  					t.Fatal(err)
   254  				}
   255  				if err := ioutil.WriteFile(filepath.Join(dir, test.path, "file2"), []byte("some value"), 0644); err != nil {
   256  					t.Fatal(err)
   257  				}
   258  				if err := os.Mkdir(filepath.Join(dir, test.path, ".subdir"), 0755); err != nil {
   259  					t.Fatal(err)
   260  				}
   261  				if err := ioutil.WriteFile(filepath.Join(dir, test.path, ".subdir", "file3"), []byte("some value"), 0644); err != nil {
   262  					t.Fatal(err)
   263  				}
   264  			} else {
   265  				if err := ioutil.WriteFile(filepath.Join(dir, test.path), []byte("some value"), 0644); err != nil {
   266  					t.Fatal(err)
   267  				}
   268  			}
   269  
   270  			// Set the modified time of the directory itself.
   271  			if err := os.Chtimes(filepath.Join(dir, rawDir), testAtime, testMtime); err != nil {
   272  				t.Fatal(err)
   273  			}
   274  
   275  			// Whiteout the path.
   276  			hdr := &tar.Header{
   277  				Name:     wh,
   278  				Typeflag: tar.TypeReg,
   279  			}
   280  
   281  			te := NewTarExtractor(MapOptions{})
   282  			if err := te.UnpackEntry(dir, hdr, nil); err != nil {
   283  				t.Fatalf("unexpected error in UnpackEntry: %s", err)
   284  			}
   285  
   286  			// Make sure that the path is gone.
   287  			if _, err := os.Lstat(filepath.Join(dir, test.path)); !os.IsNotExist(err) {
   288  				if err != nil {
   289  					t.Fatalf("unexpected error checking whiteout out path: %s", err)
   290  				}
   291  				t.Errorf("path was not removed by whiteout: %s", test.path)
   292  			}
   293  
   294  			// Make sure the parent directory wasn't modified.
   295  			if fi, err := os.Lstat(filepath.Join(dir, rawDir)); err != nil {
   296  				t.Fatalf("error checking parent directory of whiteout: %s", err)
   297  			} else {
   298  				hdr, err := tar.FileInfoHeader(fi, "")
   299  				if err != nil {
   300  					t.Fatalf("error generating header from fileinfo of parent directory of whiteout: %s", err)
   301  				}
   302  
   303  				if !hdr.ModTime.Equal(testMtime) {
   304  					t.Errorf("mtime of parent directory changed after whiteout: got='%s' expected='%s'", hdr.ModTime, testMtime)
   305  				}
   306  				if !hdr.AccessTime.Equal(testAtime) {
   307  					t.Errorf("atime of parent directory changed after whiteout: got='%s' expected='%s'", hdr.ModTime, testAtime)
   308  				}
   309  			}
   310  		}
   311  	}(t)
   312  }
   313  
   314  // TestUnpackOpaqueWhiteout checks whether *opaque* whiteout handling is done
   315  // correctly, as well as ensuring that the metadata of the parent is
   316  // maintained -- and that upperdir entries are handled.
   317  func TestUnpackOpaqueWhiteout(t *testing.T) {
   318  	type pseudoHdr struct {
   319  		path     string
   320  		linkname string
   321  		typeflag byte
   322  		upper    bool
   323  	}
   324  
   325  	fromPseudoHdr := func(ph pseudoHdr) (*tar.Header, io.Reader) {
   326  		var r io.Reader
   327  		var size int64
   328  		if ph.typeflag == tar.TypeReg || ph.typeflag == tar.TypeRegA {
   329  			size = 256 * 1024
   330  			r = &io.LimitedReader{
   331  				R: rand.Reader,
   332  				N: size,
   333  			}
   334  		}
   335  
   336  		mode := os.FileMode(0777)
   337  		if ph.typeflag == tar.TypeDir {
   338  			mode |= os.ModeDir
   339  		}
   340  
   341  		return &tar.Header{
   342  			Name:       ph.path,
   343  			Linkname:   ph.linkname,
   344  			Typeflag:   ph.typeflag,
   345  			Mode:       int64(mode),
   346  			Size:       size,
   347  			ModTime:    time.Unix(1210393, 4528036),
   348  			AccessTime: time.Unix(7892829, 2341211),
   349  			ChangeTime: time.Unix(8731293, 8218947),
   350  		}, r
   351  	}
   352  
   353  	// TODO: Modify this to use subtests once Go 1.7 is in enough places.
   354  	func(t *testing.T) {
   355  		for _, test := range []struct {
   356  			name          string
   357  			ignoreExist   bool // ignore if extra upper files exist
   358  			pseudoHeaders []pseudoHdr
   359  		}{
   360  			{"EmptyDir", false, nil},
   361  			{"OneLevel", false, []pseudoHdr{
   362  				{"file", "", tar.TypeReg, false},
   363  				{"link", "..", tar.TypeSymlink, true},
   364  				{"badlink", "./nothing", tar.TypeSymlink, true},
   365  				{"fifo", "", tar.TypeFifo, false},
   366  			}},
   367  			{"OneLevelNoUpper", false, []pseudoHdr{
   368  				{"file", "", tar.TypeReg, false},
   369  				{"link", "..", tar.TypeSymlink, false},
   370  				{"badlink", "./nothing", tar.TypeSymlink, false},
   371  				{"fifo", "", tar.TypeFifo, false},
   372  			}},
   373  			{"TwoLevel", false, []pseudoHdr{
   374  				{"file", "", tar.TypeReg, true},
   375  				{"link", "..", tar.TypeSymlink, false},
   376  				{"badlink", "./nothing", tar.TypeSymlink, false},
   377  				{"dir", "", tar.TypeDir, true},
   378  				{"dir/file", "", tar.TypeRegA, true},
   379  				{"dir/link", "../badlink", tar.TypeSymlink, false},
   380  				{"dir/verybadlink", "../../../../../../../../../../../../etc/shadow", tar.TypeSymlink, true},
   381  				{"dir/verybadlink2", "/../../../../../../../../../../../../etc/shadow", tar.TypeSymlink, false},
   382  			}},
   383  			{"TwoLevelNoUpper", false, []pseudoHdr{
   384  				{"file", "", tar.TypeReg, false},
   385  				{"link", "..", tar.TypeSymlink, false},
   386  				{"badlink", "./nothing", tar.TypeSymlink, false},
   387  				{"dir", "", tar.TypeDir, false},
   388  				{"dir/file", "", tar.TypeRegA, false},
   389  				{"dir/link", "../badlink", tar.TypeSymlink, false},
   390  				{"dir/verybadlink", "../../../../../../../../../../../../etc/shadow", tar.TypeSymlink, false},
   391  				{"dir/verybadlink2", "/../../../../../../../../../../../../etc/shadow", tar.TypeSymlink, false},
   392  			}},
   393  			{"MultiLevel", false, []pseudoHdr{
   394  				{"level1_file", "", tar.TypeReg, true},
   395  				{"level1_link", "..", tar.TypeSymlink, false},
   396  				{"level1a", "", tar.TypeDir, true},
   397  				{"level1a/level2_file", "", tar.TypeRegA, false},
   398  				{"level1a/level2_link", "../../../", tar.TypeSymlink, true},
   399  				{"level1a/level2a", "", tar.TypeDir, false},
   400  				{"level1a/level2a/level3_fileA", "", tar.TypeReg, false},
   401  				{"level1a/level2a/level3_fileB", "", tar.TypeReg, false},
   402  				{"level1a/level2b", "", tar.TypeDir, true},
   403  				{"level1a/level2b/level3_fileA", "", tar.TypeReg, true},
   404  				{"level1a/level2b/level3_fileB", "", tar.TypeReg, false},
   405  				{"level1a/level2b/level3", "", tar.TypeDir, false},
   406  				{"level1a/level2b/level3/level4", "", tar.TypeDir, false},
   407  				{"level1a/level2b/level3/level4", "", tar.TypeDir, false},
   408  				{"level1a/level2b/level3_fileA", "", tar.TypeReg, true},
   409  				{"level1b", "", tar.TypeDir, false},
   410  				{"level1b/level2_fileA", "", tar.TypeReg, false},
   411  				{"level1b/level2_fileB", "", tar.TypeReg, false},
   412  				{"level1b/level2", "", tar.TypeDir, false},
   413  				{"level1b/level2/level3_file", "", tar.TypeReg, false},
   414  			}},
   415  			{"MultiLevelNoUpper", false, []pseudoHdr{
   416  				{"level1_file", "", tar.TypeReg, false},
   417  				{"level1_link", "..", tar.TypeSymlink, false},
   418  				{"level1a", "", tar.TypeDir, false},
   419  				{"level1a/level2_file", "", tar.TypeRegA, false},
   420  				{"level1a/level2_link", "../../../", tar.TypeSymlink, false},
   421  				{"level1a/level2a", "", tar.TypeDir, false},
   422  				{"level1a/level2a/level3_fileA", "", tar.TypeReg, false},
   423  				{"level1a/level2a/level3_fileB", "", tar.TypeReg, false},
   424  				{"level1a/level2b", "", tar.TypeDir, false},
   425  				{"level1a/level2b/level3_fileA", "", tar.TypeReg, false},
   426  				{"level1a/level2b/level3_fileB", "", tar.TypeReg, false},
   427  				{"level1a/level2b/level3", "", tar.TypeDir, false},
   428  				{"level1a/level2b/level3/level4", "", tar.TypeDir, false},
   429  				{"level1a/level2b/level3/level4", "", tar.TypeDir, false},
   430  				{"level1a/level2b/level3_fileA", "", tar.TypeReg, false},
   431  				{"level1b", "", tar.TypeDir, false},
   432  				{"level1b/level2_fileA", "", tar.TypeReg, false},
   433  				{"level1b/level2_fileB", "", tar.TypeReg, false},
   434  				{"level1b/level2", "", tar.TypeDir, false},
   435  				{"level1b/level2/level3_file", "", tar.TypeReg, false},
   436  			}},
   437  			{"MissingUpperAncestor", true, []pseudoHdr{
   438  				{"some", "", tar.TypeDir, false},
   439  				{"some/dir", "", tar.TypeDir, false},
   440  				{"some/dir/somewhere", "", tar.TypeReg, true},
   441  				{"another", "", tar.TypeDir, false},
   442  				{"another/dir", "", tar.TypeDir, false},
   443  				{"another/dir/somewhere", "", tar.TypeReg, false},
   444  			}},
   445  			{"UpperWhiteout", false, []pseudoHdr{
   446  				{whPrefix + "fileB", "", tar.TypeReg, true},
   447  				{"fileA", "", tar.TypeReg, true},
   448  				{"fileB", "", tar.TypeReg, true},
   449  				{"fileC", "", tar.TypeReg, false},
   450  				{whPrefix + "fileA", "", tar.TypeReg, true},
   451  				{whPrefix + "fileC", "", tar.TypeReg, true},
   452  			}},
   453  			// XXX: What umoci should do here is not really defined by the
   454  			//      spec. In particular, whether you need a whiteout for every
   455  			//      sub-path or just the path itself is not well-defined. This
   456  			//      code assumes that you *do not*.
   457  			{"UpperDirWhiteout", false, []pseudoHdr{
   458  				{whPrefix + "dir2", "", tar.TypeReg, true},
   459  				{"file", "", tar.TypeReg, false},
   460  				{"dir1", "", tar.TypeDir, true},
   461  				{"dir1/file", "", tar.TypeRegA, true},
   462  				{"dir1/link", "../badlink", tar.TypeSymlink, false},
   463  				{"dir1/verybadlink", "../../../../../../../../../../../../etc/shadow", tar.TypeSymlink, true},
   464  				{"dir1/verybadlink2", "/../../../../../../../../../../../../etc/shadow", tar.TypeSymlink, false},
   465  				{whPrefix + "dir1", "", tar.TypeReg, true},
   466  				{"dir2", "", tar.TypeDir, true},
   467  				{"dir2/file", "", tar.TypeRegA, true},
   468  				{"dir2/link", "../badlink", tar.TypeSymlink, false},
   469  			}},
   470  		} {
   471  			t.Logf("running Test%s", test.name)
   472  			mapOptions := MapOptions{
   473  				Rootless: os.Geteuid() != 0,
   474  			}
   475  
   476  			dir, err := ioutil.TempDir("", "umoci-TestUnpackOpaqueWhiteout")
   477  			if err != nil {
   478  				t.Fatal(err)
   479  			}
   480  			defer os.RemoveAll(dir)
   481  
   482  			// We do all whiteouts in a subdirectory.
   483  			whiteoutDir := "test-subdir"
   484  			whiteoutRoot := filepath.Join(dir, whiteoutDir)
   485  			if err := os.MkdirAll(whiteoutRoot, 0755); err != nil {
   486  				t.Fatal(err)
   487  			}
   488  
   489  			// Track if we have upper entries.
   490  			numUpper := 0
   491  
   492  			// First we apply the non-upper files in a new TarExtractor.
   493  			te := NewTarExtractor(mapOptions)
   494  			for _, ph := range test.pseudoHeaders {
   495  				// Skip upper.
   496  				if ph.upper {
   497  					numUpper++
   498  					continue
   499  				}
   500  				hdr, rdr := fromPseudoHdr(ph)
   501  				hdr.Name = filepath.Join(whiteoutDir, hdr.Name)
   502  				if err := te.UnpackEntry(dir, hdr, rdr); err != nil {
   503  					t.Errorf("UnpackEntry %s failed: %v", hdr.Name, err)
   504  				}
   505  			}
   506  
   507  			// Now we apply the upper files in another TarExtractor.
   508  			te = NewTarExtractor(mapOptions)
   509  			for _, ph := range test.pseudoHeaders {
   510  				// Skip non-upper.
   511  				if !ph.upper {
   512  					continue
   513  				}
   514  				hdr, rdr := fromPseudoHdr(ph)
   515  				hdr.Name = filepath.Join(whiteoutDir, hdr.Name)
   516  				if err := te.UnpackEntry(dir, hdr, rdr); err != nil {
   517  					t.Errorf("UnpackEntry %s failed: %v", hdr.Name, err)
   518  				}
   519  			}
   520  
   521  			// And now apply a whiteout for the whiteoutRoot.
   522  			whHdr := &tar.Header{
   523  				Name:     filepath.Join(whiteoutDir, whOpaque),
   524  				Typeflag: tar.TypeReg,
   525  			}
   526  			if err := te.UnpackEntry(dir, whHdr, nil); err != nil {
   527  				t.Errorf("unpack whiteout %s failed: %v", whiteoutRoot, err)
   528  				continue
   529  			}
   530  
   531  			// Now we double-check it worked. If the file was in "upper" it
   532  			// should have survived. If it was in lower it shouldn't. We don't
   533  			// bother checking the contents here.
   534  			for _, ph := range test.pseudoHeaders {
   535  				// If there's an explicit whiteout in the headers we ignore it
   536  				// here, since it won't be on the filesystem.
   537  				if strings.HasPrefix(filepath.Base(ph.path), whPrefix) {
   538  					t.Logf("ignoring whiteout entry %q during post-check", ph.path)
   539  					continue
   540  				}
   541  
   542  				fullPath := filepath.Join(whiteoutRoot, ph.path)
   543  				_, err := te.fsEval.Lstat(fullPath)
   544  				if err != nil && !os.IsNotExist(errors.Cause(err)) {
   545  					t.Errorf("unexpected lstat error of %s: %v", ph.path, err)
   546  				} else if ph.upper && err != nil {
   547  					t.Errorf("expected upper %s to exist: got %v", ph.path, err)
   548  				} else if !ph.upper && err == nil {
   549  					if !test.ignoreExist {
   550  						t.Errorf("expected lower %s to not exist", ph.path)
   551  					}
   552  				}
   553  			}
   554  
   555  			// Make sure the whiteoutRoot still exists.
   556  			if fi, err := te.fsEval.Lstat(whiteoutRoot); err != nil {
   557  				if os.IsNotExist(errors.Cause(err)) {
   558  					t.Errorf("expected whiteout root to still exist: %v", err)
   559  				} else {
   560  					t.Errorf("unexpected error in lstat of whiteout root: %v", err)
   561  				}
   562  			} else if !fi.IsDir() {
   563  				t.Errorf("expected whiteout root to still be a directory")
   564  			}
   565  
   566  			// Check that the directory is empty if there's no uppers.
   567  			if numUpper == 0 {
   568  				if fd, err := os.Open(whiteoutRoot); err != nil {
   569  					t.Errorf("unexpected error opening whiteoutRoot: %v", err)
   570  				} else if names, err := fd.Readdirnames(-1); err != nil {
   571  					t.Errorf("unexpected error reading dirnames: %v", err)
   572  				} else if len(names) != 0 {
   573  					t.Errorf("expected empty opaque'd dir: got %v", names)
   574  				}
   575  			}
   576  		}
   577  	}(t)
   578  }
   579  
   580  // TestUnpackHardlink makes sure that hardlinks are correctly unpacked in all
   581  // cases. In particular when it comes to hardlinks to symlinks.
   582  func TestUnpackHardlink(t *testing.T) {
   583  	// Create the files we're going to play with.
   584  	dir, err := ioutil.TempDir("", "umoci-TestUnpackHardlink")
   585  	if err != nil {
   586  		t.Fatal(err)
   587  	}
   588  	defer os.RemoveAll(dir)
   589  
   590  	var (
   591  		hdr *tar.Header
   592  
   593  		ctrValue  = []byte("some content we won't check")
   594  		regFile   = "regular"
   595  		symFile   = "link"
   596  		hardFileA = "hard link"
   597  		hardFileB = "hard link to symlink"
   598  	)
   599  
   600  	te := NewTarExtractor(MapOptions{})
   601  
   602  	// Regular file.
   603  	hdr = &tar.Header{
   604  		Name:       regFile,
   605  		Uid:        os.Getuid(),
   606  		Gid:        os.Getgid(),
   607  		Mode:       0644,
   608  		Size:       int64(len(ctrValue)),
   609  		Typeflag:   tar.TypeReg,
   610  		ModTime:    time.Now(),
   611  		AccessTime: time.Now(),
   612  		ChangeTime: time.Now(),
   613  	}
   614  	if err := te.UnpackEntry(dir, hdr, bytes.NewBuffer(ctrValue)); err != nil {
   615  		t.Fatalf("regular: unexpected UnpackEntry error: %s", err)
   616  	}
   617  
   618  	// Hardlink to regFile.
   619  	hdr = &tar.Header{
   620  		Name:     hardFileA,
   621  		Typeflag: tar.TypeLink,
   622  		Linkname: filepath.Join("/", regFile),
   623  		// These should **not** be applied.
   624  		Uid: os.Getuid() + 1337,
   625  		Gid: os.Getgid() + 2020,
   626  	}
   627  	if err := te.UnpackEntry(dir, hdr, nil); err != nil {
   628  		t.Fatalf("hardlinkA: unexpected UnpackEntry error: %s", err)
   629  	}
   630  
   631  	// Symlink to regFile.
   632  	hdr = &tar.Header{
   633  		Name:     symFile,
   634  		Uid:      os.Getuid(),
   635  		Gid:      os.Getgid(),
   636  		Typeflag: tar.TypeSymlink,
   637  		Linkname: filepath.Join("../../../", regFile),
   638  	}
   639  	if err := te.UnpackEntry(dir, hdr, nil); err != nil {
   640  		t.Fatalf("symlink: unexpected UnpackEntry error: %s", err)
   641  	}
   642  
   643  	// Hardlink to symlink.
   644  	hdr = &tar.Header{
   645  		Name:     hardFileB,
   646  		Typeflag: tar.TypeLink,
   647  		Linkname: filepath.Join("../../../", symFile),
   648  		// These should **really not** be applied.
   649  		Uid: os.Getuid() + 1337,
   650  		Gid: os.Getgid() + 2020,
   651  	}
   652  	if err := te.UnpackEntry(dir, hdr, nil); err != nil {
   653  		t.Fatalf("hardlinkB: unexpected UnpackEntry error: %s", err)
   654  	}
   655  
   656  	// Quickly make sure that the contents are as expected.
   657  	ctrValueGot, err := ioutil.ReadFile(filepath.Join(dir, regFile))
   658  	if err != nil {
   659  		t.Fatalf("regular file was not created: %s", err)
   660  	}
   661  	if !bytes.Equal(ctrValueGot, ctrValue) {
   662  		t.Fatalf("regular file did not have expected values: expected=%s got=%s", ctrValue, ctrValueGot)
   663  	}
   664  
   665  	// Now we have to check the inode numbers.
   666  	var regFi, symFi, hardAFi, hardBFi unix.Stat_t
   667  
   668  	if err := unix.Lstat(filepath.Join(dir, regFile), &regFi); err != nil {
   669  		t.Fatalf("could not stat regular file: %s", err)
   670  	}
   671  	if err := unix.Lstat(filepath.Join(dir, symFile), &symFi); err != nil {
   672  		t.Fatalf("could not stat symlink: %s", err)
   673  	}
   674  	if err := unix.Lstat(filepath.Join(dir, hardFileA), &hardAFi); err != nil {
   675  		t.Fatalf("could not stat hardlinkA: %s", err)
   676  	}
   677  	if err := unix.Lstat(filepath.Join(dir, hardFileB), &hardBFi); err != nil {
   678  		t.Fatalf("could not stat hardlinkB: %s", err)
   679  	}
   680  
   681  	// This test only runs on Linux anyway.
   682  
   683  	if regFi.Ino == symFi.Ino {
   684  		t.Errorf("regular and symlink have the same inode! ino=%d", regFi.Ino)
   685  	}
   686  	if hardAFi.Ino == hardBFi.Ino {
   687  		t.Errorf("both hardlinks have the same inode! ino=%d", hardAFi.Ino)
   688  	}
   689  	if hardAFi.Ino != regFi.Ino {
   690  		t.Errorf("hardlink to regular has a different inode: reg=%d hard=%d", regFi.Ino, hardAFi.Ino)
   691  	}
   692  	if hardBFi.Ino != symFi.Ino {
   693  		t.Errorf("hardlink to symlink has a different inode: sym=%d hard=%d", symFi.Ino, hardBFi.Ino)
   694  	}
   695  
   696  	// Double-check readlink.
   697  	linknameA, err := os.Readlink(filepath.Join(dir, symFile))
   698  	if err != nil {
   699  		t.Errorf("unexpected error reading symlink: %s", err)
   700  	}
   701  	linknameB, err := os.Readlink(filepath.Join(dir, hardFileB))
   702  	if err != nil {
   703  		t.Errorf("unexpected error reading hardlink to symlink: %s", err)
   704  	}
   705  	if linknameA != linknameB {
   706  		t.Errorf("hardlink to symlink doesn't match linkname: link=%s hard=%s", linknameA, linknameB)
   707  	}
   708  
   709  	// Make sure that uid and gid don't apply to hardlinks.
   710  	if int(regFi.Uid) != os.Getuid() {
   711  		t.Errorf("regular file: uid was changed by hardlink unpack: expected=%d got=%d", os.Getuid(), regFi.Uid)
   712  	}
   713  	if int(regFi.Gid) != os.Getgid() {
   714  		t.Errorf("regular file: gid was changed by hardlink unpack: expected=%d got=%d", os.Getgid(), regFi.Gid)
   715  	}
   716  	if int(symFi.Uid) != os.Getuid() {
   717  		t.Errorf("symlink: uid was changed by hardlink unpack: expected=%d got=%d", os.Getuid(), symFi.Uid)
   718  	}
   719  	if int(symFi.Gid) != os.Getgid() {
   720  		t.Errorf("symlink: gid was changed by hardlink unpack: expected=%d got=%d", os.Getgid(), symFi.Gid)
   721  	}
   722  }
   723  
   724  // TestUnpackEntryMap checks that the mapOptions handling works.
   725  func TestUnpackEntryMap(t *testing.T) {
   726  	if os.Geteuid() != 0 {
   727  		t.Log("mapOptions tests only work with root privileges")
   728  		t.Skip()
   729  	}
   730  
   731  	// TODO: Modify this to use subtests once Go 1.7 is in enough places.
   732  	func(t *testing.T) {
   733  		for _, test := range []struct {
   734  			uidMap rspec.LinuxIDMapping
   735  			gidMap rspec.LinuxIDMapping
   736  		}{
   737  			{rspec.LinuxIDMapping{HostID: 0, ContainerID: 0, Size: 100}, rspec.LinuxIDMapping{HostID: 0, ContainerID: 0, Size: 100}},
   738  			{rspec.LinuxIDMapping{HostID: uint32(os.Getuid()), ContainerID: 0, Size: 100}, rspec.LinuxIDMapping{HostID: uint32(os.Getgid()), ContainerID: 0, Size: 100}},
   739  			{rspec.LinuxIDMapping{HostID: uint32(os.Getuid() + 100), ContainerID: 0, Size: 100}, rspec.LinuxIDMapping{HostID: uint32(os.Getgid() + 200), ContainerID: 0, Size: 100}},
   740  		} {
   741  			// Create the files we're going to play with.
   742  			dir, err := ioutil.TempDir("", "umoci-TestUnpackEntryMap")
   743  			if err != nil {
   744  				t.Fatal(err)
   745  			}
   746  			defer os.RemoveAll(dir)
   747  
   748  			var (
   749  				hdrUID, hdrGID, uUID, uGID int
   750  				hdr                        *tar.Header
   751  				fi                         unix.Stat_t
   752  
   753  				ctrValue = []byte("some content we won't check")
   754  				regFile  = "regular"
   755  				symFile  = "link"
   756  				regDir   = " a directory"
   757  				symDir   = "link-dir"
   758  			)
   759  
   760  			te := NewTarExtractor(MapOptions{
   761  				UIDMappings: []rspec.LinuxIDMapping{test.uidMap},
   762  				GIDMappings: []rspec.LinuxIDMapping{test.gidMap},
   763  			})
   764  
   765  			// Regular file.
   766  			hdrUID, hdrGID = 0, 0
   767  			hdr = &tar.Header{
   768  				Name:       regFile,
   769  				Uid:        hdrUID,
   770  				Gid:        hdrGID,
   771  				Mode:       0644,
   772  				Size:       int64(len(ctrValue)),
   773  				Typeflag:   tar.TypeReg,
   774  				ModTime:    time.Now(),
   775  				AccessTime: time.Now(),
   776  				ChangeTime: time.Now(),
   777  			}
   778  			if err := te.UnpackEntry(dir, hdr, bytes.NewBuffer(ctrValue)); err != nil {
   779  				t.Fatalf("regfile: unexpected UnpackEntry error: %s", err)
   780  			}
   781  
   782  			if err := unix.Lstat(filepath.Join(dir, hdr.Name), &fi); err != nil {
   783  				t.Errorf("failed to lstat %s: %s", hdr.Name, err)
   784  			} else {
   785  				uUID = int(fi.Uid)
   786  				uGID = int(fi.Gid)
   787  				if uUID != int(test.uidMap.HostID)+hdrUID {
   788  					t.Errorf("file %s has the wrong uid mapping: got=%d expected=%d", hdr.Name, uUID, int(test.uidMap.HostID)+hdrUID)
   789  				}
   790  				if uGID != int(test.gidMap.HostID)+hdrGID {
   791  					t.Errorf("file %s has the wrong gid mapping: got=%d expected=%d", hdr.Name, uGID, int(test.gidMap.HostID)+hdrGID)
   792  				}
   793  			}
   794  
   795  			// Regular directory.
   796  			hdrUID, hdrGID = 13, 42
   797  			hdr = &tar.Header{
   798  				Name:       regDir,
   799  				Uid:        hdrUID,
   800  				Gid:        hdrGID,
   801  				Mode:       0755,
   802  				Typeflag:   tar.TypeDir,
   803  				ModTime:    time.Now(),
   804  				AccessTime: time.Now(),
   805  				ChangeTime: time.Now(),
   806  			}
   807  			if err := te.UnpackEntry(dir, hdr, bytes.NewBuffer(ctrValue)); err != nil {
   808  				t.Fatalf("regdir: unexpected UnpackEntry error: %s", err)
   809  			}
   810  
   811  			if err := unix.Lstat(filepath.Join(dir, hdr.Name), &fi); err != nil {
   812  				t.Errorf("failed to lstat %s: %s", hdr.Name, err)
   813  			} else {
   814  				uUID = int(fi.Uid)
   815  				uGID = int(fi.Gid)
   816  				if uUID != int(test.uidMap.HostID)+hdrUID {
   817  					t.Errorf("file %s has the wrong uid mapping: got=%d expected=%d", hdr.Name, uUID, int(test.uidMap.HostID)+hdrUID)
   818  				}
   819  				if uGID != int(test.gidMap.HostID)+hdrGID {
   820  					t.Errorf("file %s has the wrong gid mapping: got=%d expected=%d", hdr.Name, uGID, int(test.gidMap.HostID)+hdrGID)
   821  				}
   822  			}
   823  
   824  			// Symlink to file.
   825  			hdrUID, hdrGID = 23, 22
   826  			hdr = &tar.Header{
   827  				Name:       symFile,
   828  				Uid:        hdrUID,
   829  				Gid:        hdrGID,
   830  				Typeflag:   tar.TypeSymlink,
   831  				Linkname:   regFile,
   832  				ModTime:    time.Now(),
   833  				AccessTime: time.Now(),
   834  				ChangeTime: time.Now(),
   835  			}
   836  			if err := te.UnpackEntry(dir, hdr, bytes.NewBuffer(ctrValue)); err != nil {
   837  				t.Fatalf("regdir: unexpected UnpackEntry error: %s", err)
   838  			}
   839  
   840  			if err := unix.Lstat(filepath.Join(dir, hdr.Name), &fi); err != nil {
   841  				t.Errorf("failed to lstat %s: %s", hdr.Name, err)
   842  			} else {
   843  				uUID = int(fi.Uid)
   844  				uGID = int(fi.Gid)
   845  				if uUID != int(test.uidMap.HostID)+hdrUID {
   846  					t.Errorf("file %s has the wrong uid mapping: got=%d expected=%d", hdr.Name, uUID, int(test.uidMap.HostID)+hdrUID)
   847  				}
   848  				if uGID != int(test.gidMap.HostID)+hdrGID {
   849  					t.Errorf("file %s has the wrong gid mapping: got=%d expected=%d", hdr.Name, uGID, int(test.gidMap.HostID)+hdrGID)
   850  				}
   851  			}
   852  
   853  			// Symlink to director.
   854  			hdrUID, hdrGID = 99, 88
   855  			hdr = &tar.Header{
   856  				Name:       symDir,
   857  				Uid:        hdrUID,
   858  				Gid:        hdrGID,
   859  				Typeflag:   tar.TypeSymlink,
   860  				Linkname:   regDir,
   861  				ModTime:    time.Now(),
   862  				AccessTime: time.Now(),
   863  				ChangeTime: time.Now(),
   864  			}
   865  			if err := te.UnpackEntry(dir, hdr, bytes.NewBuffer(ctrValue)); err != nil {
   866  				t.Fatalf("regdir: unexpected UnpackEntry error: %s", err)
   867  			}
   868  
   869  			if err := unix.Lstat(filepath.Join(dir, hdr.Name), &fi); err != nil {
   870  				t.Errorf("failed to lstat %s: %s", hdr.Name, err)
   871  			} else {
   872  				uUID = int(fi.Uid)
   873  				uGID = int(fi.Gid)
   874  				if uUID != int(test.uidMap.HostID)+hdrUID {
   875  					t.Errorf("file %s has the wrong uid mapping: got=%d expected=%d", hdr.Name, uUID, int(test.uidMap.HostID)+hdrUID)
   876  				}
   877  				if uGID != int(test.gidMap.HostID)+hdrGID {
   878  					t.Errorf("file %s has the wrong gid mapping: got=%d expected=%d", hdr.Name, uGID, int(test.gidMap.HostID)+hdrGID)
   879  				}
   880  			}
   881  		}
   882  	}(t)
   883  }
   884  
   885  func TestIsDirlink(t *testing.T) {
   886  	dir, err := ioutil.TempDir("", "umoci-TestDirLink")
   887  	if err != nil {
   888  		t.Fatal(err)
   889  	}
   890  	defer os.RemoveAll(dir)
   891  
   892  	if err := os.Mkdir(filepath.Join(dir, "test_dir"), 0755); err != nil {
   893  		t.Fatal(err)
   894  	}
   895  	if file, err := os.Create(filepath.Join(dir, "test_file")); err != nil {
   896  		t.Fatal(err)
   897  	} else {
   898  		file.Close()
   899  	}
   900  	if err := os.Symlink("test_dir", filepath.Join(dir, "link")); err != nil {
   901  		t.Fatal(err)
   902  	}
   903  
   904  	te := NewTarExtractor(MapOptions{})
   905  	// Basic symlink usage.
   906  	if dirlink, err := te.isDirlink(dir, filepath.Join(dir, "link")); err != nil {
   907  		t.Errorf("symlink failed: %v", err)
   908  	} else if !dirlink {
   909  		t.Errorf("dirlink test failed")
   910  	}
   911  
   912  	// "Read" a non-existent link.
   913  	if _, err := te.isDirlink(dir, filepath.Join(dir, "doesnt-exist")); err == nil {
   914  		t.Errorf("read non-existent dirlink")
   915  	}
   916  	// "Read" a directory.
   917  	if _, err := te.isDirlink(dir, filepath.Join(dir, "test_dir")); err == nil {
   918  		t.Errorf("read non-link dirlink")
   919  	}
   920  	// "Read" a file.
   921  	if _, err := te.isDirlink(dir, filepath.Join(dir, "test_file")); err == nil {
   922  		t.Errorf("read non-link dirlink")
   923  	}
   924  
   925  	// Break the symlink.
   926  	if err := os.Remove(filepath.Join(dir, "test_dir")); err != nil {
   927  		t.Fatal(err)
   928  	}
   929  	if dirlink, err := te.isDirlink(dir, filepath.Join(dir, "link")); err != nil {
   930  		t.Errorf("broken symlink failed: %v", err)
   931  	} else if dirlink {
   932  		t.Errorf("broken dirlink test failed")
   933  	}
   934  
   935  	// Point the symlink to a file.
   936  	if err := os.Remove(filepath.Join(dir, "link")); err != nil {
   937  		t.Fatal(err)
   938  	}
   939  	if err := os.Symlink("test_file", filepath.Join(dir, "link")); err != nil {
   940  		t.Fatal(err)
   941  	}
   942  	if dirlink, err := te.isDirlink(dir, filepath.Join(dir, "link")); err != nil {
   943  		t.Errorf("file symlink failed: %v", err)
   944  	} else if dirlink {
   945  		t.Errorf("file dirlink test failed")
   946  	}
   947  }