subpath_windows.go 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. // +build windows
  2. /*
  3. Copyright 2017 The Kubernetes Authors.
  4. Licensed under the Apache License, Version 2.0 (the "License");
  5. you may not use this file except in compliance with the License.
  6. You may obtain a copy of the License at
  7. http://www.apache.org/licenses/LICENSE-2.0
  8. Unless required by applicable law or agreed to in writing, software
  9. distributed under the License is distributed on an "AS IS" BASIS,
  10. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  11. See the License for the specific language governing permissions and
  12. limitations under the License.
  13. */
  14. package subpath
  15. import (
  16. "fmt"
  17. "os"
  18. "path/filepath"
  19. "strings"
  20. "syscall"
  21. "k8s.io/klog"
  22. "k8s.io/utils/mount"
  23. "k8s.io/utils/nsenter"
  24. )
  25. type subpath struct{}
  26. // New returns a subpath.Interface for the current system
  27. func New(mount.Interface) Interface {
  28. return &subpath{}
  29. }
  30. // NewNSEnter is to satisfy the compiler for having NewSubpathNSEnter exist for all
  31. // OS choices. however, NSEnter is only valid on Linux
  32. func NewNSEnter(mounter mount.Interface, ne *nsenter.Nsenter, rootDir string) Interface {
  33. return nil
  34. }
  35. // check whether hostPath is within volume path
  36. // this func will lock all intermediate subpath directories, need to close handle outside of this func after container started
  37. func lockAndCheckSubPath(volumePath, hostPath string) ([]uintptr, error) {
  38. if len(volumePath) == 0 || len(hostPath) == 0 {
  39. return []uintptr{}, nil
  40. }
  41. finalSubPath, err := filepath.EvalSymlinks(hostPath)
  42. if err != nil {
  43. return []uintptr{}, fmt.Errorf("cannot read link %s: %s", hostPath, err)
  44. }
  45. finalVolumePath, err := filepath.EvalSymlinks(volumePath)
  46. if err != nil {
  47. return []uintptr{}, fmt.Errorf("cannot read link %s: %s", volumePath, err)
  48. }
  49. return lockAndCheckSubPathWithoutSymlink(finalVolumePath, finalSubPath)
  50. }
  51. // lock all intermediate subPath directories and check they are all within volumePath
  52. // volumePath & subPath should not contain any symlink, otherwise it will return error
  53. func lockAndCheckSubPathWithoutSymlink(volumePath, subPath string) ([]uintptr, error) {
  54. if len(volumePath) == 0 || len(subPath) == 0 {
  55. return []uintptr{}, nil
  56. }
  57. // get relative path to volumePath
  58. relSubPath, err := filepath.Rel(volumePath, subPath)
  59. if err != nil {
  60. return []uintptr{}, fmt.Errorf("Rel(%s, %s) error: %v", volumePath, subPath, err)
  61. }
  62. if mount.StartsWithBackstep(relSubPath) {
  63. return []uintptr{}, fmt.Errorf("SubPath %q not within volume path %q", subPath, volumePath)
  64. }
  65. if relSubPath == "." {
  66. // volumePath and subPath are equal
  67. return []uintptr{}, nil
  68. }
  69. fileHandles := []uintptr{}
  70. var errorResult error
  71. currentFullPath := volumePath
  72. dirs := strings.Split(relSubPath, string(os.PathSeparator))
  73. for _, dir := range dirs {
  74. // lock intermediate subPath directory first
  75. currentFullPath = filepath.Join(currentFullPath, dir)
  76. handle, err := lockPath(currentFullPath)
  77. if err != nil {
  78. errorResult = fmt.Errorf("cannot lock path %s: %s", currentFullPath, err)
  79. break
  80. }
  81. fileHandles = append(fileHandles, handle)
  82. // make sure intermediate subPath directory does not contain symlink any more
  83. stat, err := os.Lstat(currentFullPath)
  84. if err != nil {
  85. errorResult = fmt.Errorf("Lstat(%q) error: %v", currentFullPath, err)
  86. break
  87. }
  88. if stat.Mode()&os.ModeSymlink != 0 {
  89. errorResult = fmt.Errorf("subpath %q is an unexpected symlink after EvalSymlinks", currentFullPath)
  90. break
  91. }
  92. if !mount.PathWithinBase(currentFullPath, volumePath) {
  93. errorResult = fmt.Errorf("SubPath %q not within volume path %q", currentFullPath, volumePath)
  94. break
  95. }
  96. }
  97. return fileHandles, errorResult
  98. }
  99. // unlockPath unlock directories
  100. func unlockPath(fileHandles []uintptr) {
  101. if fileHandles != nil {
  102. for _, handle := range fileHandles {
  103. syscall.CloseHandle(syscall.Handle(handle))
  104. }
  105. }
  106. }
  107. // lockPath locks a directory or symlink, return handle, exec "syscall.CloseHandle(handle)" to unlock the path
  108. func lockPath(path string) (uintptr, error) {
  109. if len(path) == 0 {
  110. return uintptr(syscall.InvalidHandle), syscall.ERROR_FILE_NOT_FOUND
  111. }
  112. pathp, err := syscall.UTF16PtrFromString(path)
  113. if err != nil {
  114. return uintptr(syscall.InvalidHandle), err
  115. }
  116. access := uint32(syscall.GENERIC_READ)
  117. sharemode := uint32(syscall.FILE_SHARE_READ)
  118. createmode := uint32(syscall.OPEN_EXISTING)
  119. flags := uint32(syscall.FILE_FLAG_BACKUP_SEMANTICS | syscall.FILE_FLAG_OPEN_REPARSE_POINT)
  120. fd, err := syscall.CreateFile(pathp, access, sharemode, nil, createmode, flags, 0)
  121. return uintptr(fd), err
  122. }
  123. // Lock all directories in subPath and check they're not symlinks.
  124. func (sp *subpath) PrepareSafeSubpath(subPath Subpath) (newHostPath string, cleanupAction func(), err error) {
  125. handles, err := lockAndCheckSubPath(subPath.VolumePath, subPath.Path)
  126. // Unlock the directories when the container starts
  127. cleanupAction = func() {
  128. unlockPath(handles)
  129. }
  130. return subPath.Path, cleanupAction, err
  131. }
  132. // No bind-mounts for subpaths are necessary on Windows
  133. func (sp *subpath) CleanSubPaths(podDir string, volumeName string) error {
  134. return nil
  135. }
  136. // SafeMakeDir makes sure that the created directory does not escape given base directory mis-using symlinks.
  137. func (sp *subpath) SafeMakeDir(subdir string, base string, perm os.FileMode) error {
  138. realBase, err := filepath.EvalSymlinks(base)
  139. if err != nil {
  140. return fmt.Errorf("error resolving symlinks in %s: %s", base, err)
  141. }
  142. realFullPath := filepath.Join(realBase, subdir)
  143. return doSafeMakeDir(realFullPath, realBase, perm)
  144. }
  145. func doSafeMakeDir(pathname string, base string, perm os.FileMode) error {
  146. klog.V(4).Infof("Creating directory %q within base %q", pathname, base)
  147. if !mount.PathWithinBase(pathname, base) {
  148. return fmt.Errorf("path %s is outside of allowed base %s", pathname, base)
  149. }
  150. // Quick check if the directory already exists
  151. s, err := os.Stat(pathname)
  152. if err == nil {
  153. // Path exists
  154. if s.IsDir() {
  155. // The directory already exists. It can be outside of the parent,
  156. // but there is no race-proof check.
  157. klog.V(4).Infof("Directory %s already exists", pathname)
  158. return nil
  159. }
  160. return &os.PathError{Op: "mkdir", Path: pathname, Err: syscall.ENOTDIR}
  161. }
  162. // Find all existing directories
  163. existingPath, toCreate, err := findExistingPrefix(base, pathname)
  164. if err != nil {
  165. return fmt.Errorf("error opening directory %s: %s", pathname, err)
  166. }
  167. if len(toCreate) == 0 {
  168. return nil
  169. }
  170. // Ensure the existing directory is inside allowed base
  171. fullExistingPath, err := filepath.EvalSymlinks(existingPath)
  172. if err != nil {
  173. return fmt.Errorf("error opening existing directory %s: %s", existingPath, err)
  174. }
  175. fullBasePath, err := filepath.EvalSymlinks(base)
  176. if err != nil {
  177. return fmt.Errorf("cannot read link %s: %s", base, err)
  178. }
  179. if !mount.PathWithinBase(fullExistingPath, fullBasePath) {
  180. return fmt.Errorf("path %s is outside of allowed base %s", fullExistingPath, err)
  181. }
  182. // lock all intermediate directories from fullBasePath to fullExistingPath (top to bottom)
  183. fileHandles, err := lockAndCheckSubPathWithoutSymlink(fullBasePath, fullExistingPath)
  184. defer unlockPath(fileHandles)
  185. if err != nil {
  186. return err
  187. }
  188. klog.V(4).Infof("%q already exists, %q to create", fullExistingPath, filepath.Join(toCreate...))
  189. currentPath := fullExistingPath
  190. // create the directories one by one, making sure nobody can change
  191. // created directory into symlink by lock that directory immediately
  192. for _, dir := range toCreate {
  193. currentPath = filepath.Join(currentPath, dir)
  194. klog.V(4).Infof("Creating %s", dir)
  195. if err := os.Mkdir(currentPath, perm); err != nil {
  196. return fmt.Errorf("cannot create directory %s: %s", currentPath, err)
  197. }
  198. handle, err := lockPath(currentPath)
  199. if err != nil {
  200. return fmt.Errorf("cannot lock path %s: %s", currentPath, err)
  201. }
  202. defer syscall.CloseHandle(syscall.Handle(handle))
  203. // make sure newly created directory does not contain symlink after lock
  204. stat, err := os.Lstat(currentPath)
  205. if err != nil {
  206. return fmt.Errorf("Lstat(%q) error: %v", currentPath, err)
  207. }
  208. if stat.Mode()&os.ModeSymlink != 0 {
  209. return fmt.Errorf("subpath %q is an unexpected symlink after Mkdir", currentPath)
  210. }
  211. }
  212. return nil
  213. }
  214. // findExistingPrefix finds prefix of pathname that exists. In addition, it
  215. // returns list of remaining directories that don't exist yet.
  216. func findExistingPrefix(base, pathname string) (string, []string, error) {
  217. rel, err := filepath.Rel(base, pathname)
  218. if err != nil {
  219. return base, nil, err
  220. }
  221. if mount.StartsWithBackstep(rel) {
  222. return base, nil, fmt.Errorf("pathname(%s) is not within base(%s)", pathname, base)
  223. }
  224. if rel == "." {
  225. // base and pathname are equal
  226. return pathname, []string{}, nil
  227. }
  228. dirs := strings.Split(rel, string(filepath.Separator))
  229. var parent string
  230. currentPath := base
  231. for i, dir := range dirs {
  232. parent = currentPath
  233. currentPath = filepath.Join(parent, dir)
  234. if _, err := os.Lstat(currentPath); err != nil {
  235. if os.IsNotExist(err) {
  236. return parent, dirs[i:], nil
  237. }
  238. return base, nil, err
  239. }
  240. }
  241. return pathname, []string{}, nil
  242. }