github.com/demonoid81/moby@v0.0.0-20200517203328-62dd8e17c460/pkg/symlink/fs_unix_test.go (about) 1 // +build !windows 2 3 // Licensed under the Apache License, Version 2.0; See LICENSE.APACHE 4 5 package symlink // import "github.com/demonoid81/moby/pkg/symlink" 6 7 import ( 8 "fmt" 9 "io/ioutil" 10 "os" 11 "path/filepath" 12 "testing" 13 ) 14 15 // TODO Windows: This needs some serious work to port to Windows. For now, 16 // turning off testing in this package. 17 18 type dirOrLink struct { 19 path string 20 target string 21 } 22 23 func makeFs(tmpdir string, fs []dirOrLink) error { 24 for _, s := range fs { 25 s.path = filepath.Join(tmpdir, s.path) 26 if s.target == "" { 27 os.MkdirAll(s.path, 0755) 28 continue 29 } 30 if err := os.MkdirAll(filepath.Dir(s.path), 0755); err != nil { 31 return err 32 } 33 if err := os.Symlink(s.target, s.path); err != nil && !os.IsExist(err) { 34 return err 35 } 36 } 37 return nil 38 } 39 40 func testSymlink(tmpdir, path, expected, scope string) error { 41 rewrite, err := FollowSymlinkInScope(filepath.Join(tmpdir, path), filepath.Join(tmpdir, scope)) 42 if err != nil { 43 return err 44 } 45 expected, err = filepath.Abs(filepath.Join(tmpdir, expected)) 46 if err != nil { 47 return err 48 } 49 if expected != rewrite { 50 return fmt.Errorf("Expected %q got %q", expected, rewrite) 51 } 52 return nil 53 } 54 55 func TestFollowSymlinkAbsolute(t *testing.T) { 56 tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkAbsolute") 57 if err != nil { 58 t.Fatal(err) 59 } 60 defer os.RemoveAll(tmpdir) 61 if err := makeFs(tmpdir, []dirOrLink{{path: "testdata/fs/a/d", target: "/b"}}); err != nil { 62 t.Fatal(err) 63 } 64 if err := testSymlink(tmpdir, "testdata/fs/a/d/c/data", "testdata/b/c/data", "testdata"); err != nil { 65 t.Fatal(err) 66 } 67 } 68 69 func TestFollowSymlinkRelativePath(t *testing.T) { 70 tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkRelativePath") 71 if err != nil { 72 t.Fatal(err) 73 } 74 defer os.RemoveAll(tmpdir) 75 if err := makeFs(tmpdir, []dirOrLink{{path: "testdata/fs/i", target: "a"}}); err != nil { 76 t.Fatal(err) 77 } 78 if err := testSymlink(tmpdir, "testdata/fs/i", "testdata/fs/a", "testdata"); err != nil { 79 t.Fatal(err) 80 } 81 } 82 83 func TestFollowSymlinkSkipSymlinksOutsideScope(t *testing.T) { 84 tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkSkipSymlinksOutsideScope") 85 if err != nil { 86 t.Fatal(err) 87 } 88 defer os.RemoveAll(tmpdir) 89 if err := makeFs(tmpdir, []dirOrLink{ 90 {path: "linkdir", target: "realdir"}, 91 {path: "linkdir/foo/bar"}, 92 }); err != nil { 93 t.Fatal(err) 94 } 95 if err := testSymlink(tmpdir, "linkdir/foo/bar", "linkdir/foo/bar", "linkdir/foo"); err != nil { 96 t.Fatal(err) 97 } 98 } 99 100 func TestFollowSymlinkInvalidScopePathPair(t *testing.T) { 101 if _, err := FollowSymlinkInScope("toto", "testdata"); err == nil { 102 t.Fatal("expected an error") 103 } 104 } 105 106 func TestFollowSymlinkLastLink(t *testing.T) { 107 tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkLastLink") 108 if err != nil { 109 t.Fatal(err) 110 } 111 defer os.RemoveAll(tmpdir) 112 if err := makeFs(tmpdir, []dirOrLink{{path: "testdata/fs/a/d", target: "/b"}}); err != nil { 113 t.Fatal(err) 114 } 115 if err := testSymlink(tmpdir, "testdata/fs/a/d", "testdata/b", "testdata"); err != nil { 116 t.Fatal(err) 117 } 118 } 119 120 func TestFollowSymlinkRelativeLinkChangeScope(t *testing.T) { 121 tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkRelativeLinkChangeScope") 122 if err != nil { 123 t.Fatal(err) 124 } 125 defer os.RemoveAll(tmpdir) 126 if err := makeFs(tmpdir, []dirOrLink{{path: "testdata/fs/a/e", target: "../b"}}); err != nil { 127 t.Fatal(err) 128 } 129 if err := testSymlink(tmpdir, "testdata/fs/a/e/c/data", "testdata/fs/b/c/data", "testdata"); err != nil { 130 t.Fatal(err) 131 } 132 // avoid letting allowing symlink e lead us to ../b 133 // normalize to the "testdata/fs/a" 134 if err := testSymlink(tmpdir, "testdata/fs/a/e", "testdata/fs/a/b", "testdata/fs/a"); err != nil { 135 t.Fatal(err) 136 } 137 } 138 139 func TestFollowSymlinkDeepRelativeLinkChangeScope(t *testing.T) { 140 tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkDeepRelativeLinkChangeScope") 141 if err != nil { 142 t.Fatal(err) 143 } 144 defer os.RemoveAll(tmpdir) 145 146 if err := makeFs(tmpdir, []dirOrLink{{path: "testdata/fs/a/f", target: "../../../../test"}}); err != nil { 147 t.Fatal(err) 148 } 149 // avoid letting symlink f lead us out of the "testdata" scope 150 // we don't normalize because symlink f is in scope and there is no 151 // information leak 152 if err := testSymlink(tmpdir, "testdata/fs/a/f", "testdata/test", "testdata"); err != nil { 153 t.Fatal(err) 154 } 155 // avoid letting symlink f lead us out of the "testdata/fs" scope 156 // we don't normalize because symlink f is in scope and there is no 157 // information leak 158 if err := testSymlink(tmpdir, "testdata/fs/a/f", "testdata/fs/test", "testdata/fs"); err != nil { 159 t.Fatal(err) 160 } 161 } 162 163 func TestFollowSymlinkRelativeLinkChain(t *testing.T) { 164 tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkRelativeLinkChain") 165 if err != nil { 166 t.Fatal(err) 167 } 168 defer os.RemoveAll(tmpdir) 169 170 // avoid letting symlink g (pointed at by symlink h) take out of scope 171 // TODO: we should probably normalize to scope here because ../[....]/root 172 // is out of scope and we leak information 173 if err := makeFs(tmpdir, []dirOrLink{ 174 {path: "testdata/fs/b/h", target: "../g"}, 175 {path: "testdata/fs/g", target: "../../../../../../../../../../../../root"}, 176 }); err != nil { 177 t.Fatal(err) 178 } 179 if err := testSymlink(tmpdir, "testdata/fs/b/h", "testdata/root", "testdata"); err != nil { 180 t.Fatal(err) 181 } 182 } 183 184 func TestFollowSymlinkBreakoutPath(t *testing.T) { 185 tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkBreakoutPath") 186 if err != nil { 187 t.Fatal(err) 188 } 189 defer os.RemoveAll(tmpdir) 190 191 // avoid letting symlink -> ../directory/file escape from scope 192 // normalize to "testdata/fs/j" 193 if err := makeFs(tmpdir, []dirOrLink{{path: "testdata/fs/j/k", target: "../i/a"}}); err != nil { 194 t.Fatal(err) 195 } 196 if err := testSymlink(tmpdir, "testdata/fs/j/k", "testdata/fs/j/i/a", "testdata/fs/j"); err != nil { 197 t.Fatal(err) 198 } 199 } 200 201 func TestFollowSymlinkToRoot(t *testing.T) { 202 tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkToRoot") 203 if err != nil { 204 t.Fatal(err) 205 } 206 defer os.RemoveAll(tmpdir) 207 208 // make sure we don't allow escaping to / 209 // normalize to dir 210 if err := makeFs(tmpdir, []dirOrLink{{path: "foo", target: "/"}}); err != nil { 211 t.Fatal(err) 212 } 213 if err := testSymlink(tmpdir, "foo", "", ""); err != nil { 214 t.Fatal(err) 215 } 216 } 217 218 func TestFollowSymlinkSlashDotdot(t *testing.T) { 219 tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkSlashDotdot") 220 if err != nil { 221 t.Fatal(err) 222 } 223 defer os.RemoveAll(tmpdir) 224 tmpdir = filepath.Join(tmpdir, "dir", "subdir") 225 226 // make sure we don't allow escaping to / 227 // normalize to dir 228 if err := makeFs(tmpdir, []dirOrLink{{path: "foo", target: "/../../"}}); err != nil { 229 t.Fatal(err) 230 } 231 if err := testSymlink(tmpdir, "foo", "", ""); err != nil { 232 t.Fatal(err) 233 } 234 } 235 236 func TestFollowSymlinkDotdot(t *testing.T) { 237 tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkDotdot") 238 if err != nil { 239 t.Fatal(err) 240 } 241 defer os.RemoveAll(tmpdir) 242 tmpdir = filepath.Join(tmpdir, "dir", "subdir") 243 244 // make sure we stay in scope without leaking information 245 // this also checks for escaping to / 246 // normalize to dir 247 if err := makeFs(tmpdir, []dirOrLink{{path: "foo", target: "../../"}}); err != nil { 248 t.Fatal(err) 249 } 250 if err := testSymlink(tmpdir, "foo", "", ""); err != nil { 251 t.Fatal(err) 252 } 253 } 254 255 func TestFollowSymlinkRelativePath2(t *testing.T) { 256 tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkRelativePath2") 257 if err != nil { 258 t.Fatal(err) 259 } 260 defer os.RemoveAll(tmpdir) 261 262 if err := makeFs(tmpdir, []dirOrLink{{path: "bar/foo", target: "baz/target"}}); err != nil { 263 t.Fatal(err) 264 } 265 if err := testSymlink(tmpdir, "bar/foo", "bar/baz/target", ""); err != nil { 266 t.Fatal(err) 267 } 268 } 269 270 func TestFollowSymlinkScopeLink(t *testing.T) { 271 tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkScopeLink") 272 if err != nil { 273 t.Fatal(err) 274 } 275 defer os.RemoveAll(tmpdir) 276 277 if err := makeFs(tmpdir, []dirOrLink{ 278 {path: "root2"}, 279 {path: "root", target: "root2"}, 280 {path: "root2/foo", target: "../bar"}, 281 }); err != nil { 282 t.Fatal(err) 283 } 284 if err := testSymlink(tmpdir, "root/foo", "root/bar", "root"); err != nil { 285 t.Fatal(err) 286 } 287 } 288 289 func TestFollowSymlinkRootScope(t *testing.T) { 290 tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkRootScope") 291 if err != nil { 292 t.Fatal(err) 293 } 294 defer os.RemoveAll(tmpdir) 295 296 expected, err := filepath.EvalSymlinks(tmpdir) 297 if err != nil { 298 t.Fatal(err) 299 } 300 rewrite, err := FollowSymlinkInScope(tmpdir, "/") 301 if err != nil { 302 t.Fatal(err) 303 } 304 if rewrite != expected { 305 t.Fatalf("expected %q got %q", expected, rewrite) 306 } 307 } 308 309 func TestFollowSymlinkEmpty(t *testing.T) { 310 res, err := FollowSymlinkInScope("", "") 311 if err != nil { 312 t.Fatal(err) 313 } 314 wd, err := os.Getwd() 315 if err != nil { 316 t.Fatal(err) 317 } 318 if res != wd { 319 t.Fatalf("expected %q got %q", wd, res) 320 } 321 } 322 323 func TestFollowSymlinkCircular(t *testing.T) { 324 tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkCircular") 325 if err != nil { 326 t.Fatal(err) 327 } 328 defer os.RemoveAll(tmpdir) 329 330 if err := makeFs(tmpdir, []dirOrLink{{path: "root/foo", target: "foo"}}); err != nil { 331 t.Fatal(err) 332 } 333 if err := testSymlink(tmpdir, "root/foo", "", "root"); err == nil { 334 t.Fatal("expected an error for foo -> foo") 335 } 336 337 if err := makeFs(tmpdir, []dirOrLink{ 338 {path: "root/bar", target: "baz"}, 339 {path: "root/baz", target: "../bak"}, 340 {path: "root/bak", target: "/bar"}, 341 }); err != nil { 342 t.Fatal(err) 343 } 344 if err := testSymlink(tmpdir, "root/foo", "", "root"); err == nil { 345 t.Fatal("expected an error for bar -> baz -> bak -> bar") 346 } 347 } 348 349 func TestFollowSymlinkComplexChainWithTargetPathsContainingLinks(t *testing.T) { 350 tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkComplexChainWithTargetPathsContainingLinks") 351 if err != nil { 352 t.Fatal(err) 353 } 354 defer os.RemoveAll(tmpdir) 355 356 if err := makeFs(tmpdir, []dirOrLink{ 357 {path: "root2"}, 358 {path: "root", target: "root2"}, 359 {path: "root/a", target: "r/s"}, 360 {path: "root/r", target: "../root/t"}, 361 {path: "root/root/t/s/b", target: "/../u"}, 362 {path: "root/u/c", target: "."}, 363 {path: "root/u/x/y", target: "../v"}, 364 {path: "root/u/v", target: "/../w"}, 365 }); err != nil { 366 t.Fatal(err) 367 } 368 if err := testSymlink(tmpdir, "root/a/b/c/x/y/z", "root/w/z", "root"); err != nil { 369 t.Fatal(err) 370 } 371 } 372 373 func TestFollowSymlinkBreakoutNonExistent(t *testing.T) { 374 tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkBreakoutNonExistent") 375 if err != nil { 376 t.Fatal(err) 377 } 378 defer os.RemoveAll(tmpdir) 379 380 if err := makeFs(tmpdir, []dirOrLink{ 381 {path: "root/slash", target: "/"}, 382 {path: "root/sym", target: "/idontexist/../slash"}, 383 }); err != nil { 384 t.Fatal(err) 385 } 386 if err := testSymlink(tmpdir, "root/sym/file", "root/file", "root"); err != nil { 387 t.Fatal(err) 388 } 389 } 390 391 func TestFollowSymlinkNoLexicalCleaning(t *testing.T) { 392 tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkNoLexicalCleaning") 393 if err != nil { 394 t.Fatal(err) 395 } 396 defer os.RemoveAll(tmpdir) 397 398 if err := makeFs(tmpdir, []dirOrLink{ 399 {path: "root/sym", target: "/foo/bar"}, 400 {path: "root/hello", target: "/sym/../baz"}, 401 }); err != nil { 402 t.Fatal(err) 403 } 404 if err := testSymlink(tmpdir, "root/hello", "root/foo/baz", "root"); err != nil { 405 t.Fatal(err) 406 } 407 }