github.com/rkt/rkt@v1.30.1-0.20200224141603-171c416fac02/pkg/tar/tar_test.go (about)

     1  // Copyright 2014 The rkt Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package tar
    16  
    17  import (
    18  	"archive/tar"
    19  	"fmt"
    20  	"io"
    21  	"io/ioutil"
    22  	"os"
    23  	"path/filepath"
    24  	"runtime"
    25  	"strings"
    26  	"syscall"
    27  	"testing"
    28  	"time"
    29  
    30  	"github.com/rkt/rkt/pkg/multicall"
    31  	"github.com/rkt/rkt/pkg/sys"
    32  	"github.com/rkt/rkt/pkg/user"
    33  )
    34  
    35  func init() {
    36  	multicall.MaybeExec()
    37  }
    38  
    39  type testTarEntry struct {
    40  	header   *tar.Header
    41  	contents string
    42  }
    43  
    44  func newTestTar(entries []*testTarEntry) (string, error) {
    45  	t, err := ioutil.TempFile("", "test-tar")
    46  	if err != nil {
    47  		return "", err
    48  	}
    49  	defer t.Close()
    50  	tw := tar.NewWriter(t)
    51  	for _, entry := range entries {
    52  		// Add default mode
    53  		if entry.header.Mode == 0 {
    54  			if entry.header.Typeflag == tar.TypeDir {
    55  				entry.header.Mode = 0755
    56  			} else {
    57  				entry.header.Mode = 0644
    58  			}
    59  		}
    60  		// Add calling user uid and gid or tests will fail
    61  		entry.header.Uid = os.Getuid()
    62  		entry.header.Gid = os.Getgid()
    63  		if err := tw.WriteHeader(entry.header); err != nil {
    64  			return "", err
    65  		}
    66  		if _, err := io.WriteString(tw, entry.contents); err != nil {
    67  			return "", err
    68  		}
    69  	}
    70  	if err := tw.Close(); err != nil {
    71  		return "", err
    72  	}
    73  	return t.Name(), nil
    74  }
    75  
    76  type fileInfo struct {
    77  	path     string
    78  	typeflag byte
    79  	size     int64
    80  	contents string
    81  	mode     os.FileMode
    82  }
    83  
    84  func fileInfoSliceToMap(slice []*fileInfo) map[string]*fileInfo {
    85  	fim := make(map[string]*fileInfo, len(slice))
    86  	for _, fi := range slice {
    87  		fim[fi.path] = fi
    88  	}
    89  	return fim
    90  }
    91  
    92  func checkExpectedFiles(dir string, expectedFiles map[string]*fileInfo) error {
    93  	files := make(map[string]*fileInfo)
    94  	err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
    95  		fm := info.Mode()
    96  		if path == dir {
    97  			return nil
    98  		}
    99  		relpath, err := filepath.Rel(dir, path)
   100  		if err != nil {
   101  			return err
   102  		}
   103  		switch {
   104  		case fm.IsRegular():
   105  			files[relpath] = &fileInfo{path: relpath, typeflag: tar.TypeReg, size: info.Size(), mode: info.Mode().Perm()}
   106  		case info.IsDir():
   107  			files[relpath] = &fileInfo{path: relpath, typeflag: tar.TypeDir, mode: info.Mode().Perm()}
   108  		case fm&os.ModeSymlink != 0:
   109  			files[relpath] = &fileInfo{path: relpath, typeflag: tar.TypeSymlink, mode: info.Mode()}
   110  		default:
   111  			return fmt.Errorf("file mode not handled: %v", fm)
   112  		}
   113  
   114  		return nil
   115  	})
   116  	if err != nil {
   117  		return err
   118  	}
   119  
   120  	// Set defaults for not specified expected file mode
   121  	for _, ef := range expectedFiles {
   122  		if ef.mode == 0 {
   123  			if ef.typeflag == tar.TypeDir {
   124  				ef.mode = 0755
   125  			} else {
   126  				ef.mode = 0644
   127  			}
   128  		}
   129  	}
   130  
   131  	for _, ef := range expectedFiles {
   132  		_, ok := files[ef.path]
   133  		if !ok {
   134  			return fmt.Errorf("Expected file %q not in files", ef.path)
   135  		}
   136  
   137  	}
   138  
   139  	for _, file := range files {
   140  		ef, ok := expectedFiles[file.path]
   141  		if !ok {
   142  			return fmt.Errorf("file %q not in expectedFiles", file.path)
   143  		}
   144  		if ef.typeflag != file.typeflag {
   145  			return fmt.Errorf("file %q: file type differs: wanted: %d, got: %d", file.path, ef.typeflag, file.typeflag)
   146  		}
   147  		if ef.typeflag == tar.TypeReg {
   148  			if ef.size != file.size {
   149  				return fmt.Errorf("file %q: size differs: wanted %d, wanted: %d", file.path, ef.size, file.size)
   150  			}
   151  			if ef.contents != "" {
   152  				buf, err := ioutil.ReadFile(filepath.Join(dir, file.path))
   153  				if err != nil {
   154  					return fmt.Errorf("unexpected error: %v", err)
   155  				}
   156  				if string(buf) != ef.contents {
   157  					return fmt.Errorf("unexpected contents, wanted: %s, got: %s", ef.contents, buf)
   158  				}
   159  
   160  			}
   161  		}
   162  		// Check modes but ignore symlinks
   163  		if ef.mode != file.mode && ef.typeflag != tar.TypeSymlink {
   164  			return fmt.Errorf("file %q: mode differs: wanted %#o, got: %#o", file.path, ef.mode, file.mode)
   165  		}
   166  
   167  	}
   168  	return nil
   169  }
   170  
   171  func TestExtractTarFolders(t *testing.T) {
   172  	if !sys.HasChrootCapability() {
   173  		t.Skipf("chroot capability not available. Disabling test.")
   174  	}
   175  	testExtractTarFolders(t, extractTarHelper)
   176  }
   177  func TestExtractTarFoldersInsecure(t *testing.T) {
   178  	testExtractTarFolders(t, extractTarInsecureHelper)
   179  }
   180  func testExtractTarFolders(t *testing.T, extractTar func(io.Reader, string) error) {
   181  	entries := []*testTarEntry{
   182  		{
   183  			contents: "foo",
   184  			header: &tar.Header{
   185  				Name: "deep/folder/foo.txt",
   186  				Size: 3,
   187  			},
   188  		},
   189  		{
   190  			header: &tar.Header{
   191  				Name:     "deep/folder/",
   192  				Typeflag: tar.TypeDir,
   193  				Mode:     int64(0747),
   194  			},
   195  		},
   196  		{
   197  			contents: "bar",
   198  			header: &tar.Header{
   199  				Name: "deep/folder/bar.txt",
   200  				Size: 3,
   201  			},
   202  		},
   203  		{
   204  			header: &tar.Header{
   205  				Name:     "deep/folder2/symlink.txt",
   206  				Typeflag: tar.TypeSymlink,
   207  				Linkname: "deep/folder/foo.txt",
   208  			},
   209  		},
   210  		{
   211  			header: &tar.Header{
   212  				Name:     "deep/folder2/",
   213  				Typeflag: tar.TypeDir,
   214  				Mode:     int64(0747),
   215  			},
   216  		},
   217  		{
   218  			contents: "bar",
   219  			header: &tar.Header{
   220  				Name: "deep/folder2/bar.txt",
   221  				Size: 3,
   222  			},
   223  		},
   224  		{
   225  			header: &tar.Header{
   226  				Name:     "deep/deep/folder",
   227  				Typeflag: tar.TypeDir,
   228  				Mode:     int64(0755),
   229  			},
   230  		},
   231  		{
   232  			header: &tar.Header{
   233  				Name:     "deep/deep/",
   234  				Typeflag: tar.TypeDir,
   235  				Mode:     int64(0747),
   236  			},
   237  		},
   238  	}
   239  
   240  	testTarPath, err := newTestTar(entries)
   241  	if err != nil {
   242  		t.Errorf("unexpected error: %v", err)
   243  	}
   244  	defer os.Remove(testTarPath)
   245  	containerTar, err := os.Open(testTarPath)
   246  	if err != nil {
   247  		t.Errorf("unexpected error: %v", err)
   248  	}
   249  	defer containerTar.Close()
   250  	tmpdir, err := ioutil.TempDir("", "rkt-temp-dir")
   251  	if err != nil {
   252  		t.Errorf("unexpected error: %v", err)
   253  	}
   254  	os.RemoveAll(tmpdir)
   255  	err = os.MkdirAll(tmpdir, 0755)
   256  	if err != nil {
   257  		t.Errorf("unexpected error: %v", err)
   258  	}
   259  	defer os.RemoveAll(tmpdir)
   260  	err = extractTar(containerTar, tmpdir)
   261  	if err != nil {
   262  		t.Errorf("unexpected error: %v", err)
   263  	}
   264  	matches, err := filepath.Glob(filepath.Join(tmpdir, "deep/folder/*.txt"))
   265  	if err != nil {
   266  		t.Errorf("unexpected error: %v", err)
   267  	}
   268  	if len(matches) != 2 {
   269  		t.Errorf("unexpected number of files found: %d, wanted 2", len(matches))
   270  	}
   271  	matches, err = filepath.Glob(filepath.Join(tmpdir, "deep/folder2/*.txt"))
   272  	if err != nil {
   273  		t.Errorf("unexpected error: %v", err)
   274  	}
   275  	if len(matches) != 2 {
   276  		t.Errorf("unexpected number of files found: %d, wanted 2", len(matches))
   277  	}
   278  
   279  	dirInfo, err := os.Lstat(filepath.Join(tmpdir, "deep/folder"))
   280  	if err != nil {
   281  		t.Errorf("unexpected error: %v", err)
   282  	} else if dirInfo.Mode().Perm() != os.FileMode(0747) {
   283  		t.Errorf("unexpected dir mode: %s", dirInfo.Mode())
   284  	}
   285  	dirInfo, err = os.Lstat(filepath.Join(tmpdir, "deep/deep"))
   286  	if err != nil {
   287  		t.Errorf("unexpected error: %v", err)
   288  	} else if dirInfo.Mode().Perm() != os.FileMode(0747) {
   289  		t.Errorf("unexpected dir mode: %s", dirInfo.Mode())
   290  	}
   291  }
   292  
   293  func TestExtractTarFileToBuf(t *testing.T) {
   294  	entries := []*testTarEntry{
   295  		{
   296  			header: &tar.Header{
   297  				Name:     "folder/",
   298  				Typeflag: tar.TypeDir,
   299  				Mode:     int64(0747),
   300  			},
   301  		},
   302  		{
   303  			contents: "foo",
   304  			header: &tar.Header{
   305  				Name: "folder/foo.txt",
   306  				Size: 3,
   307  			},
   308  		},
   309  		{
   310  			contents: "bar",
   311  			header: &tar.Header{
   312  				Name: "folder/bar.txt",
   313  				Size: 3,
   314  			},
   315  		},
   316  		{
   317  			header: &tar.Header{
   318  				Name:     "folder/symlink.txt",
   319  				Typeflag: tar.TypeSymlink,
   320  				Linkname: "folder/foo.txt",
   321  			},
   322  		},
   323  	}
   324  	testTarPath, err := newTestTar(entries)
   325  	if err != nil {
   326  		t.Fatalf("unexpected error: %v", err)
   327  	}
   328  	defer os.Remove(testTarPath)
   329  	containerTar1, err := os.Open(testTarPath)
   330  	if err != nil {
   331  		t.Fatalf("unexpected error: %v", err)
   332  	}
   333  	defer containerTar1.Close()
   334  
   335  	tr := tar.NewReader(containerTar1)
   336  	buf, err := extractFileFromTar(tr, "folder/foo.txt")
   337  	if err != nil {
   338  		t.Errorf("unexpected error: %v", err)
   339  	}
   340  	if string(buf) != "foo" {
   341  		t.Errorf("unexpected contents, wanted: %s, got: %s", "foo", buf)
   342  	}
   343  
   344  	containerTar2, err := os.Open(testTarPath)
   345  	if err != nil {
   346  		t.Errorf("unexpected error: %v", err)
   347  	}
   348  	defer containerTar2.Close()
   349  	tr = tar.NewReader(containerTar2)
   350  	buf, err = extractFileFromTar(tr, "folder/symlink.txt")
   351  	if err == nil {
   352  		t.Errorf("expected error")
   353  	}
   354  }
   355  
   356  func TestExtractTarPWL(t *testing.T) {
   357  	if !sys.HasChrootCapability() {
   358  		t.Skipf("chroot capability not available. Disabling test.")
   359  	}
   360  	testExtractTarPWL(t, extractTarHelperPWL)
   361  }
   362  func TestExtractTarPWLInsecure(t *testing.T) {
   363  	testExtractTarPWL(t, extractTarInsecureHelperPWL)
   364  }
   365  func testExtractTarPWL(t *testing.T, extractTar func(rdr io.Reader, target string, pwl PathWhitelistMap) error) {
   366  	entries := []*testTarEntry{
   367  		{
   368  			header: &tar.Header{
   369  				Name:     "folder/",
   370  				Typeflag: tar.TypeDir,
   371  				Mode:     int64(0747),
   372  			},
   373  		},
   374  		{
   375  			contents: "foo",
   376  			header: &tar.Header{
   377  				Name: "folder/foo.txt",
   378  				Size: 3,
   379  			},
   380  		},
   381  		{
   382  			contents: "bar",
   383  			header: &tar.Header{
   384  				Name: "folder/bar.txt",
   385  				Size: 3,
   386  			},
   387  		},
   388  		{
   389  			header: &tar.Header{
   390  				Name:     "folder/symlink.txt",
   391  				Typeflag: tar.TypeSymlink,
   392  				Linkname: "folder/foo.txt",
   393  			},
   394  		},
   395  	}
   396  	testTarPath, err := newTestTar(entries)
   397  	if err != nil {
   398  		t.Errorf("unexpected error: %v", err)
   399  	}
   400  	defer os.Remove(testTarPath)
   401  	containerTar, err := os.Open(testTarPath)
   402  	if err != nil {
   403  		t.Errorf("unexpected error: %v", err)
   404  	}
   405  	defer containerTar.Close()
   406  	tmpdir, err := ioutil.TempDir("", "rkt-temp-dir")
   407  	if err != nil {
   408  		t.Errorf("unexpected error: %v", err)
   409  	}
   410  	defer os.RemoveAll(tmpdir)
   411  
   412  	pwl := make(PathWhitelistMap)
   413  	pwl["folder/foo.txt"] = struct{}{}
   414  	err = extractTar(containerTar, tmpdir, pwl)
   415  	if err != nil {
   416  		t.Errorf("unexpected error: %v", err)
   417  	}
   418  	matches, err := filepath.Glob(filepath.Join(tmpdir, "folder/*.txt"))
   419  	if err != nil {
   420  		t.Errorf("unexpected error: %v", err)
   421  	}
   422  	if len(matches) != 1 {
   423  		t.Errorf("unexpected number of files found: %d, wanted 1", len(matches))
   424  	}
   425  }
   426  
   427  func TestExtractTarOverwrite(t *testing.T) {
   428  	if !sys.HasChrootCapability() {
   429  		t.Skipf("chroot capability not available. Disabling test.")
   430  	}
   431  	testExtractTarOverwrite(t, extractTarOverwriteHelper)
   432  }
   433  func TestExtractTarOverwriteInsecure(t *testing.T) {
   434  	testExtractTarOverwrite(t, extractTarInsecureHelper)
   435  }
   436  func testExtractTarOverwrite(t *testing.T, extractTar func(io.Reader, string) error) {
   437  	tmpdir, err := ioutil.TempDir("", "rkt-temp-dir")
   438  	if err != nil {
   439  		t.Fatalf("unexpected error: %v", err)
   440  	}
   441  	defer os.RemoveAll(tmpdir)
   442  
   443  	entries := []*testTarEntry{
   444  		{
   445  			contents: "hello",
   446  			header: &tar.Header{
   447  				Name: "hello.txt",
   448  				Size: 5,
   449  			},
   450  		},
   451  		{
   452  			header: &tar.Header{
   453  				Name:     "afolder",
   454  				Typeflag: tar.TypeDir,
   455  			},
   456  		},
   457  		{
   458  			contents: "hello",
   459  			header: &tar.Header{
   460  				Name: "afolder/hello.txt",
   461  				Size: 5,
   462  			},
   463  		},
   464  		{
   465  			contents: "hello",
   466  			header: &tar.Header{
   467  				Name: "afile",
   468  				Size: 5,
   469  			},
   470  		},
   471  		{
   472  			header: &tar.Header{
   473  				Name:     "folder01",
   474  				Typeflag: tar.TypeDir,
   475  			},
   476  		},
   477  		{
   478  			contents: "hello",
   479  			header: &tar.Header{
   480  				Name: "folder01/file01",
   481  				Size: 5,
   482  			},
   483  		},
   484  		{
   485  			contents: "hello",
   486  			header: &tar.Header{
   487  				Name: "filesymlinked",
   488  				Size: 5,
   489  			},
   490  		},
   491  		{
   492  			header: &tar.Header{
   493  				Name:     "linktofile",
   494  				Linkname: "filesymlinked",
   495  				Typeflag: tar.TypeSymlink,
   496  			},
   497  		},
   498  
   499  		{
   500  			header: &tar.Header{
   501  				Name:     "dirsymlinked",
   502  				Typeflag: tar.TypeDir,
   503  			},
   504  		},
   505  		{
   506  			header: &tar.Header{
   507  				Name:     "linktodir",
   508  				Linkname: "dirsymlinked",
   509  				Typeflag: tar.TypeSymlink,
   510  			},
   511  		},
   512  	}
   513  
   514  	testTarPath, err := newTestTar(entries)
   515  	if err != nil {
   516  		t.Fatalf("unexpected error: %v", err)
   517  	}
   518  	defer os.Remove(testTarPath)
   519  	containerTar1, err := os.Open(testTarPath)
   520  	if err != nil {
   521  		t.Fatalf("unexpected error: %v", err)
   522  	}
   523  	defer containerTar1.Close()
   524  	err = extractTar(containerTar1, tmpdir)
   525  	if err != nil {
   526  		t.Fatalf("unexpected error: %v", err)
   527  	}
   528  
   529  	// Now overwrite:
   530  	// a file with a new file
   531  	// a dir with a file
   532  	entries = []*testTarEntry{
   533  		{
   534  			contents: "newhello",
   535  			header: &tar.Header{
   536  				Name: "hello.txt",
   537  				Size: 8,
   538  			},
   539  		},
   540  		// Now this is a file
   541  		{
   542  			contents: "nowafile",
   543  			header: &tar.Header{
   544  				Name:     "afolder",
   545  				Typeflag: tar.TypeReg,
   546  				Size:     8,
   547  			},
   548  		},
   549  		// Now this is a dir
   550  		{
   551  			header: &tar.Header{
   552  				Name:     "afile",
   553  				Typeflag: tar.TypeDir,
   554  			},
   555  		},
   556  		// Overwrite symlink to a file with a regular file
   557  		// the linked file shouldn't be removed
   558  		{
   559  			contents: "filereplacingsymlink",
   560  			header: &tar.Header{
   561  				Name:     "linktofile",
   562  				Typeflag: tar.TypeReg,
   563  				Size:     20,
   564  			},
   565  		},
   566  		// Overwrite symlink to a dir with a regular file
   567  		// the linked directory and all its contents shouldn't be
   568  		// removed
   569  		{
   570  			contents: "filereplacingsymlink",
   571  			header: &tar.Header{
   572  				Name:     "linktodir",
   573  				Typeflag: tar.TypeReg,
   574  				Size:     20,
   575  			},
   576  		},
   577  		// folder01 already exists and shouldn't be removed (keeping folder01/file01)
   578  		{
   579  			header: &tar.Header{
   580  				Name:     "folder01",
   581  				Typeflag: tar.TypeDir,
   582  				Mode:     int64(0755),
   583  			},
   584  		},
   585  		{
   586  			contents: "hello",
   587  			header: &tar.Header{
   588  				Name: "folder01/file02",
   589  				Size: 5,
   590  				Mode: int64(0644),
   591  			},
   592  		},
   593  	}
   594  	testTarPath, err = newTestTar(entries)
   595  	if err != nil {
   596  		t.Errorf("unexpected error: %v", err)
   597  	}
   598  	defer os.Remove(testTarPath)
   599  	containerTar2, err := os.Open(testTarPath)
   600  	if err != nil {
   601  		t.Errorf("unexpected error: %v", err)
   602  	}
   603  	defer containerTar2.Close()
   604  	err = extractTar(containerTar2, tmpdir)
   605  
   606  	expectedFiles := []*fileInfo{
   607  		{path: "hello.txt", typeflag: tar.TypeReg, size: 8, contents: "newhello"},
   608  		{path: "linktofile", typeflag: tar.TypeReg, size: 20},
   609  		{path: "linktodir", typeflag: tar.TypeReg, size: 20},
   610  		{path: "afolder", typeflag: tar.TypeReg, size: 8},
   611  		{path: "dirsymlinked", typeflag: tar.TypeDir},
   612  		{path: "afile", typeflag: tar.TypeDir},
   613  		{path: "filesymlinked", typeflag: tar.TypeReg, size: 5},
   614  		{path: "folder01", typeflag: tar.TypeDir},
   615  		{path: "folder01/file01", typeflag: tar.TypeReg, size: 5},
   616  		{path: "folder01/file02", typeflag: tar.TypeReg, size: 5},
   617  	}
   618  
   619  	err = checkExpectedFiles(tmpdir, fileInfoSliceToMap(expectedFiles))
   620  	if err != nil {
   621  		t.Errorf("unexpected error: %v", err)
   622  	}
   623  }
   624  
   625  func TestExtractTarTimes(t *testing.T) {
   626  	if !sys.HasChrootCapability() {
   627  		t.Skipf("chroot capability not available. Disabling test.")
   628  	}
   629  	testExtractTarTimes(t, extractTarHelper)
   630  }
   631  func TestExtractTarTimesInsecure(t *testing.T) {
   632  	testExtractTarTimes(t, extractTarInsecureHelper)
   633  }
   634  func testExtractTarTimes(t *testing.T, extractTar func(io.Reader, string) error) {
   635  	// Do not set ns as tar has second precision
   636  	time1 := time.Unix(100000, 0)
   637  	time2 := time.Unix(200000, 0)
   638  	time3 := time.Unix(300000, 0)
   639  	entries := []*testTarEntry{
   640  		{
   641  			header: &tar.Header{
   642  				Name:     "folder/",
   643  				Typeflag: tar.TypeDir,
   644  				Mode:     int64(0747),
   645  				ModTime:  time1,
   646  			},
   647  		},
   648  		{
   649  			contents: "foo",
   650  			header: &tar.Header{
   651  				Name:    "folder/foo.txt",
   652  				Size:    3,
   653  				ModTime: time2,
   654  			},
   655  		},
   656  		{
   657  			header: &tar.Header{
   658  				Name:     "folder/symlink.txt",
   659  				Typeflag: tar.TypeSymlink,
   660  				Linkname: "folder/foo.txt",
   661  				ModTime:  time3,
   662  			},
   663  		},
   664  	}
   665  
   666  	testTarPath, err := newTestTar(entries)
   667  	if err != nil {
   668  		t.Errorf("unexpected error: %v", err)
   669  	}
   670  	defer os.Remove(testTarPath)
   671  	containerTar, err := os.Open(testTarPath)
   672  	if err != nil {
   673  		t.Errorf("unexpected error: %v", err)
   674  	}
   675  	defer containerTar.Close()
   676  	tmpdir, err := ioutil.TempDir("", "rkt-temp-dir")
   677  	if err != nil {
   678  		t.Errorf("unexpected error: %v", err)
   679  	}
   680  	os.RemoveAll(tmpdir)
   681  	err = os.MkdirAll(tmpdir, 0755)
   682  	if err != nil {
   683  		t.Errorf("unexpected error: %v", err)
   684  	}
   685  	err = extractTar(containerTar, tmpdir)
   686  	if err != nil {
   687  		t.Errorf("unexpected error: %v", err)
   688  	}
   689  	err = checkTime(filepath.Join(tmpdir, "folder/"), time1)
   690  	if err != nil {
   691  		t.Errorf("unexpected error: %v", err)
   692  	}
   693  	err = checkTime(filepath.Join(tmpdir, "folder/foo.txt"), time2)
   694  	if err != nil {
   695  		t.Errorf("unexpected error: %v", err)
   696  	}
   697  
   698  	//Check only (by now) on linux
   699  	if runtime.GOOS == "linux" {
   700  		err = checkTime(filepath.Join(tmpdir, "folder/symlink.txt"), time3)
   701  		if err != nil {
   702  			t.Errorf("unexpected error: %v", err)
   703  		}
   704  	}
   705  }
   706  
   707  func checkTime(path string, time time.Time) error {
   708  	info, err := os.Lstat(path)
   709  	if err != nil {
   710  		return err
   711  	}
   712  
   713  	if info.ModTime() != time {
   714  		return fmt.Errorf("%s: info.ModTime: %s, different from expected time: %s", path, info.ModTime(), time)
   715  	}
   716  	return nil
   717  }
   718  
   719  func TestExtractTarHardLink(t *testing.T) {
   720  	if !sys.HasChrootCapability() {
   721  		t.Skipf("chroot capability not available. Disabling test.")
   722  	}
   723  	testExtractTarHardLink(t, extractTarHelper)
   724  }
   725  func TestExtractTarHardLinkInsecure(t *testing.T) {
   726  	testExtractTarHardLink(t, extractTarInsecureHelper)
   727  }
   728  func testExtractTarHardLink(t *testing.T, extractTar func(io.Reader, string) error) {
   729  	entries := []*testTarEntry{
   730  		{
   731  			contents: "foo",
   732  			header: &tar.Header{
   733  				Name: "/foo.txt",
   734  				Size: 3,
   735  			},
   736  		},
   737  		{
   738  			header: &tar.Header{
   739  				Name:     "foolink",
   740  				Typeflag: tar.TypeLink,
   741  				Linkname: "/foo.txt",
   742  			},
   743  		},
   744  	}
   745  
   746  	testTarPath, err := newTestTar(entries)
   747  	if err != nil {
   748  		t.Errorf("unexpected error: %v", err)
   749  	}
   750  	defer os.Remove(testTarPath)
   751  	containerTar, err := os.Open(testTarPath)
   752  	if err != nil {
   753  		t.Errorf("unexpected error: %v", err)
   754  	}
   755  	defer containerTar.Close()
   756  	tmpdir, err := ioutil.TempDir("", "rkt-temp-dir")
   757  	if err != nil {
   758  		t.Errorf("unexpected error: %v", err)
   759  	}
   760  	os.RemoveAll(tmpdir)
   761  	err = os.MkdirAll(tmpdir, 0755)
   762  	if err != nil {
   763  		t.Errorf("unexpected error: %v", err)
   764  	}
   765  	defer os.RemoveAll(tmpdir)
   766  	err = extractTar(containerTar, tmpdir)
   767  	if err != nil {
   768  		t.Errorf("unexpected error: %v", err)
   769  	}
   770  	var origFile syscall.Stat_t
   771  	err = syscall.Stat(filepath.Join(tmpdir, "/foo.txt"), &origFile)
   772  	if err != nil {
   773  		t.Errorf("unexpected error: %v", err)
   774  	}
   775  	var linkedFile syscall.Stat_t
   776  	err = syscall.Stat(filepath.Join(tmpdir, "/foolink"), &linkedFile)
   777  	if err != nil {
   778  		t.Errorf("unexpected error: %v", err)
   779  	}
   780  	if origFile.Nlink != 2 {
   781  		t.Errorf("wrong number of nlinks on original file, expecting: 2, actual: %d", origFile.Nlink)
   782  	}
   783  	if linkedFile.Nlink != 2 {
   784  		t.Errorf("wrong number of nlinks on linked file, expecting: 2, actual: %d", origFile.Nlink)
   785  	}
   786  	if origFile.Ino != linkedFile.Ino {
   787  		t.Errorf("original file and linked file have different inodes")
   788  	}
   789  }
   790  
   791  func TestExtractTarXattr(t *testing.T) {
   792  	if !sys.HasChrootCapability() {
   793  		t.Skipf("chroot capability not available. Disabling test.")
   794  	}
   795  
   796  	entries := []*testTarEntry{
   797  		{
   798  			contents: "foo",
   799  			header: &tar.Header{
   800  				Name: "folder/foo.txt",
   801  				Size: 3,
   802  				Mode: int64(0755),
   803  				Xattrs: map[string]string{
   804  					"security.capability": "foo",
   805  					"user.baz":            "bar",
   806  					"faulty":              "xattr",
   807  				},
   808  			},
   809  		},
   810  	}
   811  
   812  	expected := []struct {
   813  		Xattrs map[string]string
   814  	}{
   815  		{
   816  			Xattrs: map[string]string{
   817  				"security.capability": "foo",
   818  				"user.baz":            "bar",
   819  			},
   820  		},
   821  	}
   822  
   823  	testTarPath, err := newTestTar(entries)
   824  	if err != nil {
   825  		t.Errorf("unexpected error: %v", err)
   826  	}
   827  	defer os.Remove(testTarPath)
   828  
   829  	containerTar, err := os.Open(testTarPath)
   830  	if err != nil {
   831  		t.Errorf("unexpected error: %v", err)
   832  	}
   833  	defer containerTar.Close()
   834  
   835  	wd, err := os.Getwd()
   836  	if err != nil {
   837  		t.Errorf("unexpected error: %v", err)
   838  	}
   839  
   840  	tmpdir, err := ioutil.TempDir(wd, "rkt-temp-dir")
   841  	if err != nil {
   842  		t.Errorf("unexpected error: %v", err)
   843  	}
   844  	os.RemoveAll(tmpdir)
   845  
   846  	err = os.MkdirAll(tmpdir, 0755)
   847  	if err != nil {
   848  		t.Errorf("unexpected error: %v", err)
   849  	}
   850  	defer os.RemoveAll(tmpdir)
   851  
   852  	err = ExtractTar(containerTar, tmpdir, true, user.NewBlankUidRange(), nil)
   853  	if err != nil {
   854  		if strings.Contains(err.Error(), "operation not supported") {
   855  			t.Skipf("xattr unsupported, disabling test, error %v", err)
   856  		}
   857  
   858  		t.Errorf("unexpected error: %v", err)
   859  	}
   860  
   861  	for i, expected := range expected {
   862  		for k, v := range expected.Xattrs {
   863  			p := filepath.Join(tmpdir, entries[i].header.Name)
   864  			dest := make([]byte, len(v))
   865  
   866  			_, err := syscall.Getxattr(p, k, dest)
   867  			if err != nil {
   868  				t.Errorf("unexpected error: %v", err)
   869  			}
   870  
   871  			if got := string(dest); got != v {
   872  				t.Errorf("Expected xattr %q=%q, got %q", k, v, got)
   873  			}
   874  		}
   875  	}
   876  }
   877  
   878  func extractTarOverwriteHelper(rdr io.Reader, target string) error {
   879  	return ExtractTar(rdr, target, true, user.NewBlankUidRange(), nil)
   880  }
   881  
   882  func extractTarHelper(rdr io.Reader, target string) error {
   883  	return extractTarHelperPWL(rdr, target, nil)
   884  }
   885  
   886  func extractTarHelperPWL(rdr io.Reader, target string, pwl PathWhitelistMap) error {
   887  	return ExtractTar(rdr, target, false, user.NewBlankUidRange(), pwl)
   888  }
   889  
   890  func extractTarInsecureHelper(rdr io.Reader, target string) error {
   891  	return extractTarInsecureHelperPWL(rdr, target, nil)
   892  }
   893  
   894  func extractTarInsecureHelperPWL(rdr io.Reader, target string, pwl PathWhitelistMap) error {
   895  	editor, err := NewUidShiftingFilePermEditor(user.NewBlankUidRange())
   896  	if err != nil {
   897  		return err
   898  	}
   899  	return ExtractTarInsecure(tar.NewReader(rdr), target, true, pwl, editor)
   900  }