fs.go 4.8 KB

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