1
2
3
4
19
20 package subpath
21
22 import (
23 "fmt"
24 "io/ioutil"
25 "os"
26 "path/filepath"
27 "strconv"
28 "strings"
29 "syscall"
30
31 "golang.org/x/sys/unix"
32 "k8s.io/klog/v2"
33 "k8s.io/mount-utils"
34 )
35
36 const (
37
38
39 containerSubPathDirectoryName = "volume-subpaths"
40
41 nofollowFlags = unix.O_RDONLY | unix.O_NOFOLLOW
42
43 openFDFlags = unix.O_NOFOLLOW | unix.O_PATH
44 )
45
46 type subpath struct {
47 mounter mount.Interface
48 }
49
50
51 func New(mounter mount.Interface) Interface {
52 return &subpath{
53 mounter: mounter,
54 }
55 }
56
57 func (sp *subpath) CleanSubPaths(podDir string, volumeName string) error {
58 return doCleanSubPaths(sp.mounter, podDir, volumeName)
59 }
60
61 func (sp *subpath) SafeMakeDir(subdir string, base string, perm os.FileMode) error {
62 realBase, err := filepath.EvalSymlinks(base)
63 if err != nil {
64 return fmt.Errorf("error resolving symlinks in %s: %s", base, err)
65 }
66
67 realFullPath := filepath.Join(realBase, subdir)
68
69 return doSafeMakeDir(realFullPath, realBase, perm)
70 }
71
72 func (sp *subpath) PrepareSafeSubpath(subPath Subpath) (newHostPath string, cleanupAction func(), err error) {
73 newHostPath, err = doBindSubPath(sp.mounter, subPath)
74
75
76
77 cleanupAction = nil
78 return newHostPath, cleanupAction, err
79 }
80
81
82 func safeOpenSubPath(mounter mount.Interface, subpath Subpath) (int, error) {
83 if !mount.PathWithinBase(subpath.Path, subpath.VolumePath) {
84 return -1, fmt.Errorf("subpath %q not within volume path %q", subpath.Path, subpath.VolumePath)
85 }
86 fd, err := doSafeOpen(subpath.Path, subpath.VolumePath)
87 if err != nil {
88 return -1, fmt.Errorf("error opening subpath %v: %v", subpath.Path, err)
89 }
90 return fd, nil
91 }
92
93
94
95
96
97
98
99
100 func prepareSubpathTarget(mounter mount.Interface, subpath Subpath) (bool, string, error) {
101
102 bindPathTarget := getSubpathBindTarget(subpath)
103 notMount, err := mount.IsNotMountPoint(mounter, bindPathTarget)
104 if err != nil {
105 if !os.IsNotExist(err) {
106 return false, "", fmt.Errorf("error checking path %s for mount: %s", bindPathTarget, err)
107 }
108
109 notMount = true
110 }
111 if !notMount {
112
113 samePath, err := checkSubPathFileEqual(subpath, bindPathTarget)
114 if err != nil {
115 return false, "", fmt.Errorf("error checking subpath mount info for %s: %s", bindPathTarget, err)
116 }
117 if !samePath {
118
119 if err = mounter.Unmount(bindPathTarget); err != nil {
120 return false, "", fmt.Errorf("error ummounting %s: %s", bindPathTarget, err)
121 }
122 } else {
123
124 klog.V(5).Infof("Skipping bind-mounting subpath %s: already mounted", bindPathTarget)
125 return true, bindPathTarget, nil
126 }
127 }
128
129
130
131 bindParent := filepath.Dir(bindPathTarget)
132 err = os.MkdirAll(bindParent, 0750)
133 if err != nil && !os.IsExist(err) {
134 return false, "", fmt.Errorf("error creating directory %s: %s", bindParent, err)
135 }
136
137 t, err := os.Lstat(subpath.Path)
138 if err != nil {
139 return false, "", fmt.Errorf("lstat %s failed: %s", subpath.Path, err)
140 }
141
142 if t.Mode()&os.ModeDir > 0 {
143 if err = os.Mkdir(bindPathTarget, 0750); err != nil && !os.IsExist(err) {
144 return false, "", fmt.Errorf("error creating directory %s: %s", bindPathTarget, err)
145 }
146 } else {
147
148
149
150
151 if err = ioutil.WriteFile(bindPathTarget, []byte{}, 0640); err != nil {
152 return false, "", fmt.Errorf("error creating file %s: %s", bindPathTarget, err)
153 }
154 }
155 return false, bindPathTarget, nil
156 }
157
158 func checkSubPathFileEqual(subpath Subpath, bindMountTarget string) (bool, error) {
159 s, err := os.Lstat(subpath.Path)
160 if err != nil {
161 return false, fmt.Errorf("stat %s failed: %s", subpath.Path, err)
162 }
163
164 t, err := os.Lstat(bindMountTarget)
165 if err != nil {
166 return false, fmt.Errorf("lstat %s failed: %s", bindMountTarget, err)
167 }
168
169 if !os.SameFile(s, t) {
170 return false, nil
171 }
172 return true, nil
173 }
174
175 func getSubpathBindTarget(subpath Subpath) string {
176
177 return filepath.Join(subpath.PodDir, containerSubPathDirectoryName, subpath.VolumeName, subpath.ContainerName, strconv.Itoa(subpath.VolumeMountIndex))
178 }
179
180 func doBindSubPath(mounter mount.Interface, subpath Subpath) (hostPath string, err error) {
181
182
183
184
185
186
187 newVolumePath, err := filepath.EvalSymlinks(subpath.VolumePath)
188 if err != nil {
189 return "", fmt.Errorf("error resolving symlinks in %q: %v", subpath.VolumePath, err)
190 }
191 newPath, err := filepath.EvalSymlinks(subpath.Path)
192 if err != nil {
193 return "", fmt.Errorf("error resolving symlinks in %q: %v", subpath.Path, err)
194 }
195 klog.V(5).Infof("doBindSubPath %q (%q) for volumepath %q", subpath.Path, newPath, subpath.VolumePath)
196 subpath.VolumePath = newVolumePath
197 subpath.Path = newPath
198
199 fd, err := safeOpenSubPath(mounter, subpath)
200 if err != nil {
201 return "", err
202 }
203 defer syscall.Close(fd)
204
205 alreadyMounted, bindPathTarget, err := prepareSubpathTarget(mounter, subpath)
206 if err != nil {
207 return "", err
208 }
209 if alreadyMounted {
210 return bindPathTarget, nil
211 }
212
213 success := false
214 defer func() {
215
216 if !success {
217 klog.V(4).Infof("doBindSubPath() failed for %q, cleaning up subpath", bindPathTarget)
218 if cleanErr := cleanSubPath(mounter, subpath); cleanErr != nil {
219 klog.Errorf("Failed to clean subpath %q: %v", bindPathTarget, cleanErr)
220 }
221 }
222 }()
223
224 kubeletPid := os.Getpid()
225 mountSource := fmt.Sprintf("/proc/%d/fd/%v", kubeletPid, fd)
226
227
228 options := []string{"bind"}
229 mountFlags := []string{"--no-canonicalize"}
230 klog.V(5).Infof("bind mounting %q at %q", mountSource, bindPathTarget)
231 if err = mounter.MountSensitiveWithoutSystemdWithMountFlags(mountSource, bindPathTarget, "" , options, nil , mountFlags); err != nil {
232 return "", fmt.Errorf("error mounting %s: %s", subpath.Path, err)
233 }
234 success = true
235
236 klog.V(3).Infof("Bound SubPath %s into %s", subpath.Path, bindPathTarget)
237 return bindPathTarget, nil
238 }
239
240
241 func doCleanSubPaths(mounter mount.Interface, podDir string, volumeName string) error {
242
243 subPathDir := filepath.Join(podDir, containerSubPathDirectoryName, volumeName)
244 klog.V(4).Infof("Cleaning up subpath mounts for %s", subPathDir)
245
246 containerDirs, err := ioutil.ReadDir(subPathDir)
247 if err != nil {
248 if os.IsNotExist(err) {
249 return nil
250 }
251 return fmt.Errorf("error reading %s: %s", subPathDir, err)
252 }
253
254 for _, containerDir := range containerDirs {
255 if !containerDir.IsDir() {
256 klog.V(4).Infof("Container file is not a directory: %s", containerDir.Name())
257 continue
258 }
259 klog.V(4).Infof("Cleaning up subpath mounts for container %s", containerDir.Name())
260
261
262 fullContainerDirPath := filepath.Join(subPathDir, containerDir.Name())
263
264
265
266
267
268 err = filepath.WalkDir(fullContainerDirPath, func(path string, info os.DirEntry, _ error) error {
269 if path == fullContainerDirPath {
270
271 return nil
272 }
273
274
275 if err = doCleanSubPath(mounter, fullContainerDirPath, filepath.Base(path)); err != nil {
276 return err
277 }
278
279
280 if info != nil && info.IsDir() {
281
282 return filepath.SkipDir
283 }
284
285 return nil
286 })
287 if err != nil {
288 return fmt.Errorf("error processing %s: %s", fullContainerDirPath, err)
289 }
290
291
292 if err := os.Remove(fullContainerDirPath); err != nil {
293 return fmt.Errorf("error deleting %s: %s", fullContainerDirPath, err)
294 }
295 klog.V(5).Infof("Removed %s", fullContainerDirPath)
296 }
297
298 if err := os.Remove(subPathDir); err != nil {
299 return fmt.Errorf("error deleting %s: %s", subPathDir, err)
300 }
301 klog.V(5).Infof("Removed %s", subPathDir)
302
303
304 podSubPathDir := filepath.Join(podDir, containerSubPathDirectoryName)
305 if err := os.Remove(podSubPathDir); err != nil && !os.IsExist(err) {
306 return fmt.Errorf("error deleting %s: %s", podSubPathDir, err)
307 }
308 klog.V(5).Infof("Removed %s", podSubPathDir)
309 return nil
310 }
311
312
313 func doCleanSubPath(mounter mount.Interface, fullContainerDirPath, subPathIndex string) error {
314
315 klog.V(4).Infof("Cleaning up subpath mounts for subpath %v", subPathIndex)
316 fullSubPath := filepath.Join(fullContainerDirPath, subPathIndex)
317
318 if err := mount.CleanupMountPoint(fullSubPath, mounter, true); err != nil {
319 return fmt.Errorf("error cleaning subpath mount %s: %s", fullSubPath, err)
320 }
321
322 klog.V(4).Infof("Successfully cleaned subpath directory %s", fullSubPath)
323 return nil
324 }
325
326
327 func cleanSubPath(mounter mount.Interface, subpath Subpath) error {
328 containerDir := filepath.Join(subpath.PodDir, containerSubPathDirectoryName, subpath.VolumeName, subpath.ContainerName)
329
330
331 if err := doCleanSubPath(mounter, containerDir, strconv.Itoa(subpath.VolumeMountIndex)); err != nil && !os.IsNotExist(err) {
332 return err
333 }
334
335
336 if err := removeEmptyDirs(subpath.PodDir, containerDir); err != nil {
337 return err
338 }
339
340 return nil
341 }
342
343
344
345 func removeEmptyDirs(baseDir, endDir string) error {
346 if !mount.PathWithinBase(endDir, baseDir) {
347 return fmt.Errorf("endDir %q is not within baseDir %q", endDir, baseDir)
348 }
349
350 for curDir := endDir; curDir != baseDir; curDir = filepath.Dir(curDir) {
351 s, err := os.Stat(curDir)
352 if err != nil {
353 if os.IsNotExist(err) {
354 klog.V(5).Infof("curDir %q doesn't exist, skipping", curDir)
355 continue
356 }
357 return fmt.Errorf("error stat %q: %v", curDir, err)
358 }
359 if !s.IsDir() {
360 return fmt.Errorf("path %q not a directory", curDir)
361 }
362
363 err = os.Remove(curDir)
364 if os.IsExist(err) {
365 klog.V(5).Infof("Directory %q not empty, not removing", curDir)
366 break
367 } else if err != nil {
368 return fmt.Errorf("error removing directory %q: %v", curDir, err)
369 }
370 klog.V(5).Infof("Removed directory %q", curDir)
371 }
372 return nil
373 }
374
375
376
377
378 func doSafeMakeDir(pathname string, base string, perm os.FileMode) error {
379 klog.V(4).Infof("Creating directory %q within base %q", pathname, base)
380
381 if !mount.PathWithinBase(pathname, base) {
382 return fmt.Errorf("path %s is outside of allowed base %s", pathname, base)
383 }
384
385
386 s, err := os.Stat(pathname)
387 if err == nil {
388
389 if s.IsDir() {
390
391
392 klog.V(4).Infof("Directory %s already exists", pathname)
393 return nil
394 }
395 return &os.PathError{Op: "mkdir", Path: pathname, Err: syscall.ENOTDIR}
396 }
397
398
399 existingPath, toCreate, err := findExistingPrefix(base, pathname)
400 if err != nil {
401 return fmt.Errorf("error opening directory %s: %s", pathname, err)
402 }
403
404 fullExistingPath, err := filepath.EvalSymlinks(existingPath)
405 if err != nil {
406 return fmt.Errorf("error opening directory %s: %s", existingPath, err)
407 }
408 if !mount.PathWithinBase(fullExistingPath, base) {
409 return fmt.Errorf("path %s is outside of allowed base %s", fullExistingPath, err)
410 }
411
412 klog.V(4).Infof("%q already exists, %q to create", fullExistingPath, filepath.Join(toCreate...))
413 parentFD, err := doSafeOpen(fullExistingPath, base)
414 if err != nil {
415 return fmt.Errorf("cannot open directory %s: %s", existingPath, err)
416 }
417 childFD := -1
418 defer func() {
419 if parentFD != -1 {
420 if err = syscall.Close(parentFD); err != nil {
421 klog.V(4).Infof("Closing FD %v failed for safemkdir(%v): %v", parentFD, pathname, err)
422 }
423 }
424 if childFD != -1 {
425 if err = syscall.Close(childFD); err != nil {
426 klog.V(4).Infof("Closing FD %v failed for safemkdir(%v): %v", childFD, pathname, err)
427 }
428 }
429 }()
430
431 currentPath := fullExistingPath
432
433
434 for _, dir := range toCreate {
435 currentPath = filepath.Join(currentPath, dir)
436 klog.V(4).Infof("Creating %s", dir)
437 err = syscall.Mkdirat(parentFD, currentPath, uint32(perm))
438 if err != nil {
439 return fmt.Errorf("cannot create directory %s: %s", currentPath, err)
440 }
441
442 childFD, err = syscall.Openat(parentFD, dir, nofollowFlags|unix.O_CLOEXEC, 0)
443 if err != nil {
444 return fmt.Errorf("cannot open %s: %s", currentPath, err)
445 }
446
447
448
449
450
451
452
453
454 if err = syscall.Close(parentFD); err != nil {
455 klog.V(4).Infof("Closing FD %v failed for safemkdir(%v): %v", parentFD, pathname, err)
456 }
457 parentFD = childFD
458 childFD = -1
459
460
461
462
463
464
465
466
467 kernelPerm := uint32(perm & os.ModePerm)
468 if perm&os.ModeSetgid > 0 {
469 kernelPerm |= syscall.S_ISGID
470 }
471 if perm&os.ModeSetuid > 0 {
472 kernelPerm |= syscall.S_ISUID
473 }
474 if perm&os.ModeSticky > 0 {
475 kernelPerm |= syscall.S_ISVTX
476 }
477 if err = syscall.Fchmod(parentFD, kernelPerm); err != nil {
478 return fmt.Errorf("chmod %q failed: %s", currentPath, err)
479 }
480 }
481
482 return nil
483 }
484
485
486
487 func findExistingPrefix(base, pathname string) (string, []string, error) {
488 rel, err := filepath.Rel(base, pathname)
489 if err != nil {
490 return base, nil, err
491 }
492 dirs := strings.Split(rel, string(filepath.Separator))
493
494
495
496
497 currentPath := base
498 fd, err := syscall.Open(currentPath, syscall.O_RDONLY|syscall.O_CLOEXEC, 0)
499 if err != nil {
500 return pathname, nil, fmt.Errorf("error opening %s: %s", currentPath, err)
501 }
502 defer func() {
503 if err = syscall.Close(fd); err != nil {
504 klog.V(4).Infof("Closing FD %v failed for findExistingPrefix(%v): %v", fd, pathname, err)
505 }
506 }()
507 for i, dir := range dirs {
508
509
510 childFD, err := syscall.Openat(fd, dir, unix.O_PATH|unix.O_CLOEXEC, 0)
511 if err != nil {
512 if os.IsNotExist(err) {
513 return currentPath, dirs[i:], nil
514 }
515 return base, nil, err
516 }
517 if err = syscall.Close(fd); err != nil {
518 klog.V(4).Infof("Closing FD %v failed for findExistingPrefix(%v): %v", fd, pathname, err)
519 }
520 fd = childFD
521 currentPath = filepath.Join(currentPath, dir)
522 }
523 return pathname, []string{}, nil
524 }
525
526
527
528
529
530 func doSafeOpen(pathname string, base string) (int, error) {
531 pathname = filepath.Clean(pathname)
532 base = filepath.Clean(base)
533
534
535 subpath, err := filepath.Rel(base, pathname)
536 if err != nil {
537 return -1, err
538 }
539 segments := strings.Split(subpath, string(filepath.Separator))
540
541
542
543 parentFD, err := syscall.Open(base, nofollowFlags|unix.O_CLOEXEC, 0)
544 if err != nil {
545 return -1, fmt.Errorf("cannot open directory %s: %s", base, err)
546 }
547 defer func() {
548 if parentFD != -1 {
549 if err = syscall.Close(parentFD); err != nil {
550 klog.V(4).Infof("Closing FD %v failed for safeopen(%v): %v", parentFD, pathname, err)
551 }
552 }
553 }()
554
555 childFD := -1
556 defer func() {
557 if childFD != -1 {
558 if err = syscall.Close(childFD); err != nil {
559 klog.V(4).Infof("Closing FD %v failed for safeopen(%v): %v", childFD, pathname, err)
560 }
561 }
562 }()
563
564 currentPath := base
565
566
567
568 for _, seg := range segments {
569 var deviceStat unix.Stat_t
570
571 currentPath = filepath.Join(currentPath, seg)
572 if !mount.PathWithinBase(currentPath, base) {
573 return -1, fmt.Errorf("path %s is outside of allowed base %s", currentPath, base)
574 }
575
576
577
578 unix.Fstatat(parentFD, seg+"/", &deviceStat, unix.AT_SYMLINK_NOFOLLOW)
579
580 klog.V(5).Infof("Opening path %s", currentPath)
581 childFD, err = syscall.Openat(parentFD, seg, openFDFlags|unix.O_CLOEXEC, 0)
582 if err != nil {
583 return -1, fmt.Errorf("cannot open %s: %s", currentPath, err)
584 }
585
586 err := unix.Fstat(childFD, &deviceStat)
587 if err != nil {
588 return -1, fmt.Errorf("error running fstat on %s with %v", currentPath, err)
589 }
590 fileFmt := deviceStat.Mode & syscall.S_IFMT
591 if fileFmt == syscall.S_IFLNK {
592 return -1, fmt.Errorf("unexpected symlink found %s", currentPath)
593 }
594
595
596 if err = syscall.Close(parentFD); err != nil {
597 return -1, fmt.Errorf("closing fd for %q failed: %v", filepath.Dir(currentPath), err)
598 }
599
600 parentFD = childFD
601 childFD = -1
602 }
603
604
605 finalFD := parentFD
606 parentFD = -1
607
608 return finalFD, nil
609 }
610
View as plain text