github.com/jandre/docker@v1.7.0/pkg/symlink/fs_test.go (about)

     1  // Licensed under the Apache License, Version 2.0; See LICENSE.APACHE
     2  
     3  package symlink
     4  
     5  import (
     6  	"fmt"
     7  	"io/ioutil"
     8  	"os"
     9  	"path/filepath"
    10  	"testing"
    11  )
    12  
    13  type dirOrLink struct {
    14  	path   string
    15  	target string
    16  }
    17  
    18  func makeFs(tmpdir string, fs []dirOrLink) error {
    19  	for _, s := range fs {
    20  		s.path = filepath.Join(tmpdir, s.path)
    21  		if s.target == "" {
    22  			os.MkdirAll(s.path, 0755)
    23  			continue
    24  		}
    25  		if err := os.MkdirAll(filepath.Dir(s.path), 0755); err != nil {
    26  			return err
    27  		}
    28  		if err := os.Symlink(s.target, s.path); err != nil && !os.IsExist(err) {
    29  			return err
    30  		}
    31  	}
    32  	return nil
    33  }
    34  
    35  func testSymlink(tmpdir, path, expected, scope string) error {
    36  	rewrite, err := FollowSymlinkInScope(filepath.Join(tmpdir, path), filepath.Join(tmpdir, scope))
    37  	if err != nil {
    38  		return err
    39  	}
    40  	expected, err = filepath.Abs(filepath.Join(tmpdir, expected))
    41  	if err != nil {
    42  		return err
    43  	}
    44  	if expected != rewrite {
    45  		return fmt.Errorf("Expected %q got %q", expected, rewrite)
    46  	}
    47  	return nil
    48  }
    49  
    50  func TestFollowSymlinkAbsolute(t *testing.T) {
    51  	tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkAbsolute")
    52  	if err != nil {
    53  		t.Fatal(err)
    54  	}
    55  	defer os.RemoveAll(tmpdir)
    56  	if err := makeFs(tmpdir, []dirOrLink{{path: "testdata/fs/a/d", target: "/b"}}); err != nil {
    57  		t.Fatal(err)
    58  	}
    59  	if err := testSymlink(tmpdir, "testdata/fs/a/d/c/data", "testdata/b/c/data", "testdata"); err != nil {
    60  		t.Fatal(err)
    61  	}
    62  }
    63  
    64  func TestFollowSymlinkRelativePath(t *testing.T) {
    65  	tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkRelativePath")
    66  	if err != nil {
    67  		t.Fatal(err)
    68  	}
    69  	defer os.RemoveAll(tmpdir)
    70  	if err := makeFs(tmpdir, []dirOrLink{{path: "testdata/fs/i", target: "a"}}); err != nil {
    71  		t.Fatal(err)
    72  	}
    73  	if err := testSymlink(tmpdir, "testdata/fs/i", "testdata/fs/a", "testdata"); err != nil {
    74  		t.Fatal(err)
    75  	}
    76  }
    77  
    78  func TestFollowSymlinkSkipSymlinksOutsideScope(t *testing.T) {
    79  	tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkSkipSymlinksOutsideScope")
    80  	if err != nil {
    81  		t.Fatal(err)
    82  	}
    83  	defer os.RemoveAll(tmpdir)
    84  	if err := makeFs(tmpdir, []dirOrLink{
    85  		{path: "linkdir", target: "realdir"},
    86  		{path: "linkdir/foo/bar"},
    87  	}); err != nil {
    88  		t.Fatal(err)
    89  	}
    90  	if err := testSymlink(tmpdir, "linkdir/foo/bar", "linkdir/foo/bar", "linkdir/foo"); err != nil {
    91  		t.Fatal(err)
    92  	}
    93  }
    94  
    95  func TestFollowSymlinkInvalidScopePathPair(t *testing.T) {
    96  	if _, err := FollowSymlinkInScope("toto", "testdata"); err == nil {
    97  		t.Fatal("expected an error")
    98  	}
    99  }
   100  
   101  func TestFollowSymlinkLastLink(t *testing.T) {
   102  	tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkLastLink")
   103  	if err != nil {
   104  		t.Fatal(err)
   105  	}
   106  	defer os.RemoveAll(tmpdir)
   107  	if err := makeFs(tmpdir, []dirOrLink{{path: "testdata/fs/a/d", target: "/b"}}); err != nil {
   108  		t.Fatal(err)
   109  	}
   110  	if err := testSymlink(tmpdir, "testdata/fs/a/d", "testdata/b", "testdata"); err != nil {
   111  		t.Fatal(err)
   112  	}
   113  }
   114  
   115  func TestFollowSymlinkRelativeLinkChangeScope(t *testing.T) {
   116  	tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkRelativeLinkChangeScope")
   117  	if err != nil {
   118  		t.Fatal(err)
   119  	}
   120  	defer os.RemoveAll(tmpdir)
   121  	if err := makeFs(tmpdir, []dirOrLink{{path: "testdata/fs/a/e", target: "../b"}}); err != nil {
   122  		t.Fatal(err)
   123  	}
   124  	if err := testSymlink(tmpdir, "testdata/fs/a/e/c/data", "testdata/fs/b/c/data", "testdata"); err != nil {
   125  		t.Fatal(err)
   126  	}
   127  	// avoid letting allowing symlink e lead us to ../b
   128  	// normalize to the "testdata/fs/a"
   129  	if err := testSymlink(tmpdir, "testdata/fs/a/e", "testdata/fs/a/b", "testdata/fs/a"); err != nil {
   130  		t.Fatal(err)
   131  	}
   132  }
   133  
   134  func TestFollowSymlinkDeepRelativeLinkChangeScope(t *testing.T) {
   135  	tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkDeepRelativeLinkChangeScope")
   136  	if err != nil {
   137  		t.Fatal(err)
   138  	}
   139  	defer os.RemoveAll(tmpdir)
   140  
   141  	if err := makeFs(tmpdir, []dirOrLink{{path: "testdata/fs/a/f", target: "../../../../test"}}); err != nil {
   142  		t.Fatal(err)
   143  	}
   144  	// avoid letting symlink f lead us out of the "testdata" scope
   145  	// we don't normalize because symlink f is in scope and there is no
   146  	// information leak
   147  	if err := testSymlink(tmpdir, "testdata/fs/a/f", "testdata/test", "testdata"); err != nil {
   148  		t.Fatal(err)
   149  	}
   150  	// avoid letting symlink f lead us out of the "testdata/fs" scope
   151  	// we don't normalize because symlink f is in scope and there is no
   152  	// information leak
   153  	if err := testSymlink(tmpdir, "testdata/fs/a/f", "testdata/fs/test", "testdata/fs"); err != nil {
   154  		t.Fatal(err)
   155  	}
   156  }
   157  
   158  func TestFollowSymlinkRelativeLinkChain(t *testing.T) {
   159  	tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkRelativeLinkChain")
   160  	if err != nil {
   161  		t.Fatal(err)
   162  	}
   163  	defer os.RemoveAll(tmpdir)
   164  
   165  	// avoid letting symlink g (pointed at by symlink h) take out of scope
   166  	// TODO: we should probably normalize to scope here because ../[....]/root
   167  	// is out of scope and we leak information
   168  	if err := makeFs(tmpdir, []dirOrLink{
   169  		{path: "testdata/fs/b/h", target: "../g"},
   170  		{path: "testdata/fs/g", target: "../../../../../../../../../../../../root"},
   171  	}); err != nil {
   172  		t.Fatal(err)
   173  	}
   174  	if err := testSymlink(tmpdir, "testdata/fs/b/h", "testdata/root", "testdata"); err != nil {
   175  		t.Fatal(err)
   176  	}
   177  }
   178  
   179  func TestFollowSymlinkBreakoutPath(t *testing.T) {
   180  	tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkBreakoutPath")
   181  	if err != nil {
   182  		t.Fatal(err)
   183  	}
   184  	defer os.RemoveAll(tmpdir)
   185  
   186  	// avoid letting symlink -> ../directory/file escape from scope
   187  	// normalize to "testdata/fs/j"
   188  	if err := makeFs(tmpdir, []dirOrLink{{path: "testdata/fs/j/k", target: "../i/a"}}); err != nil {
   189  		t.Fatal(err)
   190  	}
   191  	if err := testSymlink(tmpdir, "testdata/fs/j/k", "testdata/fs/j/i/a", "testdata/fs/j"); err != nil {
   192  		t.Fatal(err)
   193  	}
   194  }
   195  
   196  func TestFollowSymlinkToRoot(t *testing.T) {
   197  	tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkToRoot")
   198  	if err != nil {
   199  		t.Fatal(err)
   200  	}
   201  	defer os.RemoveAll(tmpdir)
   202  
   203  	// make sure we don't allow escaping to /
   204  	// normalize to dir
   205  	if err := makeFs(tmpdir, []dirOrLink{{path: "foo", target: "/"}}); err != nil {
   206  		t.Fatal(err)
   207  	}
   208  	if err := testSymlink(tmpdir, "foo", "", ""); err != nil {
   209  		t.Fatal(err)
   210  	}
   211  }
   212  
   213  func TestFollowSymlinkSlashDotdot(t *testing.T) {
   214  	tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkSlashDotdot")
   215  	if err != nil {
   216  		t.Fatal(err)
   217  	}
   218  	defer os.RemoveAll(tmpdir)
   219  	tmpdir = filepath.Join(tmpdir, "dir", "subdir")
   220  
   221  	// make sure we don't allow escaping to /
   222  	// normalize to dir
   223  	if err := makeFs(tmpdir, []dirOrLink{{path: "foo", target: "/../../"}}); err != nil {
   224  		t.Fatal(err)
   225  	}
   226  	if err := testSymlink(tmpdir, "foo", "", ""); err != nil {
   227  		t.Fatal(err)
   228  	}
   229  }
   230  
   231  func TestFollowSymlinkDotdot(t *testing.T) {
   232  	tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkDotdot")
   233  	if err != nil {
   234  		t.Fatal(err)
   235  	}
   236  	defer os.RemoveAll(tmpdir)
   237  	tmpdir = filepath.Join(tmpdir, "dir", "subdir")
   238  
   239  	// make sure we stay in scope without leaking information
   240  	// this also checks for escaping to /
   241  	// normalize to dir
   242  	if err := makeFs(tmpdir, []dirOrLink{{path: "foo", target: "../../"}}); err != nil {
   243  		t.Fatal(err)
   244  	}
   245  	if err := testSymlink(tmpdir, "foo", "", ""); err != nil {
   246  		t.Fatal(err)
   247  	}
   248  }
   249  
   250  func TestFollowSymlinkRelativePath2(t *testing.T) {
   251  	tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkRelativePath2")
   252  	if err != nil {
   253  		t.Fatal(err)
   254  	}
   255  	defer os.RemoveAll(tmpdir)
   256  
   257  	if err := makeFs(tmpdir, []dirOrLink{{path: "bar/foo", target: "baz/target"}}); err != nil {
   258  		t.Fatal(err)
   259  	}
   260  	if err := testSymlink(tmpdir, "bar/foo", "bar/baz/target", ""); err != nil {
   261  		t.Fatal(err)
   262  	}
   263  }
   264  
   265  func TestFollowSymlinkScopeLink(t *testing.T) {
   266  	tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkScopeLink")
   267  	if err != nil {
   268  		t.Fatal(err)
   269  	}
   270  	defer os.RemoveAll(tmpdir)
   271  
   272  	if err := makeFs(tmpdir, []dirOrLink{
   273  		{path: "root2"},
   274  		{path: "root", target: "root2"},
   275  		{path: "root2/foo", target: "../bar"},
   276  	}); err != nil {
   277  		t.Fatal(err)
   278  	}
   279  	if err := testSymlink(tmpdir, "root/foo", "root/bar", "root"); err != nil {
   280  		t.Fatal(err)
   281  	}
   282  }
   283  
   284  func TestFollowSymlinkRootScope(t *testing.T) {
   285  	tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkRootScope")
   286  	if err != nil {
   287  		t.Fatal(err)
   288  	}
   289  	defer os.RemoveAll(tmpdir)
   290  
   291  	expected, err := filepath.EvalSymlinks(tmpdir)
   292  	if err != nil {
   293  		t.Fatal(err)
   294  	}
   295  	rewrite, err := FollowSymlinkInScope(tmpdir, "/")
   296  	if err != nil {
   297  		t.Fatal(err)
   298  	}
   299  	if rewrite != expected {
   300  		t.Fatalf("expected %q got %q", expected, rewrite)
   301  	}
   302  }
   303  
   304  func TestFollowSymlinkEmpty(t *testing.T) {
   305  	res, err := FollowSymlinkInScope("", "")
   306  	if err != nil {
   307  		t.Fatal(err)
   308  	}
   309  	wd, err := os.Getwd()
   310  	if err != nil {
   311  		t.Fatal(err)
   312  	}
   313  	if res != wd {
   314  		t.Fatalf("expected %q got %q", wd, res)
   315  	}
   316  }
   317  
   318  func TestFollowSymlinkCircular(t *testing.T) {
   319  	tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkCircular")
   320  	if err != nil {
   321  		t.Fatal(err)
   322  	}
   323  	defer os.RemoveAll(tmpdir)
   324  
   325  	if err := makeFs(tmpdir, []dirOrLink{{path: "root/foo", target: "foo"}}); err != nil {
   326  		t.Fatal(err)
   327  	}
   328  	if err := testSymlink(tmpdir, "root/foo", "", "root"); err == nil {
   329  		t.Fatal("expected an error for foo -> foo")
   330  	}
   331  
   332  	if err := makeFs(tmpdir, []dirOrLink{
   333  		{path: "root/bar", target: "baz"},
   334  		{path: "root/baz", target: "../bak"},
   335  		{path: "root/bak", target: "/bar"},
   336  	}); err != nil {
   337  		t.Fatal(err)
   338  	}
   339  	if err := testSymlink(tmpdir, "root/foo", "", "root"); err == nil {
   340  		t.Fatal("expected an error for bar -> baz -> bak -> bar")
   341  	}
   342  }
   343  
   344  func TestFollowSymlinkComplexChainWithTargetPathsContainingLinks(t *testing.T) {
   345  	tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkComplexChainWithTargetPathsContainingLinks")
   346  	if err != nil {
   347  		t.Fatal(err)
   348  	}
   349  	defer os.RemoveAll(tmpdir)
   350  
   351  	if err := makeFs(tmpdir, []dirOrLink{
   352  		{path: "root2"},
   353  		{path: "root", target: "root2"},
   354  		{path: "root/a", target: "r/s"},
   355  		{path: "root/r", target: "../root/t"},
   356  		{path: "root/root/t/s/b", target: "/../u"},
   357  		{path: "root/u/c", target: "."},
   358  		{path: "root/u/x/y", target: "../v"},
   359  		{path: "root/u/v", target: "/../w"},
   360  	}); err != nil {
   361  		t.Fatal(err)
   362  	}
   363  	if err := testSymlink(tmpdir, "root/a/b/c/x/y/z", "root/w/z", "root"); err != nil {
   364  		t.Fatal(err)
   365  	}
   366  }
   367  
   368  func TestFollowSymlinkBreakoutNonExistent(t *testing.T) {
   369  	tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkBreakoutNonExistent")
   370  	if err != nil {
   371  		t.Fatal(err)
   372  	}
   373  	defer os.RemoveAll(tmpdir)
   374  
   375  	if err := makeFs(tmpdir, []dirOrLink{
   376  		{path: "root/slash", target: "/"},
   377  		{path: "root/sym", target: "/idontexist/../slash"},
   378  	}); err != nil {
   379  		t.Fatal(err)
   380  	}
   381  	if err := testSymlink(tmpdir, "root/sym/file", "root/file", "root"); err != nil {
   382  		t.Fatal(err)
   383  	}
   384  }
   385  
   386  func TestFollowSymlinkNoLexicalCleaning(t *testing.T) {
   387  	tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkNoLexicalCleaning")
   388  	if err != nil {
   389  		t.Fatal(err)
   390  	}
   391  	defer os.RemoveAll(tmpdir)
   392  
   393  	if err := makeFs(tmpdir, []dirOrLink{
   394  		{path: "root/sym", target: "/foo/bar"},
   395  		{path: "root/hello", target: "/sym/../baz"},
   396  	}); err != nil {
   397  		t.Fatal(err)
   398  	}
   399  	if err := testSymlink(tmpdir, "root/hello", "root/foo/baz", "root"); err != nil {
   400  		t.Fatal(err)
   401  	}
   402  }