2014-12-05 21:58:46 +00:00
|
|
|
// Licensed under the Apache License, Version 2.0; See LICENSE.APACHE
|
|
|
|
|
2014-05-13 17:34:30 +00:00
|
|
|
package symlink
|
|
|
|
|
|
|
|
import (
|
2014-11-30 16:39:28 +00:00
|
|
|
"fmt"
|
2014-05-13 17:34:30 +00:00
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"testing"
|
|
|
|
)
|
|
|
|
|
2014-11-30 16:39:28 +00:00
|
|
|
type dirOrLink struct {
|
|
|
|
path string
|
|
|
|
target string
|
2014-05-13 17:34:30 +00:00
|
|
|
}
|
|
|
|
|
2014-11-30 16:39:28 +00:00
|
|
|
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
|
|
|
|
}
|
2014-05-13 17:34:30 +00:00
|
|
|
|
2014-11-30 16:39:28 +00:00
|
|
|
func testSymlink(tmpdir, path, expected, scope string) error {
|
|
|
|
rewrite, err := FollowSymlinkInScope(filepath.Join(tmpdir, path), filepath.Join(tmpdir, scope))
|
2014-05-13 17:34:30 +00:00
|
|
|
if err != nil {
|
2014-11-30 16:39:28 +00:00
|
|
|
return err
|
2014-05-13 17:34:30 +00:00
|
|
|
}
|
2014-11-30 16:39:28 +00:00
|
|
|
expected, err = filepath.Abs(filepath.Join(tmpdir, expected))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
2014-05-13 17:34:30 +00:00
|
|
|
}
|
2014-11-30 16:39:28 +00:00
|
|
|
if expected != rewrite {
|
|
|
|
return fmt.Errorf("Expected %q got %q", expected, rewrite)
|
|
|
|
}
|
|
|
|
return nil
|
2014-05-13 17:34:30 +00:00
|
|
|
}
|
|
|
|
|
2014-11-30 16:39:28 +00:00
|
|
|
func TestFollowSymlinkNormal(t *testing.T) {
|
|
|
|
tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkNormal")
|
2014-05-15 21:52:36 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2014-11-30 16:39:28 +00:00
|
|
|
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)
|
2014-05-15 21:52:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-11-30 16:39:28 +00:00
|
|
|
func TestFollowSymlinkRelativePath(t *testing.T) {
|
|
|
|
tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkRelativePath")
|
2014-05-13 17:34:30 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2014-11-30 16:39:28 +00:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
2014-05-13 17:34:30 +00:00
|
|
|
|
2014-11-30 16:39:28 +00:00
|
|
|
func TestFollowSymlinkUnderLinkedDir(t *testing.T) {
|
|
|
|
tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkUnderLinkedDir")
|
2014-05-13 17:34:30 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2014-11-30 16:39:28 +00:00
|
|
|
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)
|
2014-05-13 17:34:30 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-11-30 16:39:28 +00:00
|
|
|
func TestFollowSymlinkRandomString(t *testing.T) {
|
2014-05-13 17:34:30 +00:00
|
|
|
if _, err := FollowSymlinkInScope("toto", "testdata"); err == nil {
|
|
|
|
t.Fatal("Random string should fail but didn't")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-11-30 16:39:28 +00:00
|
|
|
func TestFollowSymlinkLastLink(t *testing.T) {
|
|
|
|
tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkLastLink")
|
2014-05-13 17:34:30 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2014-11-30 16:39:28 +00:00
|
|
|
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)
|
2014-05-13 17:34:30 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-11-30 16:39:28 +00:00
|
|
|
func TestFollowSymlinkRelativeLink(t *testing.T) {
|
|
|
|
tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkRelativeLink")
|
2014-05-13 17:34:30 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2014-11-30 16:39:28 +00:00
|
|
|
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)
|
2014-05-13 17:34:30 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-11-30 16:39:28 +00:00
|
|
|
func TestFollowSymlinkRelativeLinkScope(t *testing.T) {
|
|
|
|
tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkRelativeLinkScope")
|
|
|
|
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)
|
|
|
|
}
|
2014-10-28 21:18:45 +00:00
|
|
|
// 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
|
2014-11-30 16:39:28 +00:00
|
|
|
if err := testSymlink(tmpdir, "testdata/fs/a/f", "testdata/test", "testdata"); err != nil {
|
|
|
|
t.Fatal(err)
|
2014-10-28 21:18:45 +00:00
|
|
|
}
|
|
|
|
// 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
|
2014-11-30 16:39:28 +00:00
|
|
|
if err := testSymlink(tmpdir, "testdata/fs/a/f", "testdata/fs/test", "testdata/fs"); err != nil {
|
|
|
|
t.Fatal(err)
|
2014-10-28 21:18:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
2014-11-30 16:39:28 +00:00
|
|
|
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)
|
2014-10-28 21:18:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// avoid letting allowing symlink e lead us to ../b
|
|
|
|
// normalize to the "testdata/fs/a"
|
2014-11-30 16:39:28 +00:00
|
|
|
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", "testdata/fs/a/b", "testdata/fs/a"); err != nil {
|
|
|
|
t.Fatal(err)
|
2014-10-28 21:18:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// avoid letting symlink -> ../directory/file escape from scope
|
|
|
|
// normalize to "testdata/fs/j"
|
2014-11-30 16:39:28 +00:00
|
|
|
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)
|
2014-10-28 21:18:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// make sure we don't allow escaping to /
|
|
|
|
// normalize to dir
|
2014-11-30 16:39:28 +00:00
|
|
|
if err := makeFs(tmpdir, []dirOrLink{{path: "foo", target: "/"}}); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
if err := testSymlink(tmpdir, "foo", "", ""); err != nil {
|
|
|
|
t.Fatal(err)
|
2014-10-28 21:18:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// make sure we don't allow escaping to /
|
|
|
|
// normalize to dir
|
2014-11-30 16:39:28 +00:00
|
|
|
if err := makeFs(filepath.Join(tmpdir, "dir", "subdir"), []dirOrLink{{path: "foo", target: "/../../"}}); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
if err := testSymlink(tmpdir, "foo", "", ""); err != nil {
|
|
|
|
t.Fatal(err)
|
2014-10-28 21:18:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// make sure we stay in scope without leaking information
|
|
|
|
// this also checks for escaping to /
|
|
|
|
// normalize to dir
|
2014-11-30 16:39:28 +00:00
|
|
|
if err := makeFs(filepath.Join(tmpdir, "dir", "subdir"), []dirOrLink{{path: "foo", target: "../../"}}); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
if err := testSymlink(tmpdir, "foo", "", ""); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2014-10-28 21:18:45 +00:00
|
|
|
|
2014-11-30 16:39:28 +00:00
|
|
|
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)
|
2014-05-13 17:34:30 +00:00
|
|
|
}
|
|
|
|
}
|