github.com/dougm/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 }