1
2
3
4
19
20 package subpath
21
22 import (
23 "fmt"
24 "os"
25 "os/exec"
26 "path/filepath"
27 "strings"
28 "syscall"
29
30 "k8s.io/klog/v2"
31 "k8s.io/mount-utils"
32 "k8s.io/utils/nsenter"
33 )
34
35
36
37 const MaxPathLength = 32767
38
39 type subpath struct{}
40
41
42 func New(mount.Interface) Interface {
43 return &subpath{}
44 }
45
46
47
48 func NewNSEnter(mounter mount.Interface, ne *nsenter.Nsenter, rootDir string) Interface {
49 return nil
50 }
51
52
53 func isDriveLetterorEmptyPath(path string) bool {
54 if path == "" || strings.HasSuffix(path, ":\\\\") || strings.HasSuffix(path, ":") || strings.HasSuffix(path, ":\\") {
55 return true
56 }
57 return false
58 }
59
60
61
62 func isDeviceOrUncPath(path string) bool {
63 if strings.HasPrefix(path, "Volume") || strings.HasPrefix(path, "\\\\?\\") || strings.HasPrefix(path, "\\\\.\\") || strings.HasPrefix(path, "UNC") {
64 return true
65 }
66 return false
67 }
68
69
70 func getUpperPath(path string) string {
71 sep := fmt.Sprintf("%c", filepath.Separator)
72 upperpath := strings.TrimSuffix(path, sep)
73 return filepath.Dir(upperpath)
74 }
75
76
77
78 func isLinkPath(path string) (bool, error) {
79 cmd := exec.Command("powershell", "/c", "$ErrorActionPreference = 'Stop'; (Get-Item -Force -LiteralPath $env:linkpath).LinkType")
80 cmd.Env = append(os.Environ(), fmt.Sprintf("linkpath=%s", path))
81 klog.V(8).Infof("Executing command: %q", cmd.String())
82 output, err := cmd.CombinedOutput()
83 if err != nil {
84 return false, err
85 }
86 if strings.TrimSpace(string(output)) != "" {
87 return true, nil
88 }
89 return false, nil
90 }
91
92
93
94 func evalSymlink(path string) (string, error) {
95 path = mount.NormalizeWindowsPath(path)
96 if isDeviceOrUncPath(path) || isDriveLetterorEmptyPath(path) {
97 klog.V(4).Infof("Path '%s' is not a symlink, return its original form.", path)
98 return path, nil
99 }
100 upperpath := path
101 base := ""
102 for i := 0; i < MaxPathLength; i++ {
103 isLink, err := isLinkPath(upperpath)
104 if err != nil {
105 return "", err
106 }
107 if isLink {
108 break
109 }
110
111 base = filepath.Join(filepath.Base(upperpath), base)
112 upperpath = getUpperPath(upperpath)
113 if isDriveLetterorEmptyPath(upperpath) {
114 klog.V(4).Infof("Path '%s' is not a symlink, return its original form.", path)
115 return path, nil
116 }
117 }
118
119
120 cmd := exec.Command("powershell", "/c", "$ErrorActionPreference = 'Stop'; (Get-Item -Force -LiteralPath $env:linkpath).Target")
121 cmd.Env = append(os.Environ(), fmt.Sprintf("linkpath=%s", upperpath))
122 klog.V(8).Infof("Executing command: %q", cmd.String())
123 output, err := cmd.CombinedOutput()
124 if err != nil {
125 return "", err
126 }
127 klog.V(4).Infof("evaluate path %s: symlink from %s to %s", path, upperpath, string(output))
128 linkedPath := strings.TrimSpace(string(output))
129 if linkedPath == "" || isDeviceOrUncPath(linkedPath) {
130 klog.V(4).Infof("Path '%s' has a target %s. Return its original form.", path, linkedPath)
131 return path, nil
132 }
133
134 if !filepath.IsAbs(linkedPath) {
135 linkedPath = filepath.Join(getUpperPath(upperpath), linkedPath)
136 }
137 nextLink, err := evalSymlink(linkedPath)
138 if err != nil {
139 return path, err
140 }
141 return filepath.Join(nextLink, base), nil
142 }
143
144
145
146 func lockAndCheckSubPath(volumePath, hostPath string) ([]uintptr, error) {
147 if len(volumePath) == 0 || len(hostPath) == 0 {
148 return []uintptr{}, nil
149 }
150
151 finalSubPath, err := evalSymlink(hostPath)
152 if err != nil {
153 return []uintptr{}, fmt.Errorf("cannot evaluate link %s: %s", hostPath, err)
154 }
155
156 finalVolumePath, err := evalSymlink(volumePath)
157 if err != nil {
158 return []uintptr{}, fmt.Errorf("cannot read link %s: %s", volumePath, err)
159 }
160
161 return lockAndCheckSubPathWithoutSymlink(finalVolumePath, finalSubPath)
162 }
163
164
165
166 func lockAndCheckSubPathWithoutSymlink(volumePath, subPath string) ([]uintptr, error) {
167 if len(volumePath) == 0 || len(subPath) == 0 {
168 return []uintptr{}, nil
169 }
170
171
172 relSubPath, err := filepath.Rel(volumePath, subPath)
173 if err != nil {
174 return []uintptr{}, fmt.Errorf("Rel(%s, %s) error: %v", volumePath, subPath, err)
175 }
176 if mount.StartsWithBackstep(relSubPath) {
177 return []uintptr{}, fmt.Errorf("SubPath %q not within volume path %q", subPath, volumePath)
178 }
179
180 if relSubPath == "." {
181
182 return []uintptr{}, nil
183 }
184
185 fileHandles := []uintptr{}
186 var errorResult error
187
188 currentFullPath := volumePath
189 dirs := strings.Split(relSubPath, string(os.PathSeparator))
190 for _, dir := range dirs {
191
192 currentFullPath = filepath.Join(currentFullPath, dir)
193 handle, err := lockPath(currentFullPath)
194 if err != nil {
195 errorResult = fmt.Errorf("cannot lock path %s: %s", currentFullPath, err)
196 break
197 }
198 fileHandles = append(fileHandles, handle)
199
200
201 stat, err := os.Lstat(currentFullPath)
202 if err != nil {
203 errorResult = fmt.Errorf("Lstat(%q) error: %v", currentFullPath, err)
204 break
205 }
206 if stat.Mode()&os.ModeSymlink != 0 {
207 errorResult = fmt.Errorf("subpath %q is an unexpected symlink after EvalSymlinks", currentFullPath)
208 break
209 }
210
211 if !mount.PathWithinBase(currentFullPath, volumePath) {
212 errorResult = fmt.Errorf("SubPath %q not within volume path %q", currentFullPath, volumePath)
213 break
214 }
215 }
216
217 return fileHandles, errorResult
218 }
219
220
221 func unlockPath(fileHandles []uintptr) {
222 if fileHandles != nil {
223 for _, handle := range fileHandles {
224 syscall.CloseHandle(syscall.Handle(handle))
225 }
226 }
227 }
228
229
230 func lockPath(path string) (uintptr, error) {
231 if len(path) == 0 {
232 return uintptr(syscall.InvalidHandle), syscall.ERROR_FILE_NOT_FOUND
233 }
234 pathp, err := syscall.UTF16PtrFromString(path)
235 if err != nil {
236 return uintptr(syscall.InvalidHandle), err
237 }
238 access := uint32(syscall.GENERIC_READ)
239 sharemode := uint32(syscall.FILE_SHARE_READ)
240 createmode := uint32(syscall.OPEN_EXISTING)
241 flags := uint32(syscall.FILE_FLAG_BACKUP_SEMANTICS | syscall.FILE_FLAG_OPEN_REPARSE_POINT)
242 fd, err := syscall.CreateFile(pathp, access, sharemode, nil, createmode, flags, 0)
243 return uintptr(fd), err
244 }
245
246
247 func (sp *subpath) PrepareSafeSubpath(subPath Subpath) (newHostPath string, cleanupAction func(), err error) {
248 handles, err := lockAndCheckSubPath(subPath.VolumePath, subPath.Path)
249
250
251 cleanupAction = func() {
252 unlockPath(handles)
253 }
254 return subPath.Path, cleanupAction, err
255 }
256
257
258 func (sp *subpath) CleanSubPaths(podDir string, volumeName string) error {
259 return nil
260 }
261
262
263 func (sp *subpath) SafeMakeDir(subdir string, base string, perm os.FileMode) error {
264 realBase, err := evalSymlink(base)
265 if err != nil {
266 return fmt.Errorf("error resolving symlinks in %s: %s", base, err)
267 }
268
269 realFullPath := filepath.Join(realBase, subdir)
270 return doSafeMakeDir(realFullPath, realBase, perm)
271 }
272
273 func doSafeMakeDir(pathname string, base string, perm os.FileMode) error {
274 klog.V(4).Infof("Creating directory %q within base %q", pathname, base)
275
276 if !mount.PathWithinBase(pathname, base) {
277 return fmt.Errorf("path %s is outside of allowed base %s", pathname, base)
278 }
279
280
281 s, err := os.Stat(pathname)
282 if err == nil {
283
284 if s.IsDir() {
285
286
287 klog.V(4).Infof("Directory %s already exists", pathname)
288 return nil
289 }
290 return &os.PathError{Op: "mkdir", Path: pathname, Err: syscall.ENOTDIR}
291 }
292
293
294 existingPath, toCreate, err := findExistingPrefix(base, pathname)
295 if err != nil {
296 return fmt.Errorf("error opening directory %s: %s", pathname, err)
297 }
298 if len(toCreate) == 0 {
299 return nil
300 }
301
302
303 fullExistingPath, err := evalSymlink(existingPath)
304 if err != nil {
305 return fmt.Errorf("error opening existing directory %s: %s", existingPath, err)
306 }
307 fullBasePath, err := evalSymlink(base)
308 if err != nil {
309 return fmt.Errorf("cannot read link %s: %s", base, err)
310 }
311 if !mount.PathWithinBase(fullExistingPath, fullBasePath) {
312 return fmt.Errorf("path %s is outside of allowed base %s", fullExistingPath, err)
313 }
314
315
316 fileHandles, err := lockAndCheckSubPathWithoutSymlink(fullBasePath, fullExistingPath)
317 defer unlockPath(fileHandles)
318 if err != nil {
319 return err
320 }
321
322 klog.V(4).Infof("%q already exists, %q to create", fullExistingPath, filepath.Join(toCreate...))
323 currentPath := fullExistingPath
324
325
326 for _, dir := range toCreate {
327 currentPath = filepath.Join(currentPath, dir)
328 klog.V(4).Infof("Creating %s", dir)
329 if err := os.Mkdir(currentPath, perm); err != nil {
330 return fmt.Errorf("cannot create directory %s: %s", currentPath, err)
331 }
332 handle, err := lockPath(currentPath)
333 if err != nil {
334 return fmt.Errorf("cannot lock path %s: %s", currentPath, err)
335 }
336 defer syscall.CloseHandle(syscall.Handle(handle))
337
338 stat, err := os.Lstat(currentPath)
339 if err != nil {
340 return fmt.Errorf("Lstat(%q) error: %v", currentPath, err)
341 }
342 if stat.Mode()&os.ModeSymlink != 0 {
343 return fmt.Errorf("subpath %q is an unexpected symlink after Mkdir", currentPath)
344 }
345 }
346
347 return nil
348 }
349
350
351
352 func findExistingPrefix(base, pathname string) (string, []string, error) {
353 rel, err := filepath.Rel(base, pathname)
354 if err != nil {
355 return base, nil, err
356 }
357
358 if mount.StartsWithBackstep(rel) {
359 return base, nil, fmt.Errorf("pathname(%s) is not within base(%s)", pathname, base)
360 }
361
362 if rel == "." {
363
364 return pathname, []string{}, nil
365 }
366
367 dirs := strings.Split(rel, string(filepath.Separator))
368
369 var parent string
370 currentPath := base
371 for i, dir := range dirs {
372 parent = currentPath
373 currentPath = filepath.Join(parent, dir)
374 if _, err := os.Lstat(currentPath); err != nil {
375 if os.IsNotExist(err) {
376 return parent, dirs[i:], nil
377 }
378 return base, nil, err
379 }
380 }
381
382 return pathname, []string{}, nil
383 }
384
View as plain text