diff --git a/container.go b/container.go index 36330909ce..e207c3223d 100644 --- a/container.go +++ b/container.go @@ -774,14 +774,14 @@ func (container *Container) getBindMap() (map[string]BindMap, error) { } binds[path.Clean(dst)] = bindMap } - return binds, nil + return binds, nil } func (container *Container) createVolumes() error { - binds, err := container.getBindMap() - if err != nil { - return err - } + binds, err := container.getBindMap() + if err != nil { + return err + } volumesDriver := container.runtime.volumes.driver // Create the requested volumes if they don't exist for volPath := range container.Config.Volumes { @@ -824,26 +824,25 @@ func (container *Container) createVolumes() error { } container.Volumes[volPath] = srcPath container.VolumesRW[volPath] = srcRW + // Create the mountpoint - rootVolPath := path.Join(container.RootfsPath(), volPath) - if volIsDir { - if err := os.MkdirAll(rootVolPath, 0755); err != nil { - return err - } + volPath = path.Join(container.RootfsPath(), volPath) + rootVolPath, err := utils.FollowSymlinkInScope(volPath, container.RootfsPath()) + if err != nil { + panic(err) } - volPath = path.Join(container.RootfsPath(), volPath) - if _, err := os.Stat(volPath); err != nil { + if _, err := os.Stat(rootVolPath); err != nil { if os.IsNotExist(err) { if volIsDir { - if err := os.MkdirAll(volPath, 0755); err != nil { + if err := os.MkdirAll(rootVolPath, 0755); err != nil { return err } } else { - if err := os.MkdirAll(path.Dir(volPath), 0755); err != nil { + if err := os.MkdirAll(path.Dir(rootVolPath), 0755); err != nil { return err } - if f, err := os.OpenFile(volPath, os.O_CREATE, 0755); err != nil { + if f, err := os.OpenFile(rootVolPath, os.O_CREATE, 0755); err != nil { return err } else { f.Close() diff --git a/utils/fs.go b/utils/fs.go index a7bed8679a..e710926210 100644 --- a/utils/fs.go +++ b/utils/fs.go @@ -1,8 +1,10 @@ package utils import ( + "fmt" "os" "path/filepath" + "strings" "syscall" ) @@ -33,3 +35,58 @@ func TreeSize(dir string) (size int64, err error) { }) return } + +// FollowSymlink will follow an existing link and scope it to the root +// path provided. +func FollowSymlinkInScope(link, root string) (string, error) { + prev := "/" + + root, err := filepath.Abs(root) + if err != nil { + return "", err + } + + link, err = filepath.Abs(link) + if err != nil { + return "", err + } + + if !strings.HasPrefix(filepath.Dir(link), root) { + return "", fmt.Errorf("%s is not within %s", link, root) + } + + for _, p := range strings.Split(link, "/") { + prev = filepath.Join(prev, p) + prev = filepath.Clean(prev) + + for { + stat, err := os.Lstat(prev) + if err != nil { + if os.IsNotExist(err) { + break + } + return "", err + } + if stat.Mode()&os.ModeSymlink == os.ModeSymlink { + dest, err := os.Readlink(prev) + if err != nil { + return "", err + } + + switch dest[0] { + case '/': + prev = filepath.Join(root, dest) + case '.': + prev, _ = filepath.Abs(prev) + + if prev = filepath.Clean(filepath.Join(filepath.Dir(prev), dest)); len(prev) < len(root) { + prev = filepath.Join(root, filepath.Base(dest)) + } + } + } else { + break + } + } + } + return prev, nil +} diff --git a/utils/fs_test.go b/utils/fs_test.go new file mode 100644 index 0000000000..dd5d97be40 --- /dev/null +++ b/utils/fs_test.go @@ -0,0 +1,83 @@ +package utils + +import ( + "path/filepath" + "testing" +) + +func abs(t *testing.T, p string) string { + o, err := filepath.Abs(p) + if err != nil { + t.Fatal(err) + } + return o +} + +func TestFollowSymLinkNormal(t *testing.T) { + link := "testdata/fs/a/d/c/data" + + rewrite, err := FollowSymlinkInScope(link, "testdata") + if err != nil { + t.Fatal(err) + } + + if expected := abs(t, "testdata/b/c/data"); expected != rewrite { + t.Fatalf("Expected %s got %s", expected, rewrite) + } +} + +func TestFollowSymLinkRandomString(t *testing.T) { + if _, err := FollowSymlinkInScope("toto", "testdata"); err == nil { + t.Fatal("Random string should fail but didn't") + } +} + +func TestFollowSymLinkLastLink(t *testing.T) { + link := "testdata/fs/a/d" + + rewrite, err := FollowSymlinkInScope(link, "testdata") + if err != nil { + t.Fatal(err) + } + + if expected := abs(t, "testdata/b"); expected != rewrite { + t.Fatalf("Expected %s got %s", expected, rewrite) + } +} + +func TestFollowSymLinkRelativeLink(t *testing.T) { + link := "testdata/fs/a/e/c/data" + + rewrite, err := FollowSymlinkInScope(link, "testdata") + if err != nil { + t.Fatal(err) + } + + if expected := abs(t, "testdata/fs/b/c/data"); expected != rewrite { + t.Fatalf("Expected %s got %s", expected, rewrite) + } +} + +func TestFollowSymLinkRelativeLinkScope(t *testing.T) { + link := "testdata/fs/a/f" + + rewrite, err := FollowSymlinkInScope(link, "testdata") + if err != nil { + t.Fatal(err) + } + + if expected := abs(t, "testdata/test"); expected != rewrite { + t.Fatalf("Expected %s got %s", expected, rewrite) + } + + link = "testdata/fs/b/h" + + rewrite, err = FollowSymlinkInScope(link, "testdata") + if err != nil { + t.Fatal(err) + } + + if expected := abs(t, "testdata/root"); expected != rewrite { + t.Fatalf("Expected %s got %s", expected, rewrite) + } +} diff --git a/utils/testdata/fs/a/d b/utils/testdata/fs/a/d new file mode 120000 index 0000000000..28abc96048 --- /dev/null +++ b/utils/testdata/fs/a/d @@ -0,0 +1 @@ +/b \ No newline at end of file diff --git a/utils/testdata/fs/a/e b/utils/testdata/fs/a/e new file mode 120000 index 0000000000..42532fe13c --- /dev/null +++ b/utils/testdata/fs/a/e @@ -0,0 +1 @@ +../b \ No newline at end of file diff --git a/utils/testdata/fs/a/f b/utils/testdata/fs/a/f new file mode 120000 index 0000000000..21de7edc0a --- /dev/null +++ b/utils/testdata/fs/a/f @@ -0,0 +1 @@ +../../../../test \ No newline at end of file diff --git a/utils/testdata/fs/b/h b/utils/testdata/fs/b/h new file mode 120000 index 0000000000..24387a68fb --- /dev/null +++ b/utils/testdata/fs/b/h @@ -0,0 +1 @@ +../g \ No newline at end of file diff --git a/utils/testdata/fs/g b/utils/testdata/fs/g new file mode 120000 index 0000000000..0ce5de0647 --- /dev/null +++ b/utils/testdata/fs/g @@ -0,0 +1 @@ +../../../../../../../../../../../../root \ No newline at end of file