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