Skip to content

Commit eb33a04

Browse files
committed
Plan 9 support
Since Plan 9 does not have symlinks, these problems do not occur. Therefore, SecureJoinVFS and SecureJoin can map to filepath.Join, along with the test for rootpath containing .. Split tests so some can run on Plan 9 Move common variables and functions into common.go Signed-off-by: Ronald G Minnich <[email protected]>
1 parent 54b1a62 commit eb33a04

File tree

5 files changed

+354
-293
lines changed

5 files changed

+354
-293
lines changed

common.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Copyright (C) 2014-2015 Docker Inc & Go Authors. All rights reserved.
2+
// Copyright (C) 2017-2025 SUSE LLC. All rights reserved.
3+
// Use of this source code is governed by a BSD-style
4+
// license that can be found in the LICENSE file.
5+
6+
package securejoin
7+
8+
import (
9+
"errors"
10+
"os"
11+
"path/filepath"
12+
"strings"
13+
"syscall"
14+
)
15+
16+
// IsNotExist tells you if err is an error that implies that either the path
17+
// accessed does not exist (or path components don't exist). This is
18+
// effectively a more broad version of [os.IsNotExist].
19+
func IsNotExist(err error) bool {
20+
// Check that it's not actually an ENOTDIR, which in some cases is a more
21+
// convoluted case of ENOENT (usually involving weird paths).
22+
return errors.Is(err, os.ErrNotExist) || errors.Is(err, syscall.ENOTDIR) || errors.Is(err, syscall.ENOENT)
23+
}
24+
25+
// errUnsafeRoot is returned if the user provides SecureJoinVFS with a path
26+
// that contains ".." components.
27+
var errUnsafeRoot = errors.New("root path provided to SecureJoin contains '..' components")
28+
29+
// hasDotDot checks if the path contains ".." components in a platform-agnostic
30+
// way.
31+
func hasDotDot(path string) bool {
32+
// If we are on Windows, strip any volume letters. It turns out that
33+
// C:..\foo may (or may not) be a valid pathname and we need to handle that
34+
// leading "..".
35+
path = stripVolume(path)
36+
// Look for "/../" in the path, but we need to handle leading and trailing
37+
// ".."s by adding separators. Doing this with filepath.Separator is ugly
38+
// so just convert to Unix-style "/" first.
39+
path = filepath.ToSlash(path)
40+
return strings.Contains("/"+path+"/", "/../")
41+
}
42+
43+
// stripVolume just gets rid of the Windows volume included in a path. Based on
44+
// some godbolt tests, the Go compiler is smart enough to make this a no-op on
45+
// Linux.
46+
func stripVolume(path string) string {
47+
return path[len(filepath.VolumeName(path)):]
48+
}

join.go

Lines changed: 2 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33
// Use of this source code is governed by a BSD-style
44
// license that can be found in the LICENSE file.
55

6+
//go:build !plan9
7+
68
package securejoin
79

810
import (
9-
"errors"
1011
"os"
1112
"path/filepath"
1213
"strings"
@@ -15,40 +16,6 @@ import (
1516

1617
const maxSymlinkLimit = 255
1718

18-
// IsNotExist tells you if err is an error that implies that either the path
19-
// accessed does not exist (or path components don't exist). This is
20-
// effectively a more broad version of [os.IsNotExist].
21-
func IsNotExist(err error) bool {
22-
// Check that it's not actually an ENOTDIR, which in some cases is a more
23-
// convoluted case of ENOENT (usually involving weird paths).
24-
return errors.Is(err, os.ErrNotExist) || errors.Is(err, syscall.ENOTDIR) || errors.Is(err, syscall.ENOENT)
25-
}
26-
27-
// errUnsafeRoot is returned if the user provides SecureJoinVFS with a path
28-
// that contains ".." components.
29-
var errUnsafeRoot = errors.New("root path provided to SecureJoin contains '..' components")
30-
31-
// stripVolume just gets rid of the Windows volume included in a path. Based on
32-
// some godbolt tests, the Go compiler is smart enough to make this a no-op on
33-
// Linux.
34-
func stripVolume(path string) string {
35-
return path[len(filepath.VolumeName(path)):]
36-
}
37-
38-
// hasDotDot checks if the path contains ".." components in a platform-agnostic
39-
// way.
40-
func hasDotDot(path string) bool {
41-
// If we are on Windows, strip any volume letters. It turns out that
42-
// C:..\foo may (or may not) be a valid pathname and we need to handle that
43-
// leading "..".
44-
path = stripVolume(path)
45-
// Look for "/../" in the path, but we need to handle leading and trailing
46-
// ".."s by adding separators. Doing this with filepath.Separator is ugly
47-
// so just convert to Unix-style "/" first.
48-
path = filepath.ToSlash(path)
49-
return strings.Contains("/"+path+"/", "/../")
50-
}
51-
5219
// SecureJoinVFS joins the two given path components (similar to [filepath.Join]) except
5320
// that the returned path is guaranteed to be scoped inside the provided root
5421
// path (when evaluated). Any symbolic links in the path are evaluated with the

join_plan9.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright (C) 2014-2015 Docker Inc & Go Authors. All rights reserved.
2+
// Copyright (C) 2017-2025 SUSE LLC. All rights reserved.
3+
// Use of this source code is governed by a BSD-style
4+
// license that can be found in the LICENSE file.
5+
6+
package securejoin
7+
8+
import "path/filepath"
9+
10+
// SecureJoin is equivalent to filepath.Join, as plan9 doesn't have symlinks.
11+
func SecureJoin(root, unsafePath string) (string, error) {
12+
// The root path must not contain ".." components, otherwise when we join
13+
// the subpath we will end up with a weird path. We could work around this
14+
// in other ways but users shouldn't be giving us non-lexical root paths in
15+
// the first place.
16+
if hasDotDot(root) {
17+
return "", errUnsafeRoot
18+
}
19+
20+
unsafePath = filepath.Join(string(filepath.Separator), unsafePath)
21+
return filepath.Join(root, unsafePath), nil
22+
}
23+
24+
// SecureJoinVFS is equivalent to filepath.Join, as plan9 doesn't have symlinks.
25+
func SecureJoinVFS(root, unsafePath string, _ VFS) (string, error) {
26+
return SecureJoin(root, unsafePath)
27+
}

0 commit comments

Comments
 (0)