|
@@ -1,407 +0,0 @@
|
|
|
-// +build !windows
|
|
|
-
|
|
|
-// Licensed under the Apache License, Version 2.0; See LICENSE.APACHE
|
|
|
-
|
|
|
-package symlink // import "github.com/docker/docker/pkg/symlink"
|
|
|
-
|
|
|
-import (
|
|
|
- "fmt"
|
|
|
- "io/ioutil"
|
|
|
- "os"
|
|
|
- "path/filepath"
|
|
|
- "testing"
|
|
|
-)
|
|
|
-
|
|
|
-// TODO Windows: This needs some serious work to port to Windows. For now,
|
|
|
-// turning off testing in this package.
|
|
|
-
|
|
|
-type dirOrLink struct {
|
|
|
- path string
|
|
|
- target string
|
|
|
-}
|
|
|
-
|
|
|
-func makeFs(tmpdir string, fs []dirOrLink) error {
|
|
|
- for _, s := range fs {
|
|
|
- s.path = filepath.Join(tmpdir, s.path)
|
|
|
- if s.target == "" {
|
|
|
- os.MkdirAll(s.path, 0755)
|
|
|
- continue
|
|
|
- }
|
|
|
- if err := os.MkdirAll(filepath.Dir(s.path), 0755); err != nil {
|
|
|
- return err
|
|
|
- }
|
|
|
- if err := os.Symlink(s.target, s.path); err != nil && !os.IsExist(err) {
|
|
|
- return err
|
|
|
- }
|
|
|
- }
|
|
|
- return nil
|
|
|
-}
|
|
|
-
|
|
|
-func testSymlink(tmpdir, path, expected, scope string) error {
|
|
|
- rewrite, err := FollowSymlinkInScope(filepath.Join(tmpdir, path), filepath.Join(tmpdir, scope))
|
|
|
- if err != nil {
|
|
|
- return err
|
|
|
- }
|
|
|
- expected, err = filepath.Abs(filepath.Join(tmpdir, expected))
|
|
|
- if err != nil {
|
|
|
- return err
|
|
|
- }
|
|
|
- if expected != rewrite {
|
|
|
- return fmt.Errorf("Expected %q got %q", expected, rewrite)
|
|
|
- }
|
|
|
- return nil
|
|
|
-}
|
|
|
-
|
|
|
-func TestFollowSymlinkAbsolute(t *testing.T) {
|
|
|
- tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkAbsolute")
|
|
|
- if err != nil {
|
|
|
- t.Fatal(err)
|
|
|
- }
|
|
|
- defer os.RemoveAll(tmpdir)
|
|
|
- if err := makeFs(tmpdir, []dirOrLink{{path: "testdata/fs/a/d", target: "/b"}}); err != nil {
|
|
|
- t.Fatal(err)
|
|
|
- }
|
|
|
- if err := testSymlink(tmpdir, "testdata/fs/a/d/c/data", "testdata/b/c/data", "testdata"); err != nil {
|
|
|
- t.Fatal(err)
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-func TestFollowSymlinkRelativePath(t *testing.T) {
|
|
|
- tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkRelativePath")
|
|
|
- if err != nil {
|
|
|
- t.Fatal(err)
|
|
|
- }
|
|
|
- defer os.RemoveAll(tmpdir)
|
|
|
- if err := makeFs(tmpdir, []dirOrLink{{path: "testdata/fs/i", target: "a"}}); err != nil {
|
|
|
- t.Fatal(err)
|
|
|
- }
|
|
|
- if err := testSymlink(tmpdir, "testdata/fs/i", "testdata/fs/a", "testdata"); err != nil {
|
|
|
- t.Fatal(err)
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-func TestFollowSymlinkSkipSymlinksOutsideScope(t *testing.T) {
|
|
|
- tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkSkipSymlinksOutsideScope")
|
|
|
- if err != nil {
|
|
|
- t.Fatal(err)
|
|
|
- }
|
|
|
- defer os.RemoveAll(tmpdir)
|
|
|
- if err := makeFs(tmpdir, []dirOrLink{
|
|
|
- {path: "linkdir", target: "realdir"},
|
|
|
- {path: "linkdir/foo/bar"},
|
|
|
- }); err != nil {
|
|
|
- t.Fatal(err)
|
|
|
- }
|
|
|
- if err := testSymlink(tmpdir, "linkdir/foo/bar", "linkdir/foo/bar", "linkdir/foo"); err != nil {
|
|
|
- t.Fatal(err)
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-func TestFollowSymlinkInvalidScopePathPair(t *testing.T) {
|
|
|
- if _, err := FollowSymlinkInScope("toto", "testdata"); err == nil {
|
|
|
- t.Fatal("expected an error")
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-func TestFollowSymlinkLastLink(t *testing.T) {
|
|
|
- tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkLastLink")
|
|
|
- if err != nil {
|
|
|
- t.Fatal(err)
|
|
|
- }
|
|
|
- defer os.RemoveAll(tmpdir)
|
|
|
- if err := makeFs(tmpdir, []dirOrLink{{path: "testdata/fs/a/d", target: "/b"}}); err != nil {
|
|
|
- t.Fatal(err)
|
|
|
- }
|
|
|
- if err := testSymlink(tmpdir, "testdata/fs/a/d", "testdata/b", "testdata"); err != nil {
|
|
|
- t.Fatal(err)
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-func TestFollowSymlinkRelativeLinkChangeScope(t *testing.T) {
|
|
|
- tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkRelativeLinkChangeScope")
|
|
|
- if err != nil {
|
|
|
- t.Fatal(err)
|
|
|
- }
|
|
|
- defer os.RemoveAll(tmpdir)
|
|
|
- if err := makeFs(tmpdir, []dirOrLink{{path: "testdata/fs/a/e", target: "../b"}}); err != nil {
|
|
|
- t.Fatal(err)
|
|
|
- }
|
|
|
- if err := testSymlink(tmpdir, "testdata/fs/a/e/c/data", "testdata/fs/b/c/data", "testdata"); err != nil {
|
|
|
- t.Fatal(err)
|
|
|
- }
|
|
|
- // avoid letting allowing symlink e lead us to ../b
|
|
|
- // normalize to the "testdata/fs/a"
|
|
|
- if err := testSymlink(tmpdir, "testdata/fs/a/e", "testdata/fs/a/b", "testdata/fs/a"); err != nil {
|
|
|
- t.Fatal(err)
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-func TestFollowSymlinkDeepRelativeLinkChangeScope(t *testing.T) {
|
|
|
- tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkDeepRelativeLinkChangeScope")
|
|
|
- if err != nil {
|
|
|
- t.Fatal(err)
|
|
|
- }
|
|
|
- defer os.RemoveAll(tmpdir)
|
|
|
-
|
|
|
- if err := makeFs(tmpdir, []dirOrLink{{path: "testdata/fs/a/f", target: "../../../../test"}}); err != nil {
|
|
|
- t.Fatal(err)
|
|
|
- }
|
|
|
- // avoid letting symlink f lead us out of the "testdata" scope
|
|
|
- // we don't normalize because symlink f is in scope and there is no
|
|
|
- // information leak
|
|
|
- if err := testSymlink(tmpdir, "testdata/fs/a/f", "testdata/test", "testdata"); err != nil {
|
|
|
- t.Fatal(err)
|
|
|
- }
|
|
|
- // avoid letting symlink f lead us out of the "testdata/fs" scope
|
|
|
- // we don't normalize because symlink f is in scope and there is no
|
|
|
- // information leak
|
|
|
- if err := testSymlink(tmpdir, "testdata/fs/a/f", "testdata/fs/test", "testdata/fs"); err != nil {
|
|
|
- t.Fatal(err)
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-func TestFollowSymlinkRelativeLinkChain(t *testing.T) {
|
|
|
- tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkRelativeLinkChain")
|
|
|
- if err != nil {
|
|
|
- t.Fatal(err)
|
|
|
- }
|
|
|
- defer os.RemoveAll(tmpdir)
|
|
|
-
|
|
|
- // avoid letting symlink g (pointed at by symlink h) take out of scope
|
|
|
- // TODO: we should probably normalize to scope here because ../[....]/root
|
|
|
- // is out of scope and we leak information
|
|
|
- if err := makeFs(tmpdir, []dirOrLink{
|
|
|
- {path: "testdata/fs/b/h", target: "../g"},
|
|
|
- {path: "testdata/fs/g", target: "../../../../../../../../../../../../root"},
|
|
|
- }); err != nil {
|
|
|
- t.Fatal(err)
|
|
|
- }
|
|
|
- if err := testSymlink(tmpdir, "testdata/fs/b/h", "testdata/root", "testdata"); err != nil {
|
|
|
- t.Fatal(err)
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-func TestFollowSymlinkBreakoutPath(t *testing.T) {
|
|
|
- tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkBreakoutPath")
|
|
|
- if err != nil {
|
|
|
- t.Fatal(err)
|
|
|
- }
|
|
|
- defer os.RemoveAll(tmpdir)
|
|
|
-
|
|
|
- // avoid letting symlink -> ../directory/file escape from scope
|
|
|
- // normalize to "testdata/fs/j"
|
|
|
- if err := makeFs(tmpdir, []dirOrLink{{path: "testdata/fs/j/k", target: "../i/a"}}); err != nil {
|
|
|
- t.Fatal(err)
|
|
|
- }
|
|
|
- if err := testSymlink(tmpdir, "testdata/fs/j/k", "testdata/fs/j/i/a", "testdata/fs/j"); err != nil {
|
|
|
- t.Fatal(err)
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-func TestFollowSymlinkToRoot(t *testing.T) {
|
|
|
- tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkToRoot")
|
|
|
- if err != nil {
|
|
|
- t.Fatal(err)
|
|
|
- }
|
|
|
- defer os.RemoveAll(tmpdir)
|
|
|
-
|
|
|
- // make sure we don't allow escaping to /
|
|
|
- // normalize to dir
|
|
|
- if err := makeFs(tmpdir, []dirOrLink{{path: "foo", target: "/"}}); err != nil {
|
|
|
- t.Fatal(err)
|
|
|
- }
|
|
|
- if err := testSymlink(tmpdir, "foo", "", ""); err != nil {
|
|
|
- t.Fatal(err)
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-func TestFollowSymlinkSlashDotdot(t *testing.T) {
|
|
|
- tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkSlashDotdot")
|
|
|
- if err != nil {
|
|
|
- t.Fatal(err)
|
|
|
- }
|
|
|
- defer os.RemoveAll(tmpdir)
|
|
|
- tmpdir = filepath.Join(tmpdir, "dir", "subdir")
|
|
|
-
|
|
|
- // make sure we don't allow escaping to /
|
|
|
- // normalize to dir
|
|
|
- if err := makeFs(tmpdir, []dirOrLink{{path: "foo", target: "/../../"}}); err != nil {
|
|
|
- t.Fatal(err)
|
|
|
- }
|
|
|
- if err := testSymlink(tmpdir, "foo", "", ""); err != nil {
|
|
|
- t.Fatal(err)
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-func TestFollowSymlinkDotdot(t *testing.T) {
|
|
|
- tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkDotdot")
|
|
|
- if err != nil {
|
|
|
- t.Fatal(err)
|
|
|
- }
|
|
|
- defer os.RemoveAll(tmpdir)
|
|
|
- tmpdir = filepath.Join(tmpdir, "dir", "subdir")
|
|
|
-
|
|
|
- // make sure we stay in scope without leaking information
|
|
|
- // this also checks for escaping to /
|
|
|
- // normalize to dir
|
|
|
- if err := makeFs(tmpdir, []dirOrLink{{path: "foo", target: "../../"}}); err != nil {
|
|
|
- t.Fatal(err)
|
|
|
- }
|
|
|
- if err := testSymlink(tmpdir, "foo", "", ""); err != nil {
|
|
|
- t.Fatal(err)
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-func TestFollowSymlinkRelativePath2(t *testing.T) {
|
|
|
- tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkRelativePath2")
|
|
|
- if err != nil {
|
|
|
- t.Fatal(err)
|
|
|
- }
|
|
|
- defer os.RemoveAll(tmpdir)
|
|
|
-
|
|
|
- if err := makeFs(tmpdir, []dirOrLink{{path: "bar/foo", target: "baz/target"}}); err != nil {
|
|
|
- t.Fatal(err)
|
|
|
- }
|
|
|
- if err := testSymlink(tmpdir, "bar/foo", "bar/baz/target", ""); err != nil {
|
|
|
- t.Fatal(err)
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-func TestFollowSymlinkScopeLink(t *testing.T) {
|
|
|
- tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkScopeLink")
|
|
|
- if err != nil {
|
|
|
- t.Fatal(err)
|
|
|
- }
|
|
|
- defer os.RemoveAll(tmpdir)
|
|
|
-
|
|
|
- if err := makeFs(tmpdir, []dirOrLink{
|
|
|
- {path: "root2"},
|
|
|
- {path: "root", target: "root2"},
|
|
|
- {path: "root2/foo", target: "../bar"},
|
|
|
- }); err != nil {
|
|
|
- t.Fatal(err)
|
|
|
- }
|
|
|
- if err := testSymlink(tmpdir, "root/foo", "root/bar", "root"); err != nil {
|
|
|
- t.Fatal(err)
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-func TestFollowSymlinkRootScope(t *testing.T) {
|
|
|
- tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkRootScope")
|
|
|
- if err != nil {
|
|
|
- t.Fatal(err)
|
|
|
- }
|
|
|
- defer os.RemoveAll(tmpdir)
|
|
|
-
|
|
|
- expected, err := filepath.EvalSymlinks(tmpdir)
|
|
|
- if err != nil {
|
|
|
- t.Fatal(err)
|
|
|
- }
|
|
|
- rewrite, err := FollowSymlinkInScope(tmpdir, "/")
|
|
|
- if err != nil {
|
|
|
- t.Fatal(err)
|
|
|
- }
|
|
|
- if rewrite != expected {
|
|
|
- t.Fatalf("expected %q got %q", expected, rewrite)
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-func TestFollowSymlinkEmpty(t *testing.T) {
|
|
|
- res, err := FollowSymlinkInScope("", "")
|
|
|
- if err != nil {
|
|
|
- t.Fatal(err)
|
|
|
- }
|
|
|
- wd, err := os.Getwd()
|
|
|
- if err != nil {
|
|
|
- t.Fatal(err)
|
|
|
- }
|
|
|
- if res != wd {
|
|
|
- t.Fatalf("expected %q got %q", wd, res)
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-func TestFollowSymlinkCircular(t *testing.T) {
|
|
|
- tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkCircular")
|
|
|
- if err != nil {
|
|
|
- t.Fatal(err)
|
|
|
- }
|
|
|
- defer os.RemoveAll(tmpdir)
|
|
|
-
|
|
|
- if err := makeFs(tmpdir, []dirOrLink{{path: "root/foo", target: "foo"}}); err != nil {
|
|
|
- t.Fatal(err)
|
|
|
- }
|
|
|
- if err := testSymlink(tmpdir, "root/foo", "", "root"); err == nil {
|
|
|
- t.Fatal("expected an error for foo -> foo")
|
|
|
- }
|
|
|
-
|
|
|
- if err := makeFs(tmpdir, []dirOrLink{
|
|
|
- {path: "root/bar", target: "baz"},
|
|
|
- {path: "root/baz", target: "../bak"},
|
|
|
- {path: "root/bak", target: "/bar"},
|
|
|
- }); err != nil {
|
|
|
- t.Fatal(err)
|
|
|
- }
|
|
|
- if err := testSymlink(tmpdir, "root/foo", "", "root"); err == nil {
|
|
|
- t.Fatal("expected an error for bar -> baz -> bak -> bar")
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-func TestFollowSymlinkComplexChainWithTargetPathsContainingLinks(t *testing.T) {
|
|
|
- tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkComplexChainWithTargetPathsContainingLinks")
|
|
|
- if err != nil {
|
|
|
- t.Fatal(err)
|
|
|
- }
|
|
|
- defer os.RemoveAll(tmpdir)
|
|
|
-
|
|
|
- if err := makeFs(tmpdir, []dirOrLink{
|
|
|
- {path: "root2"},
|
|
|
- {path: "root", target: "root2"},
|
|
|
- {path: "root/a", target: "r/s"},
|
|
|
- {path: "root/r", target: "../root/t"},
|
|
|
- {path: "root/root/t/s/b", target: "/../u"},
|
|
|
- {path: "root/u/c", target: "."},
|
|
|
- {path: "root/u/x/y", target: "../v"},
|
|
|
- {path: "root/u/v", target: "/../w"},
|
|
|
- }); err != nil {
|
|
|
- t.Fatal(err)
|
|
|
- }
|
|
|
- if err := testSymlink(tmpdir, "root/a/b/c/x/y/z", "root/w/z", "root"); err != nil {
|
|
|
- t.Fatal(err)
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-func TestFollowSymlinkBreakoutNonExistent(t *testing.T) {
|
|
|
- tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkBreakoutNonExistent")
|
|
|
- if err != nil {
|
|
|
- t.Fatal(err)
|
|
|
- }
|
|
|
- defer os.RemoveAll(tmpdir)
|
|
|
-
|
|
|
- if err := makeFs(tmpdir, []dirOrLink{
|
|
|
- {path: "root/slash", target: "/"},
|
|
|
- {path: "root/sym", target: "/idontexist/../slash"},
|
|
|
- }); err != nil {
|
|
|
- t.Fatal(err)
|
|
|
- }
|
|
|
- if err := testSymlink(tmpdir, "root/sym/file", "root/file", "root"); err != nil {
|
|
|
- t.Fatal(err)
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-func TestFollowSymlinkNoLexicalCleaning(t *testing.T) {
|
|
|
- tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkNoLexicalCleaning")
|
|
|
- if err != nil {
|
|
|
- t.Fatal(err)
|
|
|
- }
|
|
|
- defer os.RemoveAll(tmpdir)
|
|
|
-
|
|
|
- if err := makeFs(tmpdir, []dirOrLink{
|
|
|
- {path: "root/sym", target: "/foo/bar"},
|
|
|
- {path: "root/hello", target: "/sym/../baz"},
|
|
|
- }); err != nil {
|
|
|
- t.Fatal(err)
|
|
|
- }
|
|
|
- if err := testSymlink(tmpdir, "root/hello", "root/foo/baz", "root"); err != nil {
|
|
|
- t.Fatal(err)
|
|
|
- }
|
|
|
-}
|