github.com/demonoid81/moby@v0.0.0-20200517203328-62dd8e17c460/pkg/symlink/fs_unix_test.go (about)

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