Skip to content

Commit af9cf9e

Browse files
g.lendarminnich
authored andcommitted
split tests so some can run on Plan 9
Move common variables and functions into common.go Check for root containing .. on Plan 9 Signed-off-by: Ronald G Minnich <[email protected]>
1 parent 2ee2c46 commit af9cf9e

File tree

5 files changed

+333
-295
lines changed

5 files changed

+333
-295
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: 0 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
package securejoin
99

1010
import (
11-
"errors"
1211
"os"
1312
"path/filepath"
1413
"strings"
@@ -17,40 +16,6 @@ import (
1716

1817
const maxSymlinkLimit = 255
1918

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

join_plan9.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,14 @@ import "path/filepath"
99

1010
// SecureJoin is equivalent to filepath.Join, as plan9 doesn't have symlinks.
1111
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+
1220
unsafePath = filepath.Join(string(filepath.Separator), unsafePath)
1321
return filepath.Join(root, unsafePath), nil
1422
}

0 commit comments

Comments
 (0)