Skip to content

Commit 6894341

Browse files
committed
merge #9 into main
Paulo Gomes (1): Fix support for Windows LGTMs: cyphar
2 parents 05b6423 + c121231 commit 6894341

File tree

2 files changed

+54
-37
lines changed

2 files changed

+54
-37
lines changed

join.go

+11-1
Original file line numberDiff line numberDiff line change
@@ -39,17 +39,27 @@ func IsNotExist(err error) bool {
3939
// components in the returned string are not modified (in other words are not
4040
// replaced with symlinks on the filesystem) after this function has returned.
4141
// Such a symlink race is necessarily out-of-scope of SecureJoin.
42+
//
43+
// Volume names in unsafePath are always discarded, regardless if they are
44+
// provided via direct input or when evaluating symlinks. Therefore:
45+
//
46+
// "C:\Temp" + "D:\path\to\file.txt" results in "C:\Temp\path\to\file.txt"
4247
func SecureJoinVFS(root, unsafePath string, vfs VFS) (string, error) {
4348
// Use the os.* VFS implementation if none was specified.
4449
if vfs == nil {
4550
vfs = osVFS{}
4651
}
4752

53+
unsafePath = filepath.FromSlash(unsafePath)
4854
var path bytes.Buffer
4955
n := 0
5056
for unsafePath != "" {
5157
if n > 255 {
52-
return "", &os.PathError{Op: "SecureJoin", Path: root + "/" + unsafePath, Err: syscall.ELOOP}
58+
return "", &os.PathError{Op: "SecureJoin", Path: root + string(filepath.Separator) + unsafePath, Err: syscall.ELOOP}
59+
}
60+
61+
if v := filepath.VolumeName(unsafePath); v != "" {
62+
unsafePath = unsafePath[len(v):]
5363
}
5464

5565
// Next path component, p.

join_test.go

+43-36
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"io/ioutil"
1010
"os"
1111
"path/filepath"
12+
"runtime"
1213
"syscall"
1314
"testing"
1415
)
@@ -22,6 +23,11 @@ func symlink(t *testing.T, oldname, newname string) {
2223
}
2324
}
2425

26+
type input struct {
27+
root, unsafe string
28+
expected string
29+
}
30+
2531
// Test basic handling of symlink expansion.
2632
func TestSymlink(t *testing.T) {
2733
dir, err := ioutil.TempDir("", "TestSymlink")
@@ -38,21 +44,26 @@ func TestSymlink(t *testing.T) {
3844
symlink(t, "../../../../../../../../../../../../../etc", filepath.Join(dir, "etclink"))
3945
symlink(t, "/../../../../../../../../../../../../../etc/passwd", filepath.Join(dir, "passwd"))
4046

41-
for _, test := range []struct {
42-
root, unsafe string
43-
expected string
44-
}{
47+
rootOrVol := string(filepath.Separator)
48+
if vol := filepath.VolumeName(dir); vol != "" {
49+
rootOrVol = vol + rootOrVol
50+
}
51+
52+
tc := []input{
4553
// Make sure that expansion with a root of '/' proceeds in the expected fashion.
46-
{"/", filepath.Join(dir, "passwd"), "/etc/passwd"},
47-
{"/", filepath.Join(dir, "etclink"), "/etc"},
48-
{"/", filepath.Join(dir, "etc"), filepath.Join(dir, "somepath")},
54+
{rootOrVol, filepath.Join(dir, "passwd"), filepath.Join(rootOrVol, "etc", "passwd")},
55+
{rootOrVol, filepath.Join(dir, "etclink"), filepath.Join(rootOrVol, "etc")},
56+
57+
{rootOrVol, filepath.Join(dir, "etc"), filepath.Join(dir, "somepath")},
4958
// Now test scoped expansion.
5059
{dir, "passwd", filepath.Join(dir, "somepath", "passwd")},
5160
{dir, "etclink", filepath.Join(dir, "somepath")},
5261
{dir, "etc", filepath.Join(dir, "somepath")},
5362
{dir, "etc/test", filepath.Join(dir, "somepath", "test")},
5463
{dir, "etc/test/..", filepath.Join(dir, "somepath")},
55-
} {
64+
}
65+
66+
for _, test := range tc {
5667
got, err := SecureJoin(test.root, test.unsafe)
5768
if err != nil {
5869
t.Errorf("securejoin(%q, %q): unexpected error: %v", test.root, test.unsafe, err)
@@ -85,29 +96,31 @@ func TestNoSymlink(t *testing.T) {
8596
}
8697
defer os.RemoveAll(dir)
8798

88-
for _, test := range []struct {
89-
root, unsafe string
90-
}{
91-
// TODO: Do we need to have some conditional FromSlash handling here?
92-
{dir, "somepath"},
93-
{dir, "even/more/path"},
94-
{dir, "/this/is/a/path"},
95-
{dir, "also/a/../path/././/with/some/./.././junk"},
96-
{dir, "yetanother/../path/././/with/some/./.././junk../../../../../../../../../../../../etc/passwd"},
97-
{dir, "/../../../../../../../../../../../../../../../../etc/passwd"},
98-
{dir, "../../../../../../../../../../../../../../../../somedir"},
99-
{dir, "../../../../../../../../../../../../../../../../"},
100-
{dir, "./../../.././././../../../../../../../../../../../../../../../../etc passwd"},
101-
} {
102-
expected := filepath.Join(test.root, filepath.Clean(string(filepath.Separator)+test.unsafe))
99+
tc := []input{
100+
{dir, "somepath", filepath.Join(dir, "somepath")},
101+
{dir, "even/more/path", filepath.Join(dir, "even", "more", "path")},
102+
{dir, "/this/is/a/path", filepath.Join(dir, "this", "is", "a", "path")},
103+
{dir, "also/a/../path/././/with/some/./.././junk", filepath.Join(dir, "also", "path", "with", "junk")},
104+
{dir, "yetanother/../path/././/with/some/./.././junk../../../../../../../../../../../../etc/passwd", filepath.Join(dir, "etc", "passwd")},
105+
{dir, "/../../../../../../../../../../../../../../../../etc/passwd", filepath.Join(dir, "etc", "passwd")},
106+
{dir, "../../../../../../../../../../../../../../../../somedir", filepath.Join(dir, "somedir")},
107+
{dir, "../../../../../../../../../../../../../../../../", filepath.Join(dir)},
108+
{dir, "./../../.././././../../../../../../../../../../../../../../../../etc passwd", filepath.Join(dir, "etc passwd")},
109+
}
110+
111+
if runtime.GOOS == "windows" {
112+
tc = append(tc, []input{
113+
{dir, "d:\\etc\\test", filepath.Join(dir, "etc", "test")},
114+
}...)
115+
}
116+
117+
for _, test := range tc {
103118
got, err := SecureJoin(test.root, test.unsafe)
104119
if err != nil {
105120
t.Errorf("securejoin(%q, %q): unexpected error: %v", test.root, test.unsafe, err)
106-
continue
107121
}
108-
if got != expected {
109-
t.Errorf("securejoin(%q, %q): expected %q, got %q", test.root, test.unsafe, expected, got)
110-
continue
122+
if got != test.expected {
123+
t.Errorf("securejoin(%q, %q): expected %q, got %q", test.root, test.unsafe, test.expected, got)
111124
}
112125
}
113126
}
@@ -130,10 +143,7 @@ func TestNonLexical(t *testing.T) {
130143
symlink(t, "/../cousinparent/cousin", filepath.Join(dir, "subdir", "link2"))
131144
symlink(t, "/../../../../../../../../../../../../../../../../cousinparent/cousin", filepath.Join(dir, "subdir", "link3"))
132145

133-
for _, test := range []struct {
134-
root, unsafe string
135-
expected string
136-
}{
146+
for _, test := range []input{
137147
{dir, "subdir", filepath.Join(dir, "subdir")},
138148
{dir, "subdir/link/test", filepath.Join(dir, "cousinparent", "cousin", "test")},
139149
{dir, "subdir/link2/test", filepath.Join(dir, "cousinparent", "cousin", "test")},
@@ -188,7 +198,7 @@ func TestSymlinkLoop(t *testing.T) {
188198
} {
189199
got, err := SecureJoin(test.root, test.unsafe)
190200
if !errors.Is(err, syscall.ELOOP) {
191-
t.Errorf("securejoin(%q, %q): expected ELOOP, got %v & %q", test.root, test.unsafe, err, got)
201+
t.Errorf("securejoin(%q, %q): expected ELOOP, got %q & %v", test.root, test.unsafe, got, err)
192202
continue
193203
}
194204
}
@@ -275,10 +285,7 @@ func TestSecureJoinVFS(t *testing.T) {
275285
symlink(t, "/../cousinparent/cousin", filepath.Join(dir, "subdir", "link2"))
276286
symlink(t, "/../../../../../../../../../../../../../../../../cousinparent/cousin", filepath.Join(dir, "subdir", "link3"))
277287

278-
for _, test := range []struct {
279-
root, unsafe string
280-
expected string
281-
}{
288+
for _, test := range []input{
282289
{dir, "subdir", filepath.Join(dir, "subdir")},
283290
{dir, "subdir/link/test", filepath.Join(dir, "cousinparent", "cousin", "test")},
284291
{dir, "subdir/link2/test", filepath.Join(dir, "cousinparent", "cousin", "test")},

0 commit comments

Comments
 (0)