diff options
author | Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com> | 2019-07-25 00:12:40 +0200 |
---|---|---|
committer | Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com> | 2019-07-25 11:27:25 +0200 |
commit | e5f229974166402f51e4ee0695ffb4d1e09fa174 (patch) | |
tree | 44dc7adc4fd02cb563583afaff6ddaa781821e2f /hugofs/nosymlink_test.go | |
parent | 87a07282a2f01779e098cde0aaee1bae34dc32e6 (diff) |
Block symlink dir traversal for /static
This is in line with how it behaved before, but it was lifted a little for the project mount for Hugo Modules,
but that could create hard-to-detect loops.
Diffstat (limited to 'hugofs/nosymlink_test.go')
-rw-r--r-- | hugofs/nosymlink_test.go | 159 |
1 files changed, 104 insertions, 55 deletions
diff --git a/hugofs/nosymlink_test.go b/hugofs/nosymlink_test.go index 6d0b99dcc..5e1964419 100644 --- a/hugofs/nosymlink_test.go +++ b/hugofs/nosymlink_test.go @@ -18,6 +18,8 @@ import ( "path/filepath" "testing" + "github.com/gohugoio/hugo/common/loggers" + "github.com/gohugoio/hugo/htesting" "github.com/spf13/afero" @@ -25,73 +27,120 @@ import ( "github.com/stretchr/testify/require" ) -func TestNoSymlinkFs(t *testing.T) { - if skipSymlink() { - t.Skip("Skip; os.Symlink needs administrator rights on Windows") - } +func prepareSymlinks(t *testing.T) (string, func()) { assert := require.New(t) - workDir, clean, err := htesting.CreateTempDir(Os, "hugo-nosymlink") + + workDir, clean, err := htesting.CreateTempDir(Os, "hugo-symlink-test") assert.NoError(err) - defer clean() wd, _ := os.Getwd() - defer func() { - os.Chdir(wd) - }() blogDir := filepath.Join(workDir, "blog") - blogFile := filepath.Join(blogDir, "a.txt") - assert.NoError(os.MkdirAll(blogDir, 0777)) - afero.WriteFile(Os, filepath.Join(blogFile), []byte("content"), 0777) + blogSubDir := filepath.Join(blogDir, "sub") + assert.NoError(os.MkdirAll(blogSubDir, 0777)) + blogFile1 := filepath.Join(blogDir, "a.txt") + blogFile2 := filepath.Join(blogSubDir, "b.txt") + afero.WriteFile(Os, filepath.Join(blogFile1), []byte("content1"), 0777) + afero.WriteFile(Os, filepath.Join(blogFile2), []byte("content2"), 0777) os.Chdir(workDir) assert.NoError(os.Symlink("blog", "symlinkdedir")) os.Chdir(blogDir) + assert.NoError(os.Symlink("sub", "symsub")) assert.NoError(os.Symlink("a.txt", "symlinkdedfile.txt")) - fs := NewNoSymlinkFs(Os) - ls := fs.(afero.Lstater) - symlinkedDir := filepath.Join(workDir, "symlinkdedir") - symlinkedFile := filepath.Join(blogDir, "symlinkdedfile.txt") - - // Check Stat and Lstat - for _, stat := range []func(name string) (os.FileInfo, error){ - func(name string) (os.FileInfo, error) { - return fs.Stat(name) - }, - func(name string) (os.FileInfo, error) { - fi, _, err := ls.LstatIfPossible(name) - return fi, err - }, - } { - _, err = stat(symlinkedDir) - assert.Equal(ErrPermissionSymlink, err) - _, err = stat(symlinkedFile) - assert.Equal(ErrPermissionSymlink, err) - - fi, err := stat(filepath.Join(workDir, "blog")) - assert.NoError(err) - assert.NotNil(fi) - - fi, err = stat(blogFile) - assert.NoError(err) - assert.NotNil(fi) + return workDir, func() { + clean() + os.Chdir(wd) } +} - // Check Open - _, err = fs.Open(symlinkedDir) - assert.Equal(ErrPermissionSymlink, err) - _, err = fs.OpenFile(symlinkedDir, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0666) - assert.Equal(ErrPermissionSymlink, err) - _, err = fs.OpenFile(symlinkedFile, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0666) - assert.Equal(ErrPermissionSymlink, err) - _, err = fs.Open(symlinkedFile) - assert.Equal(ErrPermissionSymlink, err) - f, err := fs.Open(blogDir) - assert.NoError(err) - f.Close() - f, err = fs.Open(blogFile) - assert.NoError(err) - f.Close() +func TestNoSymlinkFs(t *testing.T) { + if skipSymlink() { + t.Skip("Skip; os.Symlink needs administrator rights on Windows") + } + assert := require.New(t) + workDir, clean := prepareSymlinks(t) + defer clean() + + blogDir := filepath.Join(workDir, "blog") + blogFile1 := filepath.Join(blogDir, "a.txt") + + logger := loggers.NewWarningLogger() + + for _, bfs := range []afero.Fs{NewBaseFileDecorator(Os), Os} { + for _, allowFiles := range []bool{false, true} { + logger.WarnCounter.Reset() + fs := NewNoSymlinkFs(bfs, logger, allowFiles) + ls := fs.(afero.Lstater) + symlinkedDir := filepath.Join(workDir, "symlinkdedir") + symlinkedFilename := "symlinkdedfile.txt" + symlinkedFile := filepath.Join(blogDir, symlinkedFilename) + + assertFileErr := func(err error) { + if allowFiles { + assert.NoError(err) + } else { + assert.Equal(ErrPermissionSymlink, err) + } + } - // os.OpenFile(logFile, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0666) + assertFileStat := func(name string, fi os.FileInfo, err error) { + t.Helper() + assertFileErr(err) + if err == nil { + assert.NotNil(fi) + assert.Equal(name, fi.Name()) + } + } + + // Check Stat and Lstat + for _, stat := range []func(name string) (os.FileInfo, error){ + func(name string) (os.FileInfo, error) { + return fs.Stat(name) + }, + func(name string) (os.FileInfo, error) { + fi, _, err := ls.LstatIfPossible(name) + return fi, err + }, + } { + fi, err := stat(symlinkedDir) + assert.Equal(ErrPermissionSymlink, err) + fi, err = stat(symlinkedFile) + assertFileStat(symlinkedFilename, fi, err) + + fi, err = stat(filepath.Join(workDir, "blog")) + assert.NoError(err) + assert.NotNil(fi) + + fi, err = stat(blogFile1) + assert.NoError(err) + assert.NotNil(fi) + } + + // Check Open + _, err := fs.Open(symlinkedDir) + assert.Equal(ErrPermissionSymlink, err) + _, err = fs.OpenFile(symlinkedDir, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0666) + assert.Equal(ErrPermissionSymlink, err) + _, err = fs.OpenFile(symlinkedFile, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0666) + assertFileErr(err) + _, err = fs.Open(symlinkedFile) + assertFileErr(err) + f, err := fs.Open(blogDir) + assert.NoError(err) + f.Close() + f, err = fs.Open(blogFile1) + assert.NoError(err) + f.Close() + + // Check readdir + f, err = fs.Open(workDir) + assert.NoError(err) + // There is at least one unsported symlink inside workDir + _, err = f.Readdir(-1) + f.Close() + assert.Equal(uint64(1), logger.WarnCounter.Count()) + + } + } } |