| package archive // import "github.com/docker/docker/pkg/archive" | |
| import ( | |
| "archive/tar" | |
| "io" | |
| "os" | |
| "path/filepath" | |
| "reflect" | |
| "testing" | |
| "github.com/docker/docker/pkg/ioutils" | |
| ) | |
| func TestApplyLayerInvalidFilenames(t *testing.T) { | |
| for i, headers := range [][]*tar.Header{ | |
| { | |
| { | |
| Name: "../victim/dotdot", | |
| Typeflag: tar.TypeReg, | |
| Mode: 0644, | |
| }, | |
| }, | |
| { | |
| { | |
| // Note the leading slash | |
| Name: "/../victim/slash-dotdot", | |
| Typeflag: tar.TypeReg, | |
| Mode: 0644, | |
| }, | |
| }, | |
| } { | |
| if err := testBreakout("applylayer", "docker-TestApplyLayerInvalidFilenames", headers); err != nil { | |
| t.Fatalf("i=%d. %v", i, err) | |
| } | |
| } | |
| } | |
| func TestApplyLayerInvalidHardlink(t *testing.T) { | |
| for i, headers := range [][]*tar.Header{ | |
| { // try reading victim/hello (../) | |
| { | |
| Name: "dotdot", | |
| Typeflag: tar.TypeLink, | |
| Linkname: "../victim/hello", | |
| Mode: 0644, | |
| }, | |
| }, | |
| { // try reading victim/hello (/../) | |
| { | |
| Name: "slash-dotdot", | |
| Typeflag: tar.TypeLink, | |
| // Note the leading slash | |
| Linkname: "/../victim/hello", | |
| Mode: 0644, | |
| }, | |
| }, | |
| { // try writing victim/file | |
| { | |
| Name: "loophole-victim", | |
| Typeflag: tar.TypeLink, | |
| Linkname: "../victim", | |
| Mode: 0755, | |
| }, | |
| { | |
| Name: "loophole-victim/file", | |
| Typeflag: tar.TypeReg, | |
| Mode: 0644, | |
| }, | |
| }, | |
| { // try reading victim/hello (hardlink, symlink) | |
| { | |
| Name: "loophole-victim", | |
| Typeflag: tar.TypeLink, | |
| Linkname: "../victim", | |
| Mode: 0755, | |
| }, | |
| { | |
| Name: "symlink", | |
| Typeflag: tar.TypeSymlink, | |
| Linkname: "loophole-victim/hello", | |
| Mode: 0644, | |
| }, | |
| }, | |
| { // Try reading victim/hello (hardlink, hardlink) | |
| { | |
| Name: "loophole-victim", | |
| Typeflag: tar.TypeLink, | |
| Linkname: "../victim", | |
| Mode: 0755, | |
| }, | |
| { | |
| Name: "hardlink", | |
| Typeflag: tar.TypeLink, | |
| Linkname: "loophole-victim/hello", | |
| Mode: 0644, | |
| }, | |
| }, | |
| { // Try removing victim directory (hardlink) | |
| { | |
| Name: "loophole-victim", | |
| Typeflag: tar.TypeLink, | |
| Linkname: "../victim", | |
| Mode: 0755, | |
| }, | |
| { | |
| Name: "loophole-victim", | |
| Typeflag: tar.TypeReg, | |
| Mode: 0644, | |
| }, | |
| }, | |
| } { | |
| if err := testBreakout("applylayer", "docker-TestApplyLayerInvalidHardlink", headers); err != nil { | |
| t.Fatalf("i=%d. %v", i, err) | |
| } | |
| } | |
| } | |
| func TestApplyLayerInvalidSymlink(t *testing.T) { | |
| for i, headers := range [][]*tar.Header{ | |
| { // try reading victim/hello (../) | |
| { | |
| Name: "dotdot", | |
| Typeflag: tar.TypeSymlink, | |
| Linkname: "../victim/hello", | |
| Mode: 0644, | |
| }, | |
| }, | |
| { // try reading victim/hello (/../) | |
| { | |
| Name: "slash-dotdot", | |
| Typeflag: tar.TypeSymlink, | |
| // Note the leading slash | |
| Linkname: "/../victim/hello", | |
| Mode: 0644, | |
| }, | |
| }, | |
| { // try writing victim/file | |
| { | |
| Name: "loophole-victim", | |
| Typeflag: tar.TypeSymlink, | |
| Linkname: "../victim", | |
| Mode: 0755, | |
| }, | |
| { | |
| Name: "loophole-victim/file", | |
| Typeflag: tar.TypeReg, | |
| Mode: 0644, | |
| }, | |
| }, | |
| { // try reading victim/hello (symlink, symlink) | |
| { | |
| Name: "loophole-victim", | |
| Typeflag: tar.TypeSymlink, | |
| Linkname: "../victim", | |
| Mode: 0755, | |
| }, | |
| { | |
| Name: "symlink", | |
| Typeflag: tar.TypeSymlink, | |
| Linkname: "loophole-victim/hello", | |
| Mode: 0644, | |
| }, | |
| }, | |
| { // try reading victim/hello (symlink, hardlink) | |
| { | |
| Name: "loophole-victim", | |
| Typeflag: tar.TypeSymlink, | |
| Linkname: "../victim", | |
| Mode: 0755, | |
| }, | |
| { | |
| Name: "hardlink", | |
| Typeflag: tar.TypeLink, | |
| Linkname: "loophole-victim/hello", | |
| Mode: 0644, | |
| }, | |
| }, | |
| { // try removing victim directory (symlink) | |
| { | |
| Name: "loophole-victim", | |
| Typeflag: tar.TypeSymlink, | |
| Linkname: "../victim", | |
| Mode: 0755, | |
| }, | |
| { | |
| Name: "loophole-victim", | |
| Typeflag: tar.TypeReg, | |
| Mode: 0644, | |
| }, | |
| }, | |
| } { | |
| if err := testBreakout("applylayer", "docker-TestApplyLayerInvalidSymlink", headers); err != nil { | |
| t.Fatalf("i=%d. %v", i, err) | |
| } | |
| } | |
| } | |
| func TestApplyLayerWhiteouts(t *testing.T) { | |
| wd, err := os.MkdirTemp("", "graphdriver-test-whiteouts") | |
| if err != nil { | |
| return | |
| } | |
| defer os.RemoveAll(wd) | |
| base := []string{ | |
| ".baz", | |
| "bar/", | |
| "bar/bax", | |
| "bar/bay/", | |
| "baz", | |
| "foo/", | |
| "foo/.abc", | |
| "foo/.bcd/", | |
| "foo/.bcd/a", | |
| "foo/cde/", | |
| "foo/cde/def", | |
| "foo/cde/efg", | |
| "foo/fgh", | |
| "foobar", | |
| } | |
| type tcase struct { | |
| change, expected []string | |
| } | |
| tcases := []tcase{ | |
| { | |
| base, | |
| base, | |
| }, | |
| { | |
| []string{ | |
| ".bay", | |
| ".wh.baz", | |
| "foo/", | |
| "foo/.bce", | |
| "foo/.wh..wh..opq", | |
| "foo/cde/", | |
| "foo/cde/efg", | |
| }, | |
| []string{ | |
| ".bay", | |
| ".baz", | |
| "bar/", | |
| "bar/bax", | |
| "bar/bay/", | |
| "foo/", | |
| "foo/.bce", | |
| "foo/cde/", | |
| "foo/cde/efg", | |
| "foobar", | |
| }, | |
| }, | |
| { | |
| []string{ | |
| ".bay", | |
| ".wh..baz", | |
| ".wh.foobar", | |
| "foo/", | |
| "foo/.abc", | |
| "foo/.wh.cde", | |
| "bar/", | |
| }, | |
| []string{ | |
| ".bay", | |
| "bar/", | |
| "bar/bax", | |
| "bar/bay/", | |
| "foo/", | |
| "foo/.abc", | |
| "foo/.bce", | |
| }, | |
| }, | |
| { | |
| []string{ | |
| ".abc", | |
| ".wh..wh..opq", | |
| "foobar", | |
| }, | |
| []string{ | |
| ".abc", | |
| "foobar", | |
| }, | |
| }, | |
| } | |
| for i, tc := range tcases { | |
| l, err := makeTestLayer(tc.change) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| _, err = UnpackLayer(wd, l, nil) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| err = l.Close() | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| paths, err := readDirContents(wd) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| if !reflect.DeepEqual(tc.expected, paths) { | |
| t.Fatalf("invalid files for layer %d: expected %q, got %q", i, tc.expected, paths) | |
| } | |
| } | |
| } | |
| func makeTestLayer(paths []string) (rc io.ReadCloser, err error) { | |
| tmpDir, err := os.MkdirTemp("", "graphdriver-test-mklayer") | |
| if err != nil { | |
| return | |
| } | |
| defer func() { | |
| if err != nil { | |
| os.RemoveAll(tmpDir) | |
| } | |
| }() | |
| for _, p := range paths { | |
| // Source files are always in Unix format. But we use filepath on | |
| // creation to be platform agnostic. | |
| if p[len(p)-1] == '/' { | |
| if err = os.MkdirAll(filepath.Join(tmpDir, p), 0700); err != nil { | |
| return | |
| } | |
| } else { | |
| if err = os.WriteFile(filepath.Join(tmpDir, p), nil, 0600); err != nil { | |
| return | |
| } | |
| } | |
| } | |
| archive, err := Tar(tmpDir, Uncompressed) | |
| if err != nil { | |
| return | |
| } | |
| return ioutils.NewReadCloserWrapper(archive, func() error { | |
| err := archive.Close() | |
| os.RemoveAll(tmpDir) | |
| return err | |
| }), nil | |
| } | |
| func readDirContents(root string) ([]string, error) { | |
| var files []string | |
| err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { | |
| if err != nil { | |
| return err | |
| } | |
| if path == root { | |
| return nil | |
| } | |
| rel, err := filepath.Rel(root, path) | |
| if err != nil { | |
| return err | |
| } | |
| if info.IsDir() { | |
| rel = rel + string(filepath.Separator) | |
| } | |
| // Append in Unix semantics | |
| files = append(files, filepath.ToSlash(rel)) | |
| return nil | |
| }) | |
| if err != nil { | |
| return nil, err | |
| } | |
| return files, nil | |
| } |