join.go 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. // Copyright (C) 2014-2015 Docker Inc & Go Authors. All rights reserved.
  2. // Copyright (C) 2017 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. // Package securejoin is an implementation of the hopefully-soon-to-be-included
  6. // SecureJoin helper that is meant to be part of the "path/filepath" package.
  7. // The purpose of this project is to provide a PoC implementation to make the
  8. // SecureJoin proposal (https://github.com/golang/go/issues/20126) more
  9. // tangible.
  10. package securejoin
  11. import (
  12. "bytes"
  13. "fmt"
  14. "os"
  15. "path/filepath"
  16. "strings"
  17. "syscall"
  18. "github.com/pkg/errors"
  19. )
  20. // ErrSymlinkLoop is returned by SecureJoinVFS when too many symlinks have been
  21. // evaluated in attempting to securely join the two given paths.
  22. var ErrSymlinkLoop = fmt.Errorf("SecureJoin: too many links")
  23. // IsNotExist tells you if err is an error that implies that either the path
  24. // accessed does not exist (or path components don't exist). This is
  25. // effectively a more broad version of os.IsNotExist.
  26. func IsNotExist(err error) bool {
  27. // If it's a bone-fide ENOENT just bail.
  28. if os.IsNotExist(errors.Cause(err)) {
  29. return true
  30. }
  31. // Check that it's not actually an ENOTDIR, which in some cases is a more
  32. // convoluted case of ENOENT (usually involving weird paths).
  33. var errno error
  34. switch err := errors.Cause(err).(type) {
  35. case *os.PathError:
  36. errno = err.Err
  37. case *os.LinkError:
  38. errno = err.Err
  39. case *os.SyscallError:
  40. errno = err.Err
  41. }
  42. return errno == syscall.ENOTDIR || errno == syscall.ENOENT
  43. }
  44. // SecureJoinVFS joins the two given path components (similar to Join) except
  45. // that the returned path is guaranteed to be scoped inside the provided root
  46. // path (when evaluated). Any symbolic links in the path are evaluated with the
  47. // given root treated as the root of the filesystem, similar to a chroot. The
  48. // filesystem state is evaluated through the given VFS interface (if nil, the
  49. // standard os.* family of functions are used).
  50. //
  51. // Note that the guarantees provided by this function only apply if the path
  52. // components in the returned string are not modified (in other words are not
  53. // replaced with symlinks on the filesystem) after this function has returned.
  54. // Such a symlink race is necessarily out-of-scope of SecureJoin.
  55. func SecureJoinVFS(root, unsafePath string, vfs VFS) (string, error) {
  56. // Use the os.* VFS implementation if none was specified.
  57. if vfs == nil {
  58. vfs = osVFS{}
  59. }
  60. var path bytes.Buffer
  61. n := 0
  62. for unsafePath != "" {
  63. if n > 255 {
  64. return "", ErrSymlinkLoop
  65. }
  66. // Next path component, p.
  67. i := strings.IndexRune(unsafePath, filepath.Separator)
  68. var p string
  69. if i == -1 {
  70. p, unsafePath = unsafePath, ""
  71. } else {
  72. p, unsafePath = unsafePath[:i], unsafePath[i+1:]
  73. }
  74. // Create a cleaned path, using the lexical semantics of /../a, to
  75. // create a "scoped" path component which can safely be joined to fullP
  76. // for evaluation. At this point, path.String() doesn't contain any
  77. // symlink components.
  78. cleanP := filepath.Clean(string(filepath.Separator) + path.String() + p)
  79. if cleanP == string(filepath.Separator) {
  80. path.Reset()
  81. continue
  82. }
  83. fullP := filepath.Clean(root + cleanP)
  84. // Figure out whether the path is a symlink.
  85. fi, err := vfs.Lstat(fullP)
  86. if err != nil && !IsNotExist(err) {
  87. return "", err
  88. }
  89. // Treat non-existent path components the same as non-symlinks (we
  90. // can't do any better here).
  91. if IsNotExist(err) || fi.Mode()&os.ModeSymlink == 0 {
  92. path.WriteString(p)
  93. path.WriteRune(filepath.Separator)
  94. continue
  95. }
  96. // Only increment when we actually dereference a link.
  97. n++
  98. // It's a symlink, expand it by prepending it to the yet-unparsed path.
  99. dest, err := vfs.Readlink(fullP)
  100. if err != nil {
  101. return "", err
  102. }
  103. // Absolute symlinks reset any work we've already done.
  104. if filepath.IsAbs(dest) {
  105. path.Reset()
  106. }
  107. unsafePath = dest + string(filepath.Separator) + unsafePath
  108. }
  109. // We have to clean path.String() here because it may contain '..'
  110. // components that are entirely lexical, but would be misleading otherwise.
  111. // And finally do a final clean to ensure that root is also lexically
  112. // clean.
  113. fullP := filepath.Clean(string(filepath.Separator) + path.String())
  114. return filepath.Clean(root + fullP), nil
  115. }
  116. // SecureJoin is a wrapper around SecureJoinVFS that just uses the os.* library
  117. // of functions as the VFS. If in doubt, use this function over SecureJoinVFS.
  118. func SecureJoin(root, unsafePath string) (string, error) {
  119. return SecureJoinVFS(root, unsafePath, nil)
  120. }