1
2
3
4
19
20 package subpath
21
22 import (
23 "fmt"
24 "io/ioutil"
25 "net"
26 "os"
27 "path/filepath"
28 "reflect"
29 "strconv"
30 "syscall"
31 "testing"
32
33 "k8s.io/klog/v2"
34 "k8s.io/mount-utils"
35 )
36
37 func TestSafeMakeDir(t *testing.T) {
38 defaultPerm := os.FileMode(0750) + os.ModeDir
39 maxPerm := os.FileMode(0777) + os.ModeDir
40 tests := []struct {
41 name string
42
43
44 prepare func(base string) error
45 path string
46 checkPath string
47 perm os.FileMode
48 expectError bool
49 }{
50 {
51 "directory-does-not-exist",
52 func(base string) error {
53 return nil
54 },
55 "test/directory",
56 "test/directory",
57 defaultPerm,
58 false,
59 },
60 {
61 "all-created-subpath-directory-with-permissions",
62 func(base string) error {
63 return nil
64 },
65 "test/directory",
66 "test",
67 maxPerm,
68 false,
69 },
70 {
71 "directory-with-sgid",
72 func(base string) error {
73 return nil
74 },
75 "test/directory",
76 "test/directory",
77 os.FileMode(0777) + os.ModeDir + os.ModeSetgid,
78 false,
79 },
80 {
81 "directory-with-suid",
82 func(base string) error {
83 return nil
84 },
85 "test/directory",
86 "test/directory",
87 os.FileMode(0777) + os.ModeDir + os.ModeSetuid,
88 false,
89 },
90 {
91 "directory-with-sticky-bit",
92 func(base string) error {
93 return nil
94 },
95 "test/directory",
96 "test/directory",
97 os.FileMode(0777) + os.ModeDir + os.ModeSticky,
98 false,
99 },
100 {
101 "directory-exists",
102 func(base string) error {
103 return os.MkdirAll(filepath.Join(base, "test/directory"), 0750)
104 },
105 "test/directory",
106 "test/directory",
107 defaultPerm,
108 false,
109 },
110 {
111 "create-base",
112 func(base string) error {
113 return nil
114 },
115 "",
116 "",
117 defaultPerm,
118 false,
119 },
120 {
121 "escape-base-using-dots",
122 func(base string) error {
123 return nil
124 },
125 "..",
126 "",
127 defaultPerm,
128 true,
129 },
130 {
131 "escape-base-using-dots-2",
132 func(base string) error {
133 return nil
134 },
135 "test/../../..",
136 "",
137 defaultPerm,
138 true,
139 },
140 {
141 "follow-symlinks",
142 func(base string) error {
143 if err := os.MkdirAll(filepath.Join(base, "destination"), defaultPerm); err != nil {
144 return err
145 }
146 return os.Symlink("destination", filepath.Join(base, "test"))
147 },
148 "test/directory",
149 "destination/directory",
150 defaultPerm,
151 false,
152 },
153 {
154 "follow-symlink-loop",
155 func(base string) error {
156 return os.Symlink("test", filepath.Join(base, "test"))
157 },
158 "test/directory",
159 "",
160 defaultPerm,
161 true,
162 },
163 {
164 "follow-symlink-multiple follow",
165 func(base string) error {
166
167 if err := os.MkdirAll(filepath.Join(base, "test1"), defaultPerm); err != nil {
168 return err
169 }
170 if err := os.MkdirAll(filepath.Join(base, "test2"), defaultPerm); err != nil {
171 return err
172 }
173 if err := os.Symlink(filepath.Join(base, "test2"), filepath.Join(base, "test1/dir")); err != nil {
174 return err
175 }
176 if err := os.Symlink(filepath.Join(base, "test1"), filepath.Join(base, "test2/dir")); err != nil {
177 return err
178 }
179 return nil
180 },
181 "test1/dir/dir/dir/dir/dir/dir/dir/foo",
182 "test2/foo",
183 defaultPerm,
184 false,
185 },
186 {
187 "danglink-symlink",
188 func(base string) error {
189 return os.Symlink("non-existing", filepath.Join(base, "test"))
190 },
191 "test/directory",
192 "",
193 defaultPerm,
194 true,
195 },
196 {
197 "non-directory",
198 func(base string) error {
199 return ioutil.WriteFile(filepath.Join(base, "test"), []byte{}, defaultPerm)
200 },
201 "test/directory",
202 "",
203 defaultPerm,
204 true,
205 },
206 {
207 "non-directory-final",
208 func(base string) error {
209 return ioutil.WriteFile(filepath.Join(base, "test"), []byte{}, defaultPerm)
210 },
211 "test",
212 "",
213 defaultPerm,
214 true,
215 },
216 {
217 "escape-with-relative-symlink",
218 func(base string) error {
219 if err := os.MkdirAll(filepath.Join(base, "dir"), defaultPerm); err != nil {
220 return err
221 }
222 if err := os.MkdirAll(filepath.Join(base, "exists"), defaultPerm); err != nil {
223 return err
224 }
225 return os.Symlink("../exists", filepath.Join(base, "dir/test"))
226 },
227 "dir/test",
228 "",
229 defaultPerm,
230 false,
231 },
232 {
233 "escape-with-relative-symlink-not-exists",
234 func(base string) error {
235 if err := os.MkdirAll(filepath.Join(base, "dir"), defaultPerm); err != nil {
236 return err
237 }
238 return os.Symlink("../not-exists", filepath.Join(base, "dir/test"))
239 },
240 "dir/test",
241 "",
242 defaultPerm,
243 true,
244 },
245 {
246 "escape-with-symlink",
247 func(base string) error {
248 return os.Symlink("/", filepath.Join(base, "test"))
249 },
250 "test/directory",
251 "",
252 defaultPerm,
253 true,
254 },
255 }
256
257 for i := range tests {
258 test := tests[i]
259 t.Run(test.name, func(t *testing.T) {
260 base, err := ioutil.TempDir("", "safe-make-dir-"+test.name+"-")
261 if err != nil {
262 t.Fatalf(err.Error())
263 }
264 defer os.RemoveAll(base)
265 test.prepare(base)
266 pathToCreate := filepath.Join(base, test.path)
267 err = doSafeMakeDir(pathToCreate, base, test.perm)
268 if err != nil && !test.expectError {
269 t.Fatal(err)
270 }
271 if err != nil {
272 t.Logf("got error: %s", err)
273 }
274 if err == nil && test.expectError {
275 t.Fatalf("expected error, got none")
276 }
277
278 if test.checkPath != "" {
279 st, err := os.Stat(filepath.Join(base, test.checkPath))
280 if err != nil {
281 t.Fatalf("cannot read path %s", test.checkPath)
282 }
283 actualMode := st.Mode()
284 if actualMode != test.perm {
285 if actualMode^test.perm == os.ModeSetgid && test.perm&os.ModeSetgid == 0 {
286
287 t.Logf("masking bit from %o", actualMode)
288 } else {
289 t.Errorf("expected permissions %o, got %o (%b)", test.perm, actualMode, test.perm^actualMode)
290 }
291 }
292 }
293 })
294 }
295 }
296
297 func TestRemoveEmptyDirs(t *testing.T) {
298 defaultPerm := os.FileMode(0750)
299 tests := []struct {
300 name string
301
302
303 prepare func(base string) error
304
305 validate func(base string) error
306 baseDir string
307 endDir string
308 expectError bool
309 }{
310 {
311 name: "all-empty",
312 prepare: func(base string) error {
313 return os.MkdirAll(filepath.Join(base, "a/b/c"), defaultPerm)
314 },
315 validate: func(base string) error {
316 return validateDirEmpty(filepath.Join(base, "a"))
317 },
318 baseDir: "a",
319 endDir: "a/b/c",
320 expectError: false,
321 },
322 {
323 name: "dir-not-empty",
324 prepare: func(base string) error {
325 if err := os.MkdirAll(filepath.Join(base, "a/b/c"), defaultPerm); err != nil {
326 return err
327 }
328 return os.Mkdir(filepath.Join(base, "a/b/d"), defaultPerm)
329 },
330 validate: func(base string) error {
331 if err := validateDirNotExists(filepath.Join(base, "a/b/c")); err != nil {
332 return err
333 }
334 return validateDirExists(filepath.Join(base, "a/b"))
335 },
336 baseDir: "a",
337 endDir: "a/b/c",
338 expectError: false,
339 },
340 {
341 name: "path-not-within-base",
342 prepare: func(base string) error {
343 return os.MkdirAll(filepath.Join(base, "a/b/c"), defaultPerm)
344 },
345 validate: func(base string) error {
346 return validateDirExists(filepath.Join(base, "a"))
347 },
348 baseDir: "a",
349 endDir: "b/c",
350 expectError: true,
351 },
352 {
353 name: "path-already-deleted",
354 prepare: func(base string) error {
355 return nil
356 },
357 validate: func(base string) error {
358 return nil
359 },
360 baseDir: "a",
361 endDir: "a/b/c",
362 expectError: false,
363 },
364 {
365 name: "path-not-dir",
366 prepare: func(base string) error {
367 if err := os.MkdirAll(filepath.Join(base, "a/b"), defaultPerm); err != nil {
368 return err
369 }
370 return ioutil.WriteFile(filepath.Join(base, "a/b", "c"), []byte{}, defaultPerm)
371 },
372 validate: func(base string) error {
373 if err := validateDirExists(filepath.Join(base, "a/b")); err != nil {
374 return err
375 }
376 return validateFileExists(filepath.Join(base, "a/b/c"))
377 },
378 baseDir: "a",
379 endDir: "a/b/c",
380 expectError: true,
381 },
382 }
383
384 for _, test := range tests {
385 klog.V(4).Infof("test %q", test.name)
386 base, err := ioutil.TempDir("", "remove-empty-dirs-"+test.name+"-")
387 if err != nil {
388 t.Fatalf(err.Error())
389 }
390 if err = test.prepare(base); err != nil {
391 os.RemoveAll(base)
392 t.Fatalf("failed to prepare test %q: %v", test.name, err.Error())
393 }
394
395 err = removeEmptyDirs(filepath.Join(base, test.baseDir), filepath.Join(base, test.endDir))
396 if err != nil && !test.expectError {
397 t.Errorf("test %q failed: %v", test.name, err)
398 }
399 if err == nil && test.expectError {
400 t.Errorf("test %q failed: expected error, got success", test.name)
401 }
402
403 if err = test.validate(base); err != nil {
404 t.Errorf("test %q failed validation: %v", test.name, err)
405 }
406
407 os.RemoveAll(base)
408 }
409 }
410
411 func TestCleanSubPaths(t *testing.T) {
412 defaultPerm := os.FileMode(0750)
413 testVol := "vol1"
414
415 tests := []struct {
416 name string
417
418
419 prepare func(base string) ([]mount.MountPoint, error)
420
421 validate func(base string) error
422 expectError bool
423 unmount func(path string) error
424 }{
425 {
426 name: "not-exists",
427 prepare: func(base string) ([]mount.MountPoint, error) {
428 return nil, nil
429 },
430 validate: func(base string) error {
431 return nil
432 },
433 expectError: false,
434 },
435 {
436 name: "subpath-not-mount",
437 prepare: func(base string) ([]mount.MountPoint, error) {
438 return nil, os.MkdirAll(filepath.Join(base, containerSubPathDirectoryName, testVol, "container1", "0"), defaultPerm)
439 },
440 validate: func(base string) error {
441 return validateDirNotExists(filepath.Join(base, containerSubPathDirectoryName))
442 },
443 expectError: false,
444 },
445 {
446 name: "subpath-file",
447 prepare: func(base string) ([]mount.MountPoint, error) {
448 path := filepath.Join(base, containerSubPathDirectoryName, testVol, "container1")
449 if err := os.MkdirAll(path, defaultPerm); err != nil {
450 return nil, err
451 }
452 return nil, ioutil.WriteFile(filepath.Join(path, "0"), []byte{}, defaultPerm)
453 },
454 validate: func(base string) error {
455 return validateDirNotExists(filepath.Join(base, containerSubPathDirectoryName))
456 },
457 expectError: false,
458 },
459 {
460 name: "subpath-container-not-dir",
461 prepare: func(base string) ([]mount.MountPoint, error) {
462 path := filepath.Join(base, containerSubPathDirectoryName, testVol)
463 if err := os.MkdirAll(path, defaultPerm); err != nil {
464 return nil, err
465 }
466 return nil, ioutil.WriteFile(filepath.Join(path, "container1"), []byte{}, defaultPerm)
467 },
468 validate: func(base string) error {
469 return validateDirExists(filepath.Join(base, containerSubPathDirectoryName, testVol))
470 },
471 expectError: true,
472 },
473 {
474 name: "subpath-multiple-container-not-dir",
475 prepare: func(base string) ([]mount.MountPoint, error) {
476 path := filepath.Join(base, containerSubPathDirectoryName, testVol)
477 if err := os.MkdirAll(filepath.Join(path, "container1"), defaultPerm); err != nil {
478 return nil, err
479 }
480 return nil, ioutil.WriteFile(filepath.Join(path, "container2"), []byte{}, defaultPerm)
481 },
482 validate: func(base string) error {
483 path := filepath.Join(base, containerSubPathDirectoryName, testVol)
484 if err := validateDirNotExists(filepath.Join(path, "container1")); err != nil {
485 return err
486 }
487 return validateFileExists(filepath.Join(path, "container2"))
488 },
489 expectError: true,
490 },
491 {
492 name: "subpath-mount",
493 prepare: func(base string) ([]mount.MountPoint, error) {
494 path := filepath.Join(base, containerSubPathDirectoryName, testVol, "container1", "0")
495 if err := os.MkdirAll(path, defaultPerm); err != nil {
496 return nil, err
497 }
498 mounts := []mount.MountPoint{{Device: "/dev/sdb", Path: path}}
499 return mounts, nil
500 },
501 validate: func(base string) error {
502 return validateDirNotExists(filepath.Join(base, containerSubPathDirectoryName))
503 },
504 },
505 {
506 name: "subpath-mount-multiple",
507 prepare: func(base string) ([]mount.MountPoint, error) {
508 path := filepath.Join(base, containerSubPathDirectoryName, testVol, "container1", "0")
509 path2 := filepath.Join(base, containerSubPathDirectoryName, testVol, "container1", "1")
510 path3 := filepath.Join(base, containerSubPathDirectoryName, testVol, "container2", "1")
511 if err := os.MkdirAll(path, defaultPerm); err != nil {
512 return nil, err
513 }
514 if err := os.MkdirAll(path2, defaultPerm); err != nil {
515 return nil, err
516 }
517 if err := os.MkdirAll(path3, defaultPerm); err != nil {
518 return nil, err
519 }
520 mounts := []mount.MountPoint{
521 {Device: "/dev/sdb", Path: path},
522 {Device: "/dev/sdb", Path: path3},
523 }
524 return mounts, nil
525 },
526 validate: func(base string) error {
527 return validateDirNotExists(filepath.Join(base, containerSubPathDirectoryName))
528 },
529 },
530 {
531 name: "subpath-mount-multiple-vols",
532 prepare: func(base string) ([]mount.MountPoint, error) {
533 path := filepath.Join(base, containerSubPathDirectoryName, testVol, "container1", "0")
534 path2 := filepath.Join(base, containerSubPathDirectoryName, "vol2", "container1", "1")
535 if err := os.MkdirAll(path, defaultPerm); err != nil {
536 return nil, err
537 }
538 if err := os.MkdirAll(path2, defaultPerm); err != nil {
539 return nil, err
540 }
541 mounts := []mount.MountPoint{
542 {Device: "/dev/sdb", Path: path},
543 }
544 return mounts, nil
545 },
546 validate: func(base string) error {
547 baseSubdir := filepath.Join(base, containerSubPathDirectoryName)
548 if err := validateDirNotExists(filepath.Join(baseSubdir, testVol)); err != nil {
549 return err
550 }
551 return validateDirExists(baseSubdir)
552 },
553 },
554 {
555 name: "subpath-with-files",
556 prepare: func(base string) ([]mount.MountPoint, error) {
557 containerPath := filepath.Join(base, containerSubPathDirectoryName, testVol, "container1")
558 if err := os.MkdirAll(containerPath, defaultPerm); err != nil {
559 return nil, err
560 }
561
562 file0 := filepath.Join(containerPath, "0")
563 if err := ioutil.WriteFile(file0, []byte{}, defaultPerm); err != nil {
564 return nil, err
565 }
566
567 dir1 := filepath.Join(containerPath, "1")
568 if err := os.MkdirAll(filepath.Join(dir1, "my-dir-1"), defaultPerm); err != nil {
569 return nil, err
570 }
571
572 dir2 := filepath.Join(containerPath, "2")
573 if err := os.MkdirAll(filepath.Join(dir2, "my-dir-2"), defaultPerm); err != nil {
574 return nil, err
575 }
576
577 file3 := filepath.Join(containerPath, "3")
578 if err := ioutil.WriteFile(file3, []byte{}, defaultPerm); err != nil {
579 return nil, err
580 }
581
582 mounts := []mount.MountPoint{
583 {Device: "/dev/sdb", Path: file0},
584 {Device: "/dev/sdc", Path: dir1},
585 {Device: "/dev/sdd", Path: dir2},
586 {Device: "/dev/sde", Path: file3},
587 }
588 return mounts, nil
589 },
590 unmount: func(mountpath string) error {
591 err := filepath.Walk(mountpath, func(path string, info os.FileInfo, _ error) error {
592 if path == mountpath {
593
594 return nil
595 }
596
597 if err := os.Remove(path); err != nil {
598 return err
599 }
600 return filepath.SkipDir
601 })
602 if err != nil {
603 return fmt.Errorf("error processing %s: %s", mountpath, err)
604 }
605
606 return nil
607 },
608 validate: func(base string) error {
609 return validateDirNotExists(filepath.Join(base, containerSubPathDirectoryName))
610 },
611 },
612 }
613
614 for _, test := range tests {
615 klog.V(4).Infof("test %q", test.name)
616 base, err := ioutil.TempDir("", "clean-subpaths-"+test.name+"-")
617 if err != nil {
618 t.Fatalf(err.Error())
619 }
620 mounts, err := test.prepare(base)
621 if err != nil {
622 os.RemoveAll(base)
623 t.Fatalf("failed to prepare test %q: %v", test.name, err.Error())
624 }
625
626 fm := mount.NewFakeMounter(mounts)
627 fm.UnmountFunc = test.unmount
628
629 err = doCleanSubPaths(fm, base, testVol)
630 if err != nil && !test.expectError {
631 t.Errorf("test %q failed: %v", test.name, err)
632 }
633 if err == nil && test.expectError {
634 t.Errorf("test %q failed: expected error, got success", test.name)
635 }
636 if err = test.validate(base); err != nil {
637 t.Errorf("test %q failed validation: %v", test.name, err)
638 }
639
640 os.RemoveAll(base)
641 }
642 }
643
644 var (
645 testVol = "vol1"
646 testPod = "pod0"
647 testContainer = "container0"
648 testSubpath = 1
649 )
650
651 func setupFakeMounter(testMounts []string) *mount.FakeMounter {
652 mounts := []mount.MountPoint{}
653 for _, mountPoint := range testMounts {
654 mounts = append(mounts, mount.MountPoint{Device: "/foo", Path: mountPoint})
655 }
656 return mount.NewFakeMounter(mounts)
657 }
658
659 func getTestPaths(base string) (string, string) {
660 return filepath.Join(base, testVol),
661 filepath.Join(base, testPod, containerSubPathDirectoryName, testVol, testContainer, strconv.Itoa(testSubpath))
662 }
663
664 func TestBindSubPath(t *testing.T) {
665 defaultPerm := os.FileMode(0750)
666
667 tests := []struct {
668 name string
669
670
671 prepare func(base string) ([]string, string, string, error)
672 expectError bool
673 }{
674 {
675 name: "subpath-dir",
676 prepare: func(base string) ([]string, string, string, error) {
677 volpath, _ := getTestPaths(base)
678 subpath := filepath.Join(volpath, "dir0")
679 return nil, volpath, subpath, os.MkdirAll(subpath, defaultPerm)
680 },
681 expectError: false,
682 },
683 {
684 name: "subpath-dir-symlink",
685 prepare: func(base string) ([]string, string, string, error) {
686 volpath, _ := getTestPaths(base)
687 subpath := filepath.Join(volpath, "dir0")
688 if err := os.MkdirAll(subpath, defaultPerm); err != nil {
689 return nil, "", "", err
690 }
691 subpathLink := filepath.Join(volpath, "dirLink")
692 return nil, volpath, subpath, os.Symlink(subpath, subpathLink)
693 },
694 expectError: false,
695 },
696 {
697 name: "subpath-file",
698 prepare: func(base string) ([]string, string, string, error) {
699 volpath, _ := getTestPaths(base)
700 subpath := filepath.Join(volpath, "file0")
701 if err := os.MkdirAll(volpath, defaultPerm); err != nil {
702 return nil, "", "", err
703 }
704 return nil, volpath, subpath, ioutil.WriteFile(subpath, []byte{}, defaultPerm)
705 },
706 expectError: false,
707 },
708 {
709 name: "subpath-not-exists",
710 prepare: func(base string) ([]string, string, string, error) {
711 volpath, _ := getTestPaths(base)
712 subpath := filepath.Join(volpath, "file0")
713 return nil, volpath, subpath, nil
714 },
715 expectError: true,
716 },
717 {
718 name: "subpath-outside",
719 prepare: func(base string) ([]string, string, string, error) {
720 volpath, _ := getTestPaths(base)
721 subpath := filepath.Join(volpath, "dir0")
722 if err := os.MkdirAll(volpath, defaultPerm); err != nil {
723 return nil, "", "", err
724 }
725 return nil, volpath, subpath, os.Symlink(base, subpath)
726 },
727 expectError: true,
728 },
729 {
730 name: "subpath-symlink-child-outside",
731 prepare: func(base string) ([]string, string, string, error) {
732 volpath, _ := getTestPaths(base)
733 subpathDir := filepath.Join(volpath, "dir0")
734 subpath := filepath.Join(subpathDir, "child0")
735 if err := os.MkdirAll(subpathDir, defaultPerm); err != nil {
736 return nil, "", "", err
737 }
738 return nil, volpath, subpath, os.Symlink(base, subpath)
739 },
740 expectError: true,
741 },
742 {
743 name: "subpath-child-outside-exists",
744 prepare: func(base string) ([]string, string, string, error) {
745 volpath, _ := getTestPaths(base)
746 subpathDir := filepath.Join(volpath, "dir0")
747 child := filepath.Join(base, "child0")
748 subpath := filepath.Join(subpathDir, "child0")
749 if err := os.MkdirAll(volpath, defaultPerm); err != nil {
750 return nil, "", "", err
751 }
752
753 if err := ioutil.WriteFile(child, []byte{}, defaultPerm); err != nil {
754 return nil, "", "", err
755 }
756
757
758 return nil, volpath, subpath, os.Symlink(base, subpathDir)
759 },
760 expectError: true,
761 },
762 {
763 name: "subpath-child-outside-not-exists",
764 prepare: func(base string) ([]string, string, string, error) {
765 volpath, _ := getTestPaths(base)
766 subpathDir := filepath.Join(volpath, "dir0")
767 subpath := filepath.Join(subpathDir, "child0")
768 if err := os.MkdirAll(volpath, defaultPerm); err != nil {
769 return nil, "", "", err
770 }
771
772 return nil, volpath, subpath, os.Symlink(base, subpathDir)
773 },
774 expectError: true,
775 },
776 {
777 name: "subpath-child-outside-exists-middle-dir-symlink",
778 prepare: func(base string) ([]string, string, string, error) {
779 volpath, _ := getTestPaths(base)
780 subpathDir := filepath.Join(volpath, "dir0")
781 symlinkDir := filepath.Join(subpathDir, "linkDir0")
782 child := filepath.Join(base, "child0")
783 subpath := filepath.Join(symlinkDir, "child0")
784 if err := os.MkdirAll(subpathDir, defaultPerm); err != nil {
785 return nil, "", "", err
786 }
787
788 if err := ioutil.WriteFile(child, []byte{}, defaultPerm); err != nil {
789 return nil, "", "", err
790 }
791
792
793 return nil, volpath, subpath, os.Symlink(base, symlinkDir)
794 },
795 expectError: true,
796 },
797 {
798 name: "subpath-backstepping",
799 prepare: func(base string) ([]string, string, string, error) {
800 volpath, _ := getTestPaths(base)
801 subpath := filepath.Join(volpath, "dir0")
802 symlinkBase := filepath.Join(volpath, "..")
803 if err := os.MkdirAll(volpath, defaultPerm); err != nil {
804 return nil, "", "", err
805 }
806
807
808 return nil, volpath, subpath, os.Symlink(symlinkBase, subpath)
809 },
810 expectError: true,
811 },
812 {
813 name: "subpath-mountdir-already-exists",
814 prepare: func(base string) ([]string, string, string, error) {
815 volpath, subpathMount := getTestPaths(base)
816 if err := os.MkdirAll(subpathMount, defaultPerm); err != nil {
817 return nil, "", "", err
818 }
819
820 subpath := filepath.Join(volpath, "dir0")
821 return nil, volpath, subpath, os.MkdirAll(subpath, defaultPerm)
822 },
823 expectError: false,
824 },
825 {
826 name: "subpath-mount-already-exists",
827 prepare: func(base string) ([]string, string, string, error) {
828 volpath, subpathMount := getTestPaths(base)
829 mounts := []string{subpathMount}
830 if err := os.MkdirAll(subpathMount, defaultPerm); err != nil {
831 return nil, "", "", err
832 }
833
834 subpath := filepath.Join(volpath, "dir0")
835 return mounts, volpath, subpath, os.MkdirAll(subpath, defaultPerm)
836 },
837 expectError: false,
838 },
839 {
840 name: "mount-unix-socket",
841 prepare: func(base string) ([]string, string, string, error) {
842 volpath, subpathMount := getTestPaths(base)
843 mounts := []string{subpathMount}
844 if err := os.MkdirAll(volpath, defaultPerm); err != nil {
845 return nil, "", "", err
846 }
847
848 socketFile, socketCreateError := createSocketFile(volpath)
849
850 return mounts, volpath, socketFile, socketCreateError
851 },
852 expectError: false,
853 },
854 {
855 name: "subpath-mounting-fifo",
856 prepare: func(base string) ([]string, string, string, error) {
857 volpath, subpathMount := getTestPaths(base)
858 mounts := []string{subpathMount}
859 if err := os.MkdirAll(volpath, defaultPerm); err != nil {
860 return nil, "", "", err
861 }
862
863 testFifo := filepath.Join(volpath, "mount_test.fifo")
864 err := syscall.Mkfifo(testFifo, 0)
865 return mounts, volpath, testFifo, err
866 },
867 expectError: false,
868 },
869 }
870
871 for _, test := range tests {
872 klog.V(4).Infof("test %q", test.name)
873 base, err := ioutil.TempDir("", "bind-subpath-"+test.name+"-")
874 if err != nil {
875 t.Fatalf(err.Error())
876 }
877
878 mounts, volPath, subPath, err := test.prepare(base)
879 if err != nil {
880 os.RemoveAll(base)
881 t.Fatalf("failed to prepare test %q: %v", test.name, err.Error())
882 }
883
884 fm := setupFakeMounter(mounts)
885
886 subpath := Subpath{
887 VolumeMountIndex: testSubpath,
888 Path: subPath,
889 VolumeName: testVol,
890 VolumePath: volPath,
891 PodDir: filepath.Join(base, "pod0"),
892 ContainerName: testContainer,
893 }
894
895 _, subpathMount := getTestPaths(base)
896 bindPathTarget, err := doBindSubPath(fm, subpath)
897 if test.expectError {
898 if err == nil {
899 t.Errorf("test %q failed: expected error, got success", test.name)
900 }
901 if bindPathTarget != "" {
902 t.Errorf("test %q failed: expected empty bindPathTarget, got %v", test.name, bindPathTarget)
903 }
904 if err = validateDirNotExists(subpathMount); err != nil {
905 t.Errorf("test %q failed: %v", test.name, err)
906 }
907 }
908 if !test.expectError {
909 if err != nil {
910 t.Errorf("test %q failed: %v", test.name, err)
911 }
912 if bindPathTarget != subpathMount {
913 t.Errorf("test %q failed: expected bindPathTarget %v, got %v", test.name, subpathMount, bindPathTarget)
914 }
915 if err = validateFileExists(subpathMount); err != nil {
916 t.Errorf("test %q failed: %v", test.name, err)
917 }
918 }
919
920 os.RemoveAll(base)
921 }
922 }
923
924 func TestSubpath_PrepareSafeSubpath(t *testing.T) {
925
926 defaultPerm := os.FileMode(0750)
927
928 tests := []struct {
929 name string
930
931
932 prepare func(base string) ([]string, string, string, error)
933 expectError bool
934 expectAction []mount.FakeAction
935 mountExists bool
936 }{
937 {
938 name: "subpath-mount-already-exists-with-mismatching-mount",
939 prepare: func(base string) ([]string, string, string, error) {
940 volpath, subpathMount := getTestPaths(base)
941 mounts := []string{subpathMount}
942 if err := os.MkdirAll(subpathMount, defaultPerm); err != nil {
943 return nil, "", "", err
944 }
945
946 subpath := filepath.Join(volpath, "dir0")
947 return mounts, volpath, subpath, os.MkdirAll(subpath, defaultPerm)
948 },
949 expectError: false,
950 expectAction: []mount.FakeAction{{Action: "unmount"}},
951 mountExists: false,
952 },
953 {
954 name: "subpath-mount-already-exists-with-samefile",
955 prepare: func(base string) ([]string, string, string, error) {
956 volpath, subpathMount := getTestPaths(base)
957 mounts := []string{subpathMount}
958 subpathMountRoot := filepath.Dir(subpathMount)
959
960 if err := os.MkdirAll(subpathMountRoot, defaultPerm); err != nil {
961 return nil, "", "", err
962 }
963 targetFile, err := os.Create(subpathMount)
964 if err != nil {
965 return nil, "", "", err
966 }
967 defer targetFile.Close()
968
969 if err := os.MkdirAll(volpath, defaultPerm); err != nil {
970 return nil, "", "", err
971 }
972 subpath := filepath.Join(volpath, "file0")
973
974 err = os.Link(subpathMount, subpath)
975 if err != nil {
976 return nil, "", "", err
977 }
978 return mounts, volpath, subpath, nil
979 },
980 expectError: false,
981 expectAction: []mount.FakeAction{},
982 mountExists: true,
983 },
984 }
985 for _, test := range tests {
986 klog.V(4).Infof("test %q", test.name)
987 base, err := ioutil.TempDir("", "bind-subpath-"+test.name+"-")
988 if err != nil {
989 t.Fatalf(err.Error())
990 }
991 defer os.RemoveAll(base)
992
993 mounts, volPath, subPath, err := test.prepare(base)
994 if err != nil {
995 os.RemoveAll(base)
996 t.Fatalf("failed to prepare test %q: %v", test.name, err.Error())
997 }
998
999 fm := setupFakeMounter(mounts)
1000
1001 subpath := Subpath{
1002 VolumeMountIndex: testSubpath,
1003 Path: subPath,
1004 VolumeName: testVol,
1005 VolumePath: volPath,
1006 PodDir: filepath.Join(base, "pod0"),
1007 ContainerName: testContainer,
1008 }
1009
1010 _, subpathMount := getTestPaths(base)
1011 bindMountExists, bindPathTarget, err := prepareSubpathTarget(fm, subpath)
1012
1013 if bindMountExists != test.mountExists {
1014 t.Errorf("test %q failed: expected bindMountExists %v, got %v", test.name, test.mountExists, bindMountExists)
1015 }
1016
1017 logActions := fm.GetLog()
1018 if len(test.expectAction) == 0 && len(logActions) > 0 {
1019 t.Errorf("test %q failed: expected no actions, got %v", test.name, logActions)
1020 }
1021
1022 if len(test.expectAction) > 0 {
1023 foundMatchingAction := false
1024 testAction := test.expectAction[0]
1025 for _, action := range logActions {
1026 if action.Action == testAction.Action {
1027 foundMatchingAction = true
1028 break
1029 }
1030 }
1031 if !foundMatchingAction {
1032 t.Errorf("test %q failed: expected action %q, got %v", test.name, testAction.Action, logActions)
1033 }
1034 }
1035
1036 if test.expectError {
1037 if err == nil {
1038 t.Errorf("test %q failed: expected error, got success", test.name)
1039 }
1040 if bindPathTarget != "" {
1041 t.Errorf("test %q failed: expected empty bindPathTarget, got %v", test.name, bindPathTarget)
1042 }
1043 if err = validateDirNotExists(subpathMount); err != nil {
1044 t.Errorf("test %q failed: %v", test.name, err)
1045 }
1046 }
1047 if !test.expectError {
1048 if err != nil {
1049 t.Errorf("test %q failed: %v", test.name, err)
1050 }
1051 if bindPathTarget != subpathMount {
1052 t.Errorf("test %q failed: expected bindPathTarget %v, got %v", test.name, subpathMount, bindPathTarget)
1053 }
1054 if err = validateFileExists(subpathMount); err != nil {
1055 t.Errorf("test %q failed: %v", test.name, err)
1056 }
1057 }
1058 }
1059 }
1060
1061 func TestSafeOpen(t *testing.T) {
1062 defaultPerm := os.FileMode(0750)
1063
1064 tests := []struct {
1065 name string
1066
1067
1068 prepare func(base string) error
1069 path string
1070 expectError bool
1071 }{
1072 {
1073 "directory-does-not-exist",
1074 func(base string) error {
1075 return nil
1076 },
1077 "test/directory",
1078 true,
1079 },
1080 {
1081 "directory-exists",
1082 func(base string) error {
1083 return os.MkdirAll(filepath.Join(base, "test/directory"), 0750)
1084 },
1085 "test/directory",
1086 false,
1087 },
1088 {
1089 "escape-base-using-dots",
1090 func(base string) error {
1091 return nil
1092 },
1093 "..",
1094 true,
1095 },
1096 {
1097 "escape-base-using-dots-2",
1098 func(base string) error {
1099 return os.MkdirAll(filepath.Join(base, "test"), 0750)
1100 },
1101 "test/../../..",
1102 true,
1103 },
1104 {
1105 "symlink",
1106 func(base string) error {
1107 if err := os.MkdirAll(filepath.Join(base, "destination"), defaultPerm); err != nil {
1108 return err
1109 }
1110 return os.Symlink("destination", filepath.Join(base, "test"))
1111 },
1112 "test",
1113 true,
1114 },
1115 {
1116 "symlink-nested",
1117 func(base string) error {
1118 if err := os.MkdirAll(filepath.Join(base, "dir1/dir2"), defaultPerm); err != nil {
1119 return err
1120 }
1121 return os.Symlink("dir1", filepath.Join(base, "dir1/dir2/test"))
1122 },
1123 "test",
1124 true,
1125 },
1126 {
1127 "symlink-loop",
1128 func(base string) error {
1129 return os.Symlink("test", filepath.Join(base, "test"))
1130 },
1131 "test",
1132 true,
1133 },
1134 {
1135 "symlink-not-exists",
1136 func(base string) error {
1137 return os.Symlink("non-existing", filepath.Join(base, "test"))
1138 },
1139 "test",
1140 true,
1141 },
1142 {
1143 "non-directory",
1144 func(base string) error {
1145 return ioutil.WriteFile(filepath.Join(base, "test"), []byte{}, defaultPerm)
1146 },
1147 "test/directory",
1148 true,
1149 },
1150 {
1151 "non-directory-final",
1152 func(base string) error {
1153 return ioutil.WriteFile(filepath.Join(base, "test"), []byte{}, defaultPerm)
1154 },
1155 "test",
1156 false,
1157 },
1158 {
1159 "escape-with-relative-symlink",
1160 func(base string) error {
1161 if err := os.MkdirAll(filepath.Join(base, "dir"), defaultPerm); err != nil {
1162 return err
1163 }
1164 if err := os.MkdirAll(filepath.Join(base, "exists"), defaultPerm); err != nil {
1165 return err
1166 }
1167 return os.Symlink("../exists", filepath.Join(base, "dir/test"))
1168 },
1169 "dir/test",
1170 true,
1171 },
1172 {
1173 "escape-with-relative-symlink-not-exists",
1174 func(base string) error {
1175 if err := os.MkdirAll(filepath.Join(base, "dir"), defaultPerm); err != nil {
1176 return err
1177 }
1178 return os.Symlink("../not-exists", filepath.Join(base, "dir/test"))
1179 },
1180 "dir/test",
1181 true,
1182 },
1183 {
1184 "escape-with-symlink",
1185 func(base string) error {
1186 return os.Symlink("/", filepath.Join(base, "test"))
1187 },
1188 "test",
1189 true,
1190 },
1191 {
1192 "mount-unix-socket",
1193 func(base string) error {
1194 socketFile, socketError := createSocketFile(base)
1195
1196 if socketError != nil {
1197 return fmt.Errorf("error preparing socket file %s with %w", socketFile, socketError)
1198 }
1199 return nil
1200 },
1201 "mt.sock",
1202 false,
1203 },
1204 {
1205 "mounting-unix-socket-in-middle",
1206 func(base string) error {
1207 testSocketFile, socketError := createSocketFile(base)
1208
1209 if socketError != nil {
1210 return fmt.Errorf("error preparing socket file %s with %w", testSocketFile, socketError)
1211 }
1212 return nil
1213 },
1214 "mt.sock/bar",
1215 true,
1216 },
1217 }
1218
1219 for _, test := range tests {
1220 klog.V(4).Infof("test %q", test.name)
1221 base, err := ioutil.TempDir("", "safe-open-"+test.name+"-")
1222 if err != nil {
1223 t.Fatalf(err.Error())
1224 }
1225
1226 test.prepare(base)
1227 pathToCreate := filepath.Join(base, test.path)
1228 fd, err := doSafeOpen(pathToCreate, base)
1229 if err != nil && !test.expectError {
1230 t.Errorf("test %q: %s", test.name, err)
1231 }
1232 if err != nil {
1233 klog.Infof("got error: %s", err)
1234 }
1235 if err == nil && test.expectError {
1236 t.Errorf("test %q: expected error, got none", test.name)
1237 }
1238
1239 syscall.Close(fd)
1240 os.RemoveAll(base)
1241 }
1242 }
1243
1244 func createSocketFile(socketDir string) (string, error) {
1245 testSocketFile := filepath.Join(socketDir, "mt.sock")
1246
1247
1248
1249
1250 oldDir, _ := os.Getwd()
1251
1252 err := os.Chdir(socketDir)
1253 if err != nil {
1254 return "", err
1255 }
1256 defer func() {
1257 os.Chdir(oldDir)
1258 }()
1259 _, socketCreateError := net.Listen("unix", "mt.sock")
1260 return testSocketFile, socketCreateError
1261 }
1262
1263 func TestFindExistingPrefix(t *testing.T) {
1264 defaultPerm := os.FileMode(0750)
1265 tests := []struct {
1266 name string
1267
1268
1269 prepare func(base string) error
1270 path string
1271 expectedPath string
1272 expectedDirs []string
1273 expectError bool
1274 }{
1275 {
1276 "directory-does-not-exist",
1277 func(base string) error {
1278 return nil
1279 },
1280 "directory",
1281 "",
1282 []string{"directory"},
1283 false,
1284 },
1285 {
1286 "directory-exists",
1287 func(base string) error {
1288 return os.MkdirAll(filepath.Join(base, "test/directory"), 0750)
1289 },
1290 "test/directory",
1291 "test/directory",
1292 []string{},
1293 false,
1294 },
1295 {
1296 "follow-symlinks",
1297 func(base string) error {
1298 if err := os.MkdirAll(filepath.Join(base, "destination/directory"), defaultPerm); err != nil {
1299 return err
1300 }
1301 return os.Symlink("destination", filepath.Join(base, "test"))
1302 },
1303 "test/directory",
1304 "test/directory",
1305 []string{},
1306 false,
1307 },
1308 {
1309 "follow-symlink-loop",
1310 func(base string) error {
1311 return os.Symlink("test", filepath.Join(base, "test"))
1312 },
1313 "test/directory",
1314 "",
1315 nil,
1316 true,
1317 },
1318 {
1319 "follow-symlink-multiple follow",
1320 func(base string) error {
1321
1322 if err := os.MkdirAll(filepath.Join(base, "test1"), defaultPerm); err != nil {
1323 return err
1324 }
1325 if err := os.MkdirAll(filepath.Join(base, "test2"), defaultPerm); err != nil {
1326 return err
1327 }
1328 if err := os.Symlink(filepath.Join(base, "test2"), filepath.Join(base, "test1/dir")); err != nil {
1329 return err
1330 }
1331 if err := os.Symlink(filepath.Join(base, "test1"), filepath.Join(base, "test2/dir")); err != nil {
1332 return err
1333 }
1334 return nil
1335 },
1336 "test1/dir/dir/foo/bar",
1337 "test1/dir/dir",
1338 []string{"foo", "bar"},
1339 false,
1340 },
1341 {
1342 "danglink-symlink",
1343 func(base string) error {
1344 return os.Symlink("non-existing", filepath.Join(base, "test"))
1345 },
1346
1347
1348 "test/directory",
1349 "",
1350 []string{"test", "directory"},
1351 false,
1352 },
1353 {
1354 "with-fifo-in-middle",
1355 func(base string) error {
1356 testFifo := filepath.Join(base, "mount_test.fifo")
1357 return syscall.Mkfifo(testFifo, 0)
1358 },
1359 "mount_test.fifo/directory",
1360 "",
1361 nil,
1362 true,
1363 },
1364 }
1365
1366 for _, test := range tests {
1367 klog.V(4).Infof("test %q", test.name)
1368 base, err := ioutil.TempDir("", "find-prefix-"+test.name+"-")
1369 if err != nil {
1370 t.Fatalf(err.Error())
1371 }
1372 test.prepare(base)
1373 path := filepath.Join(base, test.path)
1374 existingPath, dirs, err := findExistingPrefix(base, path)
1375 if err != nil && !test.expectError {
1376 t.Errorf("test %q: %s", test.name, err)
1377 }
1378 if err != nil {
1379 klog.Infof("got error: %s", err)
1380 }
1381 if err == nil && test.expectError {
1382 t.Errorf("test %q: expected error, got none", test.name)
1383 }
1384
1385 fullExpectedPath := filepath.Join(base, test.expectedPath)
1386 if existingPath != fullExpectedPath {
1387 t.Errorf("test %q: expected path %q, got %q", test.name, fullExpectedPath, existingPath)
1388 }
1389 if !reflect.DeepEqual(dirs, test.expectedDirs) {
1390 t.Errorf("test %q: expected dirs %v, got %v", test.name, test.expectedDirs, dirs)
1391 }
1392 os.RemoveAll(base)
1393 }
1394 }
1395
1396 func validateDirEmpty(dir string) error {
1397 files, err := ioutil.ReadDir(dir)
1398 if err != nil {
1399 return err
1400 }
1401
1402 if len(files) != 0 {
1403 return fmt.Errorf("directory %q is not empty", dir)
1404 }
1405 return nil
1406 }
1407
1408 func validateDirExists(dir string) error {
1409 _, err := ioutil.ReadDir(dir)
1410 if err != nil {
1411 return err
1412 }
1413 return nil
1414 }
1415
1416 func validateDirNotExists(dir string) error {
1417 _, err := ioutil.ReadDir(dir)
1418 if os.IsNotExist(err) {
1419 return nil
1420 }
1421 if err != nil {
1422 return err
1423 }
1424 return fmt.Errorf("dir %q still exists", dir)
1425 }
1426
1427 func validateFileExists(file string) error {
1428 if _, err := os.Stat(file); err != nil {
1429 return err
1430 }
1431 return nil
1432 }
1433
View as plain text