2
2
// Use of this source code is governed by a BSD-style
3
3
// license that can be found in the LICENSE.BSD file.
4
4
5
- // This code is a modified version of path/filepath/symlink.go from the Go standard library.
5
+ // This code is a modified version of path/filepath/symlink.go from the Go
6
+ // standard library in [docker@fa3ec89], which was based on [go1.3.3],
7
+ // with Windows implementatinos being added in [docker@9b648df].
8
+ //
9
+ // [docker@fa3ec89]: https://github.com/moby/moby/commit/fa3ec89515431ce425f924c8a9a804d5cb18382f
10
+ // [go1.3.3]: https://github.com/golang/go/blob/go1.3.3/src/pkg/path/filepath/symlink.go
11
+ // [docker@9b648df]: https://github.com/moby/moby/commit/9b648dfac6453de5944ee4bb749115d85a253a05
6
12
7
13
package symlink
8
14
@@ -14,8 +20,34 @@ import (
14
20
"strings"
15
21
)
16
22
17
- // FollowSymlinkInScope is a wrapper around evalSymlinksInScope that returns an
18
- // absolute path. This function handles paths in a platform-agnostic manner.
23
+ // FollowSymlinkInScope evaluates symbolic links in "path" within a scope "root"
24
+ // and returns a result guaranteed to be contained within the scope "root" at
25
+ // the time of the call. It returns an error of either "path" or "root" cannot
26
+ // be converted to an absolute path.
27
+ //
28
+ // Symbolic links in "root" are not evaluated and left as-is. Errors encountered
29
+ // while attempting to evaluate symlinks in path are returned, but non-existing
30
+ // paths are valid and do not constitute an error. "path" must contain "root"
31
+ // as a prefix, or else an error is returned. Trying to break out from "root"
32
+ // does not constitute an error, instead resolves the path within "root".
33
+ //
34
+ // Example:
35
+ //
36
+ // // If "/foo/bar" is a symbolic link to "/outside":
37
+ // FollowSymlinkInScope("/foo/bar", "/foo") // Returns "/foo/outside" instead of "/outside"
38
+ //
39
+ // IMPORTANT: It is the caller's responsibility to call FollowSymlinkInScope
40
+ // after relevant symbolic links are created to avoid Time-of-check Time-of-use
41
+ // (TOCTOU) race conditions ([CWE-367]). No additional symbolic links must be
42
+ // created after evaluating, as those could potentially make a previously-safe
43
+ // path unsafe.
44
+ //
45
+ // For example, if "/foo/bar" does not exist, FollowSymlinkInScope("/foo/bar", "/foo")
46
+ // evaluates the path to "/foo/bar". If one makes "/foo/bar" a symbolic link to
47
+ // "/baz" subsequently, then "/foo/bar" should no longer be considered safely
48
+ // contained in "/foo".
49
+ //
50
+ // [CWE-367]: https://cwe.mitre.org/data/definitions/367.html
19
51
func FollowSymlinkInScope (path , root string ) (string , error ) {
20
52
path , err := filepath .Abs (filepath .FromSlash (path ))
21
53
if err != nil {
@@ -28,24 +60,9 @@ func FollowSymlinkInScope(path, root string) (string, error) {
28
60
return evalSymlinksInScope (path , root )
29
61
}
30
62
31
- // evalSymlinksInScope will evaluate symlinks in `path` within a scope `root` and return
32
- // a result guaranteed to be contained within the scope `root`, at the time of the call.
33
- // Symlinks in `root` are not evaluated and left as-is.
34
- // Errors encountered while attempting to evaluate symlinks in path will be returned.
35
- // Non-existing paths are valid and do not constitute an error.
36
- // `path` has to contain `root` as a prefix, or else an error will be returned.
37
- // Trying to break out from `root` does not constitute an error.
38
- //
39
- // Example:
40
- //
41
- // If /foo/bar -> /outside,
42
- // FollowSymlinkInScope("/foo/bar", "/foo") == "/foo/outside" instead of "/outside"
43
- //
44
- // IMPORTANT: it is the caller's responsibility to call evalSymlinksInScope *after* relevant symlinks
45
- // are created and not to create subsequently, additional symlinks that could potentially make a
46
- // previously-safe path, unsafe. Example: if /foo/bar does not exist, evalSymlinksInScope("/foo/bar", "/foo")
47
- // would return "/foo/bar". If one makes /foo/bar a symlink to /baz subsequently, then "/foo/bar" should
48
- // no longer be considered safely contained in "/foo".
63
+ // evalSymlinksInScope evaluates symbolic links in "path" within a scope "root"
64
+ // and returns a result guaranteed to be contained within the scope "root" at
65
+ // the time of the call. Refer to [FollowSymlinkInScope] for details.
49
66
func evalSymlinksInScope (path , root string ) (string , error ) {
50
67
root = filepath .Clean (root )
51
68
if path == root {
@@ -133,11 +150,15 @@ func evalSymlinksInScope(path, root string) (string, error) {
133
150
return filepath .Clean (root + filepath .Clean (string (filepath .Separator )+ b .String ())), nil
134
151
}
135
152
153
+ // EvalSymlinks is a modified version of [path/filepath.EvalSymlinks] from
154
+ // the Go standard library with support for Windows long paths (paths prepended
155
+ // with "\\?\"). On non-Windows platforms, it's an alias for [path/filepath.EvalSymlinks].
156
+ //
136
157
// EvalSymlinks returns the path name after the evaluation of any symbolic
137
- // links.
138
- // If path is relative the result will be relative to the current directory,
139
- // unless one of the components is an absolute symbolic link.
140
- // This version has been updated to support long paths prepended with `\\?\` .
158
+ // links. If path is relative, the result will be relative to the current
159
+ // directory, unless one of the components is an absolute symbolic link.
160
+ //
161
+ // EvalSymlinks calls [path/filepath.Clean] on the result .
141
162
func EvalSymlinks (path string ) (string , error ) {
142
163
return evalSymlinks (path )
143
164
}
0 commit comments