github.com/dougm/docker@v1.5.0/pkg/symlink/fs.go (about) 1 // Copyright 2012 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE.BSD file. 4 5 // This code is a modified version of path/filepath/symlink.go from the Go standard library. 6 7 package symlink 8 9 import ( 10 "bytes" 11 "errors" 12 "os" 13 "path/filepath" 14 "strings" 15 ) 16 17 // FollowSymlinkInScope is a wrapper around evalSymlinksInScope that returns an absolute path 18 func FollowSymlinkInScope(path, root string) (string, error) { 19 path, err := filepath.Abs(path) 20 if err != nil { 21 return "", err 22 } 23 root, err = filepath.Abs(root) 24 if err != nil { 25 return "", err 26 } 27 return evalSymlinksInScope(path, root) 28 } 29 30 // evalSymlinksInScope will evaluate symlinks in `path` within a scope `root` and return 31 // a result guaranteed to be contained within the scope `root`, at the time of the call. 32 // Symlinks in `root` are not evaluated and left as-is. 33 // Errors encountered while attempting to evaluate symlinks in path will be returned. 34 // Non-existing paths are valid and do not constitute an error. 35 // `path` has to contain `root` as a prefix, or else an error will be returned. 36 // Trying to break out from `root` does not constitute an error. 37 // 38 // Example: 39 // If /foo/bar -> /outside, 40 // FollowSymlinkInScope("/foo/bar", "/foo") == "/foo/outside" instead of "/oustide" 41 // 42 // IMPORTANT: it is the caller's responsibility to call evalSymlinksInScope *after* relevant symlinks 43 // are created and not to create subsequently, additional symlinks that could potentially make a 44 // previously-safe path, unsafe. Example: if /foo/bar does not exist, evalSymlinksInScope("/foo/bar", "/foo") 45 // would return "/foo/bar". If one makes /foo/bar a symlink to /baz subsequently, then "/foo/bar" should 46 // no longer be considered safely contained in "/foo". 47 func evalSymlinksInScope(path, root string) (string, error) { 48 root = filepath.Clean(root) 49 if path == root { 50 return path, nil 51 } 52 if !strings.HasPrefix(path, root) { 53 return "", errors.New("evalSymlinksInScope: " + path + " is not in " + root) 54 } 55 const maxIter = 255 56 originalPath := path 57 // given root of "/a" and path of "/a/b/../../c" we want path to be "/b/../../c" 58 path = path[len(root):] 59 if root == string(filepath.Separator) { 60 path = string(filepath.Separator) + path 61 } 62 if !strings.HasPrefix(path, string(filepath.Separator)) { 63 return "", errors.New("evalSymlinksInScope: " + path + " is not in " + root) 64 } 65 path = filepath.Clean(path) 66 // consume path by taking each frontmost path element, 67 // expanding it if it's a symlink, and appending it to b 68 var b bytes.Buffer 69 // b here will always be considered to be the "current absolute path inside 70 // root" when we append paths to it, we also append a slash and use 71 // filepath.Clean after the loop to trim the trailing slash 72 for n := 0; path != ""; n++ { 73 if n > maxIter { 74 return "", errors.New("evalSymlinksInScope: too many links in " + originalPath) 75 } 76 77 // find next path component, p 78 i := strings.IndexRune(path, filepath.Separator) 79 var p string 80 if i == -1 { 81 p, path = path, "" 82 } else { 83 p, path = path[:i], path[i+1:] 84 } 85 86 if p == "" { 87 continue 88 } 89 90 // this takes a b.String() like "b/../" and a p like "c" and turns it 91 // into "/b/../c" which then gets filepath.Cleaned into "/c" and then 92 // root gets prepended and we Clean again (to remove any trailing slash 93 // if the first Clean gave us just "/") 94 cleanP := filepath.Clean(string(filepath.Separator) + b.String() + p) 95 if cleanP == string(filepath.Separator) { 96 // never Lstat "/" itself 97 b.Reset() 98 continue 99 } 100 fullP := filepath.Clean(root + cleanP) 101 102 fi, err := os.Lstat(fullP) 103 if os.IsNotExist(err) { 104 // if p does not exist, accept it 105 b.WriteString(p) 106 b.WriteRune(filepath.Separator) 107 continue 108 } 109 if err != nil { 110 return "", err 111 } 112 if fi.Mode()&os.ModeSymlink == 0 { 113 b.WriteString(p + string(filepath.Separator)) 114 continue 115 } 116 117 // it's a symlink, put it at the front of path 118 dest, err := os.Readlink(fullP) 119 if err != nil { 120 return "", err 121 } 122 if filepath.IsAbs(dest) { 123 b.Reset() 124 } 125 path = dest + string(filepath.Separator) + path 126 } 127 128 // see note above on "fullP := ..." for why this is double-cleaned and 129 // what's happening here 130 return filepath.Clean(root + filepath.Clean(string(filepath.Separator)+b.String())), nil 131 }