github.com/mizzy/docker@v1.5.0/pkg/archive/utils_test.go (about)

     1  package archive
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	"io/ioutil"
     8  	"os"
     9  	"path/filepath"
    10  	"time"
    11  
    12  	"github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
    13  )
    14  
    15  var testUntarFns = map[string]func(string, io.Reader) error{
    16  	"untar": func(dest string, r io.Reader) error {
    17  		return Untar(r, dest, nil)
    18  	},
    19  	"applylayer": func(dest string, r io.Reader) error {
    20  		_, err := ApplyLayer(dest, ArchiveReader(r))
    21  		return err
    22  	},
    23  }
    24  
    25  // testBreakout is a helper function that, within the provided `tmpdir` directory,
    26  // creates a `victim` folder with a generated `hello` file in it.
    27  // `untar` extracts to a directory named `dest`, the tar file created from `headers`.
    28  //
    29  // Here are the tested scenarios:
    30  // - removed `victim` folder				(write)
    31  // - removed files from `victim` folder			(write)
    32  // - new files in `victim` folder			(write)
    33  // - modified files in `victim` folder			(write)
    34  // - file in `dest` with same content as `victim/hello` (read)
    35  //
    36  // When using testBreakout make sure you cover one of the scenarios listed above.
    37  func testBreakout(untarFn string, tmpdir string, headers []*tar.Header) error {
    38  	tmpdir, err := ioutil.TempDir("", tmpdir)
    39  	if err != nil {
    40  		return err
    41  	}
    42  	defer os.RemoveAll(tmpdir)
    43  
    44  	dest := filepath.Join(tmpdir, "dest")
    45  	if err := os.Mkdir(dest, 0755); err != nil {
    46  		return err
    47  	}
    48  
    49  	victim := filepath.Join(tmpdir, "victim")
    50  	if err := os.Mkdir(victim, 0755); err != nil {
    51  		return err
    52  	}
    53  	hello := filepath.Join(victim, "hello")
    54  	helloData, err := time.Now().MarshalText()
    55  	if err != nil {
    56  		return err
    57  	}
    58  	if err := ioutil.WriteFile(hello, helloData, 0644); err != nil {
    59  		return err
    60  	}
    61  	helloStat, err := os.Stat(hello)
    62  	if err != nil {
    63  		return err
    64  	}
    65  
    66  	reader, writer := io.Pipe()
    67  	go func() {
    68  		t := tar.NewWriter(writer)
    69  		for _, hdr := range headers {
    70  			t.WriteHeader(hdr)
    71  		}
    72  		t.Close()
    73  	}()
    74  
    75  	untar := testUntarFns[untarFn]
    76  	if untar == nil {
    77  		return fmt.Errorf("could not find untar function %q in testUntarFns", untarFn)
    78  	}
    79  	if err := untar(dest, reader); err != nil {
    80  		if _, ok := err.(breakoutError); !ok {
    81  			// If untar returns an error unrelated to an archive breakout,
    82  			// then consider this an unexpected error and abort.
    83  			return err
    84  		}
    85  		// Here, untar detected the breakout.
    86  		// Let's move on verifying that indeed there was no breakout.
    87  		fmt.Printf("breakoutError: %v\n", err)
    88  	}
    89  
    90  	// Check victim folder
    91  	f, err := os.Open(victim)
    92  	if err != nil {
    93  		// codepath taken if victim folder was removed
    94  		return fmt.Errorf("archive breakout: error reading %q: %v", victim, err)
    95  	}
    96  	defer f.Close()
    97  
    98  	// Check contents of victim folder
    99  	//
   100  	// We are only interested in getting 2 files from the victim folder, because if all is well
   101  	// we expect only one result, the `hello` file. If there is a second result, it cannot
   102  	// hold the same name `hello` and we assume that a new file got created in the victim folder.
   103  	// That is enough to detect an archive breakout.
   104  	names, err := f.Readdirnames(2)
   105  	if err != nil {
   106  		// codepath taken if victim is not a folder
   107  		return fmt.Errorf("archive breakout: error reading directory content of %q: %v", victim, err)
   108  	}
   109  	for _, name := range names {
   110  		if name != "hello" {
   111  			// codepath taken if new file was created in victim folder
   112  			return fmt.Errorf("archive breakout: new file %q", name)
   113  		}
   114  	}
   115  
   116  	// Check victim/hello
   117  	f, err = os.Open(hello)
   118  	if err != nil {
   119  		// codepath taken if read permissions were removed
   120  		return fmt.Errorf("archive breakout: could not lstat %q: %v", hello, err)
   121  	}
   122  	defer f.Close()
   123  	b, err := ioutil.ReadAll(f)
   124  	if err != nil {
   125  		return err
   126  	}
   127  	fi, err := f.Stat()
   128  	if err != nil {
   129  		return err
   130  	}
   131  	if helloStat.IsDir() != fi.IsDir() ||
   132  		// TODO: cannot check for fi.ModTime() change
   133  		helloStat.Mode() != fi.Mode() ||
   134  		helloStat.Size() != fi.Size() ||
   135  		!bytes.Equal(helloData, b) {
   136  		// codepath taken if hello has been modified
   137  		return fmt.Errorf("archive breakout: file %q has been modified. Contents: expected=%q, got=%q. FileInfo: expected=%#v, got=%#v.", hello, helloData, b, helloStat, fi)
   138  	}
   139  
   140  	// Check that nothing in dest/ has the same content as victim/hello.
   141  	// Since victim/hello was generated with time.Now(), it is safe to assume
   142  	// that any file whose content matches exactly victim/hello, managed somehow
   143  	// to access victim/hello.
   144  	return filepath.Walk(dest, func(path string, info os.FileInfo, err error) error {
   145  		if info.IsDir() {
   146  			if err != nil {
   147  				// skip directory if error
   148  				return filepath.SkipDir
   149  			}
   150  			// enter directory
   151  			return nil
   152  		}
   153  		if err != nil {
   154  			// skip file if error
   155  			return nil
   156  		}
   157  		b, err := ioutil.ReadFile(path)
   158  		if err != nil {
   159  			// Houston, we have a problem. Aborting (space)walk.
   160  			return err
   161  		}
   162  		if bytes.Equal(helloData, b) {
   163  			return fmt.Errorf("archive breakout: file %q has been accessed via %q", hello, path)
   164  		}
   165  		return nil
   166  	})
   167  }