github.com/moby/docker@v26.1.3+incompatible/internal/safepath/join_test.go (about) 1 package safepath 2 3 import ( 4 "context" 5 "os" 6 "path/filepath" 7 "runtime" 8 "strings" 9 "testing" 10 11 "gotest.tools/v3/assert" 12 is "gotest.tools/v3/assert/cmp" 13 ) 14 15 func TestJoinEscapingSymlink(t *testing.T) { 16 type testCase struct { 17 name string 18 target string 19 } 20 var cases []testCase 21 22 if runtime.GOOS == "windows" { 23 cases = []testCase{ 24 {name: "root", target: `C:\`}, 25 {name: "absolute file", target: `C:\Windows\System32\cmd.exe`}, 26 } 27 } else { 28 cases = []testCase{ 29 {name: "root", target: "/"}, 30 {name: "absolute file", target: "/etc/passwd"}, 31 } 32 } 33 cases = append(cases, testCase{name: "relative", target: "../../"}) 34 35 for _, tc := range cases { 36 t.Run(tc.name, func(t *testing.T) { 37 tempDir := t.TempDir() 38 dir, err := filepath.EvalSymlinks(tempDir) 39 assert.NilError(t, err, "filepath.EvalSymlinks failed for temporary directory %s", tempDir) 40 41 err = os.Symlink(tc.target, filepath.Join(dir, "link")) 42 assert.NilError(t, err, "failed to create symlink to %s", tc.target) 43 44 safe, err := Join(context.Background(), dir, "link") 45 if err == nil { 46 safe.Close(context.Background()) 47 } 48 assert.ErrorType(t, err, &ErrEscapesBase{}) 49 }) 50 } 51 } 52 53 func TestJoinGoodSymlink(t *testing.T) { 54 tempDir := t.TempDir() 55 dir, err := filepath.EvalSymlinks(tempDir) 56 assert.NilError(t, err, "filepath.EvalSymlinks failed for temporary directory %s", tempDir) 57 58 assert.Assert(t, os.WriteFile(filepath.Join(dir, "foo"), []byte("bar"), 0o744), "failed to create file 'foo'") 59 assert.Assert(t, os.Mkdir(filepath.Join(dir, "subdir"), 0o744), "failed to create directory 'subdir'") 60 assert.Assert(t, os.WriteFile(filepath.Join(dir, "subdir/hello.txt"), []byte("world"), 0o744), "failed to create file 'subdir/hello.txt'") 61 62 assert.Assert(t, os.Symlink(filepath.Join(dir, "subdir"), filepath.Join(dir, "subdir_link_absolute")), "failed to create absolute symlink to directory 'subdir'") 63 assert.Assert(t, os.Symlink("subdir", filepath.Join(dir, "subdir_link_relative")), "failed to create relative symlink to directory 'subdir'") 64 65 assert.Assert(t, os.Symlink(filepath.Join(dir, "foo"), filepath.Join(dir, "foo_link_absolute")), "failed to create absolute symlink to file 'foo'") 66 assert.Assert(t, os.Symlink("foo", filepath.Join(dir, "foo_link_relative")), "failed to create relative symlink to file 'foo'") 67 68 for _, target := range []string{ 69 "foo", "subdir", 70 "subdir_link_absolute", "foo_link_absolute", 71 "subdir_link_relative", "foo_link_relative", 72 } { 73 t.Run(target, func(t *testing.T) { 74 safe, err := Join(context.Background(), dir, target) 75 assert.NilError(t, err) 76 77 defer safe.Close(context.Background()) 78 if strings.HasPrefix(target, "subdir") { 79 data, err := os.ReadFile(filepath.Join(safe.Path(), "hello.txt")) 80 assert.NilError(t, err) 81 assert.Assert(t, is.Equal(string(data), "world")) 82 } 83 }) 84 } 85 } 86 87 func TestJoinWithSymlinkReplace(t *testing.T) { 88 tempDir := t.TempDir() 89 dir, err := filepath.EvalSymlinks(tempDir) 90 assert.NilError(t, err, "filepath.EvalSymlinks failed for temporary directory %s", tempDir) 91 92 link := filepath.Join(dir, "link") 93 target := filepath.Join(dir, "foo") 94 95 err = os.WriteFile(target, []byte("bar"), 0o744) 96 assert.NilError(t, err, "failed to create test file") 97 98 err = os.Symlink(target, link) 99 assert.Check(t, err, "failed to create symlink to foo") 100 101 safe, err := Join(context.Background(), dir, "link") 102 assert.NilError(t, err) 103 104 defer safe.Close(context.Background()) 105 106 // Delete the link target. 107 err = os.Remove(target) 108 if runtime.GOOS == "windows" { 109 // On Windows it shouldn't be possible. 110 assert.Assert(t, is.ErrorType(err, &os.PathError{}), "link shouldn't be deletable before cleanup") 111 } else { 112 // On Linux we can delete it just fine. 113 assert.NilError(t, err, "failed to remove symlink") 114 115 // Replace target with a symlink to /etc/paswd 116 err = os.Symlink("/etc/passwd", target) 117 assert.NilError(t, err, "failed to create symlink") 118 } 119 120 // The returned safe path should still point to the old file. 121 data, err := os.ReadFile(safe.Path()) 122 assert.NilError(t, err, "failed to read file") 123 124 assert.Check(t, is.Equal(string(data), "bar")) 125 126 } 127 128 func TestJoinCloseInvalidates(t *testing.T) { 129 tempDir := t.TempDir() 130 dir, err := filepath.EvalSymlinks(tempDir) 131 assert.NilError(t, err) 132 133 foo := filepath.Join(dir, "foo") 134 err = os.WriteFile(foo, []byte("bar"), 0o744) 135 assert.NilError(t, err, "failed to create test file") 136 137 safe, err := Join(context.Background(), dir, "foo") 138 assert.NilError(t, err) 139 140 assert.Check(t, safe.IsValid()) 141 142 assert.NilError(t, safe.Close(context.Background())) 143 144 assert.Check(t, !safe.IsValid()) 145 }