github.com/Heebron/moby@v0.0.0-20221111184709-6eab4f55faf7/pkg/archive/diff_test.go (about) 1 package archive // import "github.com/docker/docker/pkg/archive" 2 3 import ( 4 "archive/tar" 5 "io" 6 "os" 7 "path/filepath" 8 "reflect" 9 "testing" 10 11 "github.com/docker/docker/pkg/ioutils" 12 ) 13 14 func TestApplyLayerInvalidFilenames(t *testing.T) { 15 for i, headers := range [][]*tar.Header{ 16 { 17 { 18 Name: "../victim/dotdot", 19 Typeflag: tar.TypeReg, 20 Mode: 0644, 21 }, 22 }, 23 { 24 { 25 // Note the leading slash 26 Name: "/../victim/slash-dotdot", 27 Typeflag: tar.TypeReg, 28 Mode: 0644, 29 }, 30 }, 31 } { 32 if err := testBreakout("applylayer", "docker-TestApplyLayerInvalidFilenames", headers); err != nil { 33 t.Fatalf("i=%d. %v", i, err) 34 } 35 } 36 } 37 38 func TestApplyLayerInvalidHardlink(t *testing.T) { 39 for i, headers := range [][]*tar.Header{ 40 { // try reading victim/hello (../) 41 { 42 Name: "dotdot", 43 Typeflag: tar.TypeLink, 44 Linkname: "../victim/hello", 45 Mode: 0644, 46 }, 47 }, 48 { // try reading victim/hello (/../) 49 { 50 Name: "slash-dotdot", 51 Typeflag: tar.TypeLink, 52 // Note the leading slash 53 Linkname: "/../victim/hello", 54 Mode: 0644, 55 }, 56 }, 57 { // try writing victim/file 58 { 59 Name: "loophole-victim", 60 Typeflag: tar.TypeLink, 61 Linkname: "../victim", 62 Mode: 0755, 63 }, 64 { 65 Name: "loophole-victim/file", 66 Typeflag: tar.TypeReg, 67 Mode: 0644, 68 }, 69 }, 70 { // try reading victim/hello (hardlink, symlink) 71 { 72 Name: "loophole-victim", 73 Typeflag: tar.TypeLink, 74 Linkname: "../victim", 75 Mode: 0755, 76 }, 77 { 78 Name: "symlink", 79 Typeflag: tar.TypeSymlink, 80 Linkname: "loophole-victim/hello", 81 Mode: 0644, 82 }, 83 }, 84 { // Try reading victim/hello (hardlink, hardlink) 85 { 86 Name: "loophole-victim", 87 Typeflag: tar.TypeLink, 88 Linkname: "../victim", 89 Mode: 0755, 90 }, 91 { 92 Name: "hardlink", 93 Typeflag: tar.TypeLink, 94 Linkname: "loophole-victim/hello", 95 Mode: 0644, 96 }, 97 }, 98 { // Try removing victim directory (hardlink) 99 { 100 Name: "loophole-victim", 101 Typeflag: tar.TypeLink, 102 Linkname: "../victim", 103 Mode: 0755, 104 }, 105 { 106 Name: "loophole-victim", 107 Typeflag: tar.TypeReg, 108 Mode: 0644, 109 }, 110 }, 111 } { 112 if err := testBreakout("applylayer", "docker-TestApplyLayerInvalidHardlink", headers); err != nil { 113 t.Fatalf("i=%d. %v", i, err) 114 } 115 } 116 } 117 118 func TestApplyLayerInvalidSymlink(t *testing.T) { 119 for i, headers := range [][]*tar.Header{ 120 { // try reading victim/hello (../) 121 { 122 Name: "dotdot", 123 Typeflag: tar.TypeSymlink, 124 Linkname: "../victim/hello", 125 Mode: 0644, 126 }, 127 }, 128 { // try reading victim/hello (/../) 129 { 130 Name: "slash-dotdot", 131 Typeflag: tar.TypeSymlink, 132 // Note the leading slash 133 Linkname: "/../victim/hello", 134 Mode: 0644, 135 }, 136 }, 137 { // try writing victim/file 138 { 139 Name: "loophole-victim", 140 Typeflag: tar.TypeSymlink, 141 Linkname: "../victim", 142 Mode: 0755, 143 }, 144 { 145 Name: "loophole-victim/file", 146 Typeflag: tar.TypeReg, 147 Mode: 0644, 148 }, 149 }, 150 { // try reading victim/hello (symlink, symlink) 151 { 152 Name: "loophole-victim", 153 Typeflag: tar.TypeSymlink, 154 Linkname: "../victim", 155 Mode: 0755, 156 }, 157 { 158 Name: "symlink", 159 Typeflag: tar.TypeSymlink, 160 Linkname: "loophole-victim/hello", 161 Mode: 0644, 162 }, 163 }, 164 { // try reading victim/hello (symlink, hardlink) 165 { 166 Name: "loophole-victim", 167 Typeflag: tar.TypeSymlink, 168 Linkname: "../victim", 169 Mode: 0755, 170 }, 171 { 172 Name: "hardlink", 173 Typeflag: tar.TypeLink, 174 Linkname: "loophole-victim/hello", 175 Mode: 0644, 176 }, 177 }, 178 { // try removing victim directory (symlink) 179 { 180 Name: "loophole-victim", 181 Typeflag: tar.TypeSymlink, 182 Linkname: "../victim", 183 Mode: 0755, 184 }, 185 { 186 Name: "loophole-victim", 187 Typeflag: tar.TypeReg, 188 Mode: 0644, 189 }, 190 }, 191 } { 192 if err := testBreakout("applylayer", "docker-TestApplyLayerInvalidSymlink", headers); err != nil { 193 t.Fatalf("i=%d. %v", i, err) 194 } 195 } 196 } 197 198 func TestApplyLayerWhiteouts(t *testing.T) { 199 wd, err := os.MkdirTemp("", "graphdriver-test-whiteouts") 200 if err != nil { 201 return 202 } 203 defer os.RemoveAll(wd) 204 205 base := []string{ 206 ".baz", 207 "bar/", 208 "bar/bax", 209 "bar/bay/", 210 "baz", 211 "foo/", 212 "foo/.abc", 213 "foo/.bcd/", 214 "foo/.bcd/a", 215 "foo/cde/", 216 "foo/cde/def", 217 "foo/cde/efg", 218 "foo/fgh", 219 "foobar", 220 } 221 222 type tcase struct { 223 change, expected []string 224 } 225 226 tcases := []tcase{ 227 { 228 base, 229 base, 230 }, 231 { 232 []string{ 233 ".bay", 234 ".wh.baz", 235 "foo/", 236 "foo/.bce", 237 "foo/.wh..wh..opq", 238 "foo/cde/", 239 "foo/cde/efg", 240 }, 241 []string{ 242 ".bay", 243 ".baz", 244 "bar/", 245 "bar/bax", 246 "bar/bay/", 247 "foo/", 248 "foo/.bce", 249 "foo/cde/", 250 "foo/cde/efg", 251 "foobar", 252 }, 253 }, 254 { 255 []string{ 256 ".bay", 257 ".wh..baz", 258 ".wh.foobar", 259 "foo/", 260 "foo/.abc", 261 "foo/.wh.cde", 262 "bar/", 263 }, 264 []string{ 265 ".bay", 266 "bar/", 267 "bar/bax", 268 "bar/bay/", 269 "foo/", 270 "foo/.abc", 271 "foo/.bce", 272 }, 273 }, 274 { 275 []string{ 276 ".abc", 277 ".wh..wh..opq", 278 "foobar", 279 }, 280 []string{ 281 ".abc", 282 "foobar", 283 }, 284 }, 285 } 286 287 for i, tc := range tcases { 288 l, err := makeTestLayer(tc.change) 289 if err != nil { 290 t.Fatal(err) 291 } 292 293 _, err = UnpackLayer(wd, l, nil) 294 if err != nil { 295 t.Fatal(err) 296 } 297 err = l.Close() 298 if err != nil { 299 t.Fatal(err) 300 } 301 302 paths, err := readDirContents(wd) 303 if err != nil { 304 t.Fatal(err) 305 } 306 307 if !reflect.DeepEqual(tc.expected, paths) { 308 t.Fatalf("invalid files for layer %d: expected %q, got %q", i, tc.expected, paths) 309 } 310 } 311 } 312 313 func makeTestLayer(paths []string) (rc io.ReadCloser, err error) { 314 tmpDir, err := os.MkdirTemp("", "graphdriver-test-mklayer") 315 if err != nil { 316 return 317 } 318 defer func() { 319 if err != nil { 320 os.RemoveAll(tmpDir) 321 } 322 }() 323 for _, p := range paths { 324 // Source files are always in Unix format. But we use filepath on 325 // creation to be platform agnostic. 326 if p[len(p)-1] == '/' { 327 if err = os.MkdirAll(filepath.Join(tmpDir, p), 0700); err != nil { 328 return 329 } 330 } else { 331 if err = os.WriteFile(filepath.Join(tmpDir, p), nil, 0600); err != nil { 332 return 333 } 334 } 335 } 336 archive, err := Tar(tmpDir, Uncompressed) 337 if err != nil { 338 return 339 } 340 return ioutils.NewReadCloserWrapper(archive, func() error { 341 err := archive.Close() 342 os.RemoveAll(tmpDir) 343 return err 344 }), nil 345 } 346 347 func readDirContents(root string) ([]string, error) { 348 var files []string 349 err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { 350 if err != nil { 351 return err 352 } 353 if path == root { 354 return nil 355 } 356 rel, err := filepath.Rel(root, path) 357 if err != nil { 358 return err 359 } 360 if info.IsDir() { 361 rel = rel + string(filepath.Separator) 362 } 363 // Append in Unix semantics 364 files = append(files, filepath.ToSlash(rel)) 365 return nil 366 }) 367 if err != nil { 368 return nil, err 369 } 370 return files, nil 371 }