Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit f8b5fa0

Browse files
committed
Fix prevention of direct symlink reads in resources.Get
* Note for themes, this is only an issue for themes stored locally, e.g. below `themes/...`. Themes mounted as modules from GitHub gets symlinks stripped away. * Thas was also not an issue for file reading walking one or more directories. * This is an regression introduced in `v0.123.0`.
1 parent 86fbb0f commit f8b5fa0

5 files changed

Lines changed: 90 additions & 1 deletion

File tree

hugofs/decorators.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,22 @@ func (fs *baseFileDecoratorFs) Stat(name string) (os.FileInfo, error) {
106106
return fim.(os.FileInfo), nil
107107
}
108108

109+
func (fs *baseFileDecoratorFs) LstatIfPossible(name string) (os.FileInfo, bool, error) {
110+
if lstater, ok := fs.Fs.(afero.Lstater); ok {
111+
fi, ok, err := lstater.LstatIfPossible(name)
112+
if err != nil {
113+
return nil, false, err
114+
}
115+
fim, err := fs.decorate(fi, name)
116+
if err != nil {
117+
return nil, false, err
118+
}
119+
return fim.(os.FileInfo), ok, nil
120+
}
121+
fi, err := fs.Stat(name)
122+
return fi, false, err
123+
}
124+
109125
func (fs *baseFileDecoratorFs) Open(name string) (afero.File, error) {
110126
return fs.open(name)
111127
}

hugofs/fs.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,3 +267,13 @@ func ReadDirWithContext(ctx context.Context, f DirOnlyOps, count int) ([]iofs.Di
267267
}
268268
return v, ctx, nil
269269
}
270+
271+
// LstatIfPossible tries to use LstatIfPossible if the filesystem supports it, otherwise it falls back to Stat.
272+
func LstatIfPossible(fs afero.Fs, name string) (os.FileInfo, error) {
273+
if lstater, ok := fs.(afero.Lstater); ok {
274+
fi, _, err := lstater.LstatIfPossible(name)
275+
return fi, err
276+
}
277+
fi, err := fs.Stat(name)
278+
return fi, err
279+
}

hugofs/rootmapping_fs.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -724,11 +724,16 @@ func (fs *RootMappingFs) statRoot(root *RootMapping, filename string) (FileMetaI
724724
}
725725

726726
filename = root.filename(filename)
727-
fi, err := fs.Fs.Stat(filename)
727+
fi, err := LstatIfPossible(fs.Fs, filename)
728728
if err != nil {
729729
return nil, err
730730
}
731731

732+
// Don't allow symlinks to escape the mount.
733+
if fi.Mode()&os.ModeSymlink != 0 {
734+
return nil, os.ErrNotExist
735+
}
736+
732737
var opener func() (afero.File, error)
733738
if !fi.IsDir() {
734739
// Open the file directly.

main_test.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,21 @@ var commonTestScriptsParam = testscript.Params{
237237
ts.Fatalf("failed to write file: %v", err)
238238
}
239239
},
240+
// ln creates a symlink, but throws an error on Windows.
241+
"ln": func(ts *testscript.TestScript, neg bool, args []string) {
242+
if runtime.GOOS == "windows" {
243+
ts.Fatalf("ln is not supported on Windows")
244+
}
245+
if len(args) != 2 {
246+
ts.Fatalf("usage: ln TARGET LINKNAME")
247+
}
248+
target := ts.MkAbs(args[0])
249+
linkname := ts.MkAbs(args[1])
250+
err := os.Symlink(target, linkname)
251+
if err != nil {
252+
ts.Fatalf("failed to create symlink: %v", err)
253+
}
254+
},
240255

241256
// httpget checks that a HTTP resource's body matches (if it compiles as a regexp) or contains all of the strings given as arguments.
242257
"httpget": func(ts *testscript.TestScript, neg bool, args []string) {
@@ -314,6 +329,10 @@ var commonTestScriptsParam = testscript.Params{
314329
if !ok {
315330
ts.Fatalf("stat %s: %v", filename, err)
316331
}
332+
if ok && neg {
333+
// OK.
334+
continue
335+
}
317336
if fi.Size() == 0 {
318337
ts.Fatalf("%s is empty", filename)
319338
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
[windows] skip
2+
3+
ln ./rootfile.txt ./themes/mytheme/assets/modassetsymlink.txt
4+
ln ./rootfile.txt ./themes/mytheme/static/modstaticsymlink.txt
5+
ln ./README.md ./content/pagesymlink.md
6+
7+
hugo
8+
9+
grep 'OK' public/index.html
10+
! grep 'FAIL' public/index.html
11+
12+
tree public
13+
14+
stdout modassetok
15+
! stdout modassetsymlink
16+
stdout 'modstatictok'
17+
! stdout 'modstaticsymlink'
18+
stdout pageok
19+
! stdout pagesymlink
20+
21+
-- hugo.toml --
22+
disableKinds = ["taxonomy", "term", "rss"]
23+
[[module.imports]]
24+
path = 'mytheme'
25+
-- README.md --
26+
Read me.
27+
-- layouts/all.html --
28+
{{ with resources.Get "modassetok.txt"}}OK {{ .Publish }}{{ else }}FAIL{{ end }}
29+
{{ with resources.Get "modassetsymlink.txt"}}FAIL {{ .Publish }}{{ else }}OK{{ end }}
30+
Page: {{ .RelPermalink }}|{{ .Content }}|
31+
-- content/pageok.md --
32+
-- themes/mytheme/assets/modassetok.txt --
33+
Content.
34+
-- themes/mytheme/static/modstatictok.txt --
35+
Content.
36+
-- rootfile.txt --
37+
Roo Content.
38+
39+

0 commit comments

Comments
 (0)