mirror of
https://github.com/gohugoio/hugo.git
synced 2026-08-24 15:28:54 +00:00
Drop symlinks in parent directories
Lstat only refrains from following the last element of a path, so a symlink in an intermediate directory was still resolved, and reads could escape the mount via e.g. resources.Get "symlinkdir/secret.txt". Walk the directories up to the mount root and reject any that is a symlink. Follow-up tocf9c8f93candf8b5fa09a.
This commit is contained in:
+2
-1
@@ -5,4 +5,5 @@ dist/
|
|||||||
public/
|
public/
|
||||||
.DS_Store
|
.DS_Store
|
||||||
cache/filecache/_gen/
|
cache/filecache/_gen/
|
||||||
.claude/
|
.claude/
|
||||||
|
dump.txt
|
||||||
@@ -15,10 +15,37 @@ package hugofs
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/gohugoio/hugo/common/herrors"
|
||||||
"github.com/spf13/afero"
|
"github.com/spf13/afero"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// hasSymlinkParent reports whether any directory between name and base is a symlink.
|
||||||
|
// Lstat only refrains from following the last element of a path, so callers checking
|
||||||
|
// name itself need this to keep a symlinked parent from escaping base.
|
||||||
|
// base itself is not checked; an empty base walks to the root of fs.
|
||||||
|
func hasSymlinkParent(fs afero.Fs, base, name string) (bool, error) {
|
||||||
|
for name != base {
|
||||||
|
parent := filepath.Dir(name)
|
||||||
|
if parent == name || parent == base || parent == "." {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
fi, err := LstatIfPossible(fs, parent)
|
||||||
|
if err != nil {
|
||||||
|
if herrors.IsNotExist(err) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if fi.Mode()&os.ModeSymlink != 0 {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
name = parent
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
// NewDropSymlinksFs returns an afero.Fs wrapper that treats symlinks as non-existing files.
|
// NewDropSymlinksFs returns an afero.Fs wrapper that treats symlinks as non-existing files.
|
||||||
func NewDropSymlinksFs(base afero.Fs) *DropSymlinksFs {
|
func NewDropSymlinksFs(base afero.Fs) *DropSymlinksFs {
|
||||||
return &DropSymlinksFs{base}
|
return &DropSymlinksFs{base}
|
||||||
@@ -48,5 +75,12 @@ func (fs *DropSymlinksFs) Stat(name string) (os.FileInfo, error) {
|
|||||||
if fi.Mode()&os.ModeSymlink != 0 {
|
if fi.Mode()&os.ModeSymlink != 0 {
|
||||||
return nil, os.ErrNotExist
|
return nil, os.ErrNotExist
|
||||||
}
|
}
|
||||||
|
symlinkParent, err := hasSymlinkParent(fs.Fs, "", name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if symlinkParent {
|
||||||
|
return nil, os.ErrNotExist
|
||||||
|
}
|
||||||
return fi, nil
|
return fi, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -729,10 +729,18 @@ func (fs *RootMappingFs) statRoot(root *RootMapping, filename string) (FileMetaI
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't allow symlinks to escape the mount.
|
// Don't allow symlinks to escape the mount, neither as the file itself
|
||||||
|
// nor as any directory between it and the mount root.
|
||||||
if fi.Mode()&os.ModeSymlink != 0 {
|
if fi.Mode()&os.ModeSymlink != 0 {
|
||||||
return nil, os.ErrNotExist
|
return nil, os.ErrNotExist
|
||||||
}
|
}
|
||||||
|
symlinkParent, err := hasSymlinkParent(fs.Fs, root.To, filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if symlinkParent {
|
||||||
|
return nil, os.ErrNotExist
|
||||||
|
}
|
||||||
|
|
||||||
var opener func() (afero.File, error)
|
var opener func() (afero.File, error)
|
||||||
if !fi.IsDir() {
|
if !fi.IsDir() {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ stdout 'modstatictok'
|
|||||||
! stdout 'modstaticsymlink'
|
! stdout 'modstaticsymlink'
|
||||||
stdout pageok
|
stdout pageok
|
||||||
! stdout pagesymlink
|
! stdout pagesymlink
|
||||||
|
! stdout rootdirfile
|
||||||
|
|
||||||
-- hugo.toml --
|
-- hugo.toml --
|
||||||
disableKinds = ["taxonomy", "term", "rss"]
|
disableKinds = ["taxonomy", "term", "rss"]
|
||||||
@@ -30,15 +31,24 @@ Read me.
|
|||||||
{{ with resources.Get "modassetsymlink.txt"}}FAIL {{ .Publish }}{{ else }}OK{{ end }}
|
{{ with resources.Get "modassetsymlink.txt"}}FAIL {{ .Publish }}{{ else }}OK{{ end }}
|
||||||
{{ with resources.GetMatch "modassetsymlink.txt"}}FAIL {{ .Publish }}{{ else }}OK{{ end }}
|
{{ with resources.GetMatch "modassetsymlink.txt"}}FAIL {{ .Publish }}{{ else }}OK{{ end }}
|
||||||
{{ with resources.GetMatch "myassets/symlinkdir/**"}}FAIL {{ .Publish }}{{ else }}OK{{ end }}
|
{{ with resources.GetMatch "myassets/symlinkdir/**"}}FAIL {{ .Publish }}{{ else }}OK{{ end }}
|
||||||
|
{{ with resources.Get "myassets/symlinkdir/rootdirfile1.txt"}}FAIL {{ .Publish }}{{ else }}OK{{ end }}
|
||||||
Page: {{ .RelPermalink }}|{{ .Content }}|
|
Page: {{ .RelPermalink }}|{{ .Content }}|
|
||||||
|
|
||||||
{{/* os template package. */}}
|
{{/* os template package. */}}
|
||||||
{{ $symFilePath := "content/pagesymlink.md" }}
|
{{ $symFilePath := "content/pagesymlink.md" }}
|
||||||
|
|
||||||
{{ with os.ReadDir "assets/myassets/symlinkdir" }}FAIL {{ len . }}{{ else }}OK{{ end }}
|
{{ with os.ReadDir "assets/myassets/symlinkdir" }}FAIL {{ len . }}{{ else }}OK{{ end }}
|
||||||
{{ with os.Stat $symFilePath }}FAIL{{ else }}OK{{ end }}
|
{{ template "check-os-path" $symFilePath }}
|
||||||
{{ with os.ReadFile $symFilePath }}FAIL{{ else }}OK{{ end }}
|
{{ $symFilePath = "assets/myassets/symlinkdir/rootdirfile1.txt" }}
|
||||||
{{ with os.FileExists $symFilePath }}FAIL{{ else }}OK{{ end }}
|
{{ template "check-os-path" $symFilePath }}
|
||||||
|
{{ $symFilePath = "assets/myassets/symlinkdir/subdir/rootdirfile3.txt" }}
|
||||||
|
{{ template "check-os-path" $symFilePath }}
|
||||||
|
|
||||||
|
{{ define "check-os-path" }}
|
||||||
|
{{ with os.Stat . }}FAIL{{ else }}OK{{ end }}
|
||||||
|
{{ with os.ReadFile . }}FAIL{{ else }}OK{{ end }}
|
||||||
|
{{ with os.FileExists . }}FAIL{{ else }}OK{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
-- content/pageok.md --
|
-- content/pageok.md --
|
||||||
-- themes/mytheme/assets/modassetok.txt --
|
-- themes/mytheme/assets/modassetok.txt --
|
||||||
@@ -53,5 +63,7 @@ My file.
|
|||||||
Rootdirfile1 content.
|
Rootdirfile1 content.
|
||||||
-- rootdir/rootdirfile2.txt --
|
-- rootdir/rootdirfile2.txt --
|
||||||
Rootdirfile2 content.
|
Rootdirfile2 content.
|
||||||
|
-- rootdir/subdir/rootdirfile3.txt --
|
||||||
|
Rootdirfile3 content.
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user