diff --git a/loader/loader.go b/loader/loader.go index 759fc9b05d..461639ed19 100644 --- a/loader/loader.go +++ b/loader/loader.go @@ -247,6 +247,10 @@ func (fl fileLoader) AsBundle(path string) (*bundle.Bundle, error) { return nil, err } + if err := checkForUNCPath(path); err != nil { + return nil, err + } + var bundleLoader bundle.DirectoryLoader var isDir bool if fl.reader != nil { @@ -254,6 +258,7 @@ func (fl fileLoader) AsBundle(path string) (*bundle.Bundle, error) { } else { bundleLoader, isDir, err = GetBundleDirectoryLoaderFS(fl.fsys, path, fl.filter) } + if err != nil { return nil, err } @@ -303,6 +308,10 @@ func GetBundleDirectoryLoaderFS(fsys fs.FS, path string, filter Filter) (bundle. return nil, false, err } + if err := checkForUNCPath(path); err != nil { + return nil, false, err + } + var fi fs.FileInfo if fsys != nil { fi, err = fs.Stat(fsys, path) @@ -663,12 +672,18 @@ func allRec(fsys fs.FS, path string, filter Filter, errors *Errors, loaded *Resu return } + if err := checkForUNCPath(path); err != nil { + errors.add(err) + return + } + var info fs.FileInfo if fsys != nil { info, err = fs.Stat(fsys, path) } else { info, err = os.Stat(path) } + if err != nil { errors.add(err) return @@ -804,3 +819,19 @@ func makeDir(path []string, x interface{}) (map[string]interface{}, bool) { } return makeDir(path[:len(path)-1], map[string]interface{}{path[len(path)-1]: x}) } + +// isUNC reports whether path is a UNC path. +func isUNC(path string) bool { + return len(path) > 1 && isSlash(path[0]) && isSlash(path[1]) +} + +func isSlash(c uint8) bool { + return c == '\\' || c == '/' +} + +func checkForUNCPath(path string) error { + if isUNC(path) { + return fmt.Errorf("UNC path read is not allowed: %s", path) + } + return nil +} diff --git a/loader/loader_test.go b/loader/loader_test.go index caa972adab..01c4d2e21e 100644 --- a/loader/loader_test.go +++ b/loader/loader_test.go @@ -8,6 +8,7 @@ import ( "bytes" "embed" "encoding/json" + "fmt" "io" "io/fs" "os" @@ -574,6 +575,61 @@ func TestAsBundleWithFile(t *testing.T) { }) } +func TestCheckForUNCPath(t *testing.T) { + cases := []struct { + input string + wantErr bool + err error + }{ + { + input: "c:/foo", + wantErr: false, + }, + { + input: "file:///c:/a/b", + wantErr: false, + }, + { + input: `\\localhost\c$`, + wantErr: true, + err: fmt.Errorf("UNC path read is not allowed: \\\\localhost\\c$"), + }, + { + input: `\\\\localhost\c$`, + wantErr: true, + err: fmt.Errorf("UNC path read is not allowed: \\\\\\\\localhost\\c$"), + }, + { + input: `//localhost/foo`, + wantErr: true, + err: fmt.Errorf("UNC path read is not allowed: //localhost/foo"), + }, + { + input: `file:///a/b/c`, + wantErr: false, + }, + } + + for _, tc := range cases { + t.Run(tc.input, func(t *testing.T) { + err := checkForUNCPath(tc.input) + if tc.wantErr { + if err == nil { + t.Fatal("Expected error but got nil") + } + + if tc.err != nil && tc.err.Error() != err.Error() { + t.Fatalf("Expected error message %v but got %v", tc.err.Error(), err.Error()) + } + } else { + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + } + }) + } +} + func TestLoadRooted(t *testing.T) { files := map[string]string{ "/foo.json": "[1,2,3]",