github.com/apptainer/singularity@v3.1.1+incompatible/internal/pkg/util/fs/mount/mount_test.go (about) 1 // Copyright (c) 2018, Sylabs Inc. All rights reserved. 2 // This software is licensed under a 3-clause BSD license. Please consult the 3 // LICENSE.md file distributed with the sources of this project regarding your 4 // rights to use or distribute this software. 5 6 package mount 7 8 import ( 9 "fmt" 10 "syscall" 11 "testing" 12 13 specs "github.com/opencontainers/runtime-spec/specs-go" 14 "github.com/sylabs/singularity/internal/pkg/test" 15 ) 16 17 func TestImage(t *testing.T) { 18 test.DropPrivilege(t) 19 defer test.ResetPrivilege(t) 20 21 points := &Points{} 22 23 if err := points.AddImage(RootfsTag, "", "/fake", "ext3", 0, 0, 10); err == nil { 24 t.Errorf("should have failed with empty source") 25 } 26 if err := points.AddImage(RootfsTag, "/fake", "", "ext3", 0, 0, 10); err == nil { 27 t.Errorf("should have failed with empty destination") 28 } 29 30 if err := points.AddImage(RootfsTag, "fake", "/", "ext3", 0, 0, 10); err == nil { 31 t.Errorf("should have failed as source is not an absolute path") 32 } 33 if err := points.AddImage(RootfsTag, "/", "fake", "ext3", 0, 0, 10); err == nil { 34 t.Errorf("should have failed as destination is not an absolute path") 35 } 36 37 if err := points.AddImage(RootfsTag, "", "/", "ext3", 0, 0, 10); err == nil { 38 t.Errorf("should have failed with empty source") 39 } 40 if err := points.AddImage(RootfsTag, "/fake", "/", "xfs", 0, 0, 10); err == nil { 41 t.Errorf("should have failed with bad filesystem type") 42 } 43 if err := points.AddImage(RootfsTag, "/fake", "/", "ext3", syscall.MS_BIND, 0, 10); err == nil { 44 t.Errorf("should have failed with bad bind flag") 45 } 46 if err := points.AddImage(RootfsTag, "/fake", "/", "ext3", syscall.MS_REMOUNT, 0, 10); err == nil { 47 t.Errorf("should have failed with bad remount flag") 48 } 49 if err := points.AddImage(RootfsTag, "/fake", "/", "ext3", syscall.MS_REC, 0, 10); err == nil { 50 t.Errorf("should have failed with bad recursive flag") 51 } 52 if err := points.AddImage(RootfsTag, "/fake", "/ext3", "ext3", 0, 0, 10); err != nil { 53 t.Errorf("should have passed with ext3 filesystem") 54 } 55 points.RemoveAll() 56 if err := points.AddImage(RootfsTag, "/fake", "/squash", "squashfs", 0, 0, 10); err != nil { 57 t.Errorf("should have passed with squashfs filesystem") 58 } 59 if err := points.AddImage(RootfsTag, "/fake", "/", "squashfs", 0, 0, 0); err == nil { 60 t.Errorf("should have failed with 0 size limit") 61 } 62 if err := points.AddImage(RootfsTag, "/fake", "/squash", "squashfs", 0, 0, 10); err == nil { 63 t.Errorf("nil error returned, should have returned non-nil mount.ErrMountExists") 64 } else if err != nil && err != ErrMountExists { 65 t.Errorf("non-nil error should have been mount.ErrMountExists") 66 } 67 points.RemoveAll() 68 69 if err := points.AddImage(RootfsTag, "/fake", "/", "squashfs", syscall.MS_NOSUID, 31, 10); err != nil { 70 t.Fatalf("should have passed with squashfs filesystem") 71 } 72 images := points.GetAllImages() 73 if len(images) != 1 { 74 t.Fatalf("should get only one registered image") 75 } 76 hasNoSuid := false 77 for _, option := range images[0].Options { 78 if option == "nosuid" { 79 hasNoSuid = true 80 } 81 } 82 if offset, err := GetOffset(images[0].InternalOptions); err != nil || offset != 31 { 83 t.Errorf("offset option wasn't found or is invalid") 84 } 85 if size, err := GetSizeLimit(images[0].InternalOptions); err != nil || size != 10 { 86 t.Errorf("sizelimit option wasn't found or is invalid") 87 } 88 if _, err := GetOffset([]string{}); err == nil { 89 t.Errorf("should have failed, offset not provided") 90 } 91 if _, err := GetSizeLimit([]string{}); err == nil { 92 t.Errorf("should have failed, sizelimit not provided") 93 } 94 if !hasNoSuid { 95 t.Errorf("nosuid option wasn't applied") 96 } 97 points.RemoveByDest("/") 98 if len(points.GetAllImages()) != 0 { 99 t.Errorf("failed to remove image from mount point") 100 } 101 } 102 103 func TestOverlay(t *testing.T) { 104 test.DropPrivilege(t) 105 defer test.ResetPrivilege(t) 106 107 points := &Points{} 108 109 if err := points.AddOverlay(LayerTag, "", 0, "/", "", ""); err == nil { 110 t.Errorf("should have failed with empty destination") 111 } 112 if err := points.AddOverlay(LayerTag, "/fake", 0, "", "/upper", "/work"); err == nil { 113 t.Errorf("should have failed with empty lowerdir") 114 } 115 if err := points.AddOverlay(LayerTag, "/fake", 0, "/lower", "/upper", ""); err == nil { 116 t.Errorf("should have failed with empty workdir") 117 } 118 119 if err := points.AddOverlay(LayerTag, "/", 0, "lower", "", ""); err == nil { 120 t.Errorf("should have failed as lowerdir is not an absolute path") 121 } 122 if err := points.AddOverlay(LayerTag, "/", 0, "/lower", "upper", "/work"); err == nil { 123 t.Errorf("should have failed as upperdir is not an absolute path") 124 } 125 if err := points.AddOverlay(LayerTag, "/", 0, "/lower", "/upper", "work"); err == nil { 126 t.Errorf("should have failed as workdir is not an absolute path") 127 } 128 129 if err := points.AddOverlay(LayerTag, "/fake", syscall.MS_BIND, "/lower", "", ""); err == nil { 130 t.Errorf("should have failed with bad bind flag") 131 } 132 if err := points.AddOverlay(LayerTag, "/fake", syscall.MS_REMOUNT, "/lower", "", ""); err == nil { 133 t.Errorf("should have failed with bad remount flag") 134 } 135 if err := points.AddOverlay(LayerTag, "/fake", syscall.MS_REC, "/lower", "", ""); err == nil { 136 t.Errorf("should have failed with bad recursive flag") 137 } 138 points.RemoveAll() 139 140 if err := points.AddOverlay(LayerTag, "/fake", 0, "/lower", "", ""); err != nil { 141 t.Errorf("%s", err) 142 } 143 points.RemoveAll() 144 145 if err := points.AddOverlay(LayerTag, "/fake", 0, "/lower", "/upper", "/work"); err != nil { 146 t.Errorf("%s", err) 147 } 148 points.RemoveAll() 149 150 if err := points.AddOverlay(LayerTag, "/mnt", syscall.MS_NOSUID, "/lower", "/upper", "/work"); err != nil { 151 t.Fatalf("%s", err) 152 } 153 154 overlay := points.GetByDest("/mnt") 155 if len(overlay) != 1 { 156 t.Fatalf("one filesystem mount points should be returned") 157 } 158 hasNoSuid := false 159 for _, option := range overlay[0].Options { 160 if option == "nosuid" { 161 hasNoSuid = true 162 } 163 } 164 if !hasNoSuid { 165 t.Errorf("option nosuid not applied for /mnt") 166 } 167 } 168 169 func TestFS(t *testing.T) { 170 test.DropPrivilege(t) 171 defer test.ResetPrivilege(t) 172 173 points := &Points{} 174 175 if err := points.AddFS(SessionTag, "", "tmpfs", 0, ""); err == nil { 176 t.Errorf("should have failed with empty destination") 177 } 178 if err := points.AddFS(SessionTag, "fake", "tmpfs", 0, ""); err == nil { 179 t.Errorf("should have failed as destination is not an absolute path") 180 } 181 182 if err := points.AddFS(SessionTag, "fake", "tmpfs", syscall.MS_BIND, ""); err == nil { 183 t.Errorf("should have failed with bad bind flag") 184 } 185 if err := points.AddFS(SessionTag, "fake", "tmpfs", syscall.MS_REMOUNT, ""); err == nil { 186 t.Errorf("should have failed with bad remount flag") 187 } 188 if err := points.AddFS(SessionTag, "fake", "tmpfs", syscall.MS_REC, ""); err == nil { 189 t.Errorf("should have failed with bad recursive flag") 190 } 191 192 points.RemoveAll() 193 194 if err := points.AddFS(SessionTag, "/fields/of", "cows", 0, ""); err == nil { 195 t.Errorf("should have failed as filesystem is not authorized") 196 } 197 198 fs := points.GetAllFS() 199 if len(fs) != 0 { 200 t.Errorf("no filesystem mount points should be returned") 201 } 202 points.RemoveAll() 203 204 if err := points.AddFS(SessionTag, "/mnt", "tmpfs", syscall.MS_NOSUID, ""); err != nil { 205 t.Fatalf("%s", err) 206 } 207 208 fs = points.GetByDest("/mnt") 209 if len(fs) != 1 { 210 t.Fatalf("one filesystem mount points should be returned") 211 } 212 hasNoSuid := false 213 for _, option := range fs[0].Options { 214 if option == "nosuid" { 215 hasNoSuid = true 216 } 217 } 218 if !hasNoSuid { 219 t.Errorf("option nosuid not applied for /mnt") 220 } 221 } 222 223 func TestBind(t *testing.T) { 224 test.DropPrivilege(t) 225 defer test.ResetPrivilege(t) 226 227 points := &Points{} 228 229 if err := points.AddBind(UserbindsTag, "/", "", 0); err == nil { 230 t.Errorf("should have failed with empty destination") 231 } 232 233 if err := points.AddBind(UserbindsTag, "fake", "/", 0); err == nil { 234 t.Errorf("should have failed as source is not an absolute path") 235 } 236 if err := points.AddBind(UserbindsTag, "/", "fake", 0); err == nil { 237 t.Errorf("should have failed as destination is not an absolute path") 238 } 239 points.RemoveAll() 240 241 if err := points.AddBind(UserbindsTag, "/", "/mnt", syscall.MS_BIND); err != nil { 242 t.Fatalf("%s", err) 243 } 244 bind := points.GetByDest("/mnt") 245 if len(bind) != 1 { 246 t.Fatalf("more than one mount point for /mnt has been returned") 247 } 248 hasBind := false 249 for _, option := range bind[0].Options { 250 if option == "bind" { 251 hasBind = true 252 } 253 } 254 if !hasBind { 255 t.Errorf("option bind not applied for /mnt") 256 } 257 points.RemoveAll() 258 259 if err := points.AddBind(UserbindsTag, "/", "/mnt", syscall.MS_BIND|syscall.MS_REC); err != nil { 260 t.Fatalf("%s", err) 261 } 262 bind = points.GetByDest("/mnt") 263 if len(bind) != 1 { 264 t.Fatalf("more than one mount point for /mnt has been returned") 265 } 266 bind = points.GetBySource("/") 267 if len(bind) != 1 { 268 t.Fatalf("more than one mount point for / has been returned") 269 } 270 hasBind = false 271 for _, option := range bind[0].Options { 272 if option == "rbind" { 273 hasBind = true 274 } 275 } 276 if !hasBind { 277 t.Errorf("option rbind not applied for /mnt") 278 } 279 } 280 281 func TestRemount(t *testing.T) { 282 test.DropPrivilege(t) 283 defer test.ResetPrivilege(t) 284 285 points := &Points{} 286 287 if err := points.AddRemount(UserbindsTag, "", 0); err == nil { 288 t.Errorf("should have failed with empty destination") 289 } 290 if err := points.AddRemount(UserbindsTag, "fake", 0); err == nil { 291 t.Errorf("should have failed as destination is not an absolute path") 292 } 293 points.RemoveAll() 294 } 295 296 func TestAddPropagation(t *testing.T) { 297 test.DropPrivilege(t) 298 defer test.ResetPrivilege(t) 299 300 points := &Points{} 301 302 if err := points.AddPropagation(UserbindsTag, "", 0); err == nil { 303 t.Errorf("should have failed with empty destination") 304 } 305 if err := points.AddPropagation(UserbindsTag, "/mnt", 0); err == nil { 306 t.Errorf("should have failed with no propagation flag found") 307 } 308 if err := points.AddPropagation(UserbindsTag, "/mnt", syscall.MS_SHARED|syscall.MS_REC); err != nil { 309 t.Error(err) 310 } 311 312 points.RemoveAll() 313 } 314 315 func TestImport(t *testing.T) { 316 test.DropPrivilege(t) 317 defer test.ResetPrivilege(t) 318 319 mountLabel := "system_u:object_r:removable_t" 320 points := &Points{} 321 322 if err := points.SetContext(mountLabel); err != nil { 323 t.Fatalf("should have passed since context is not set") 324 } 325 if err := points.SetContext(mountLabel); err == nil { 326 t.Fatalf("should have failed since context has already been set") 327 } 328 if points.GetContext() != mountLabel { 329 t.Fatalf("%s != %s", mountLabel, points.GetContext()) 330 } 331 332 validImport := map[AuthorizedTag][]Point{ 333 UserbindsTag: { 334 { 335 Mount: specs.Mount{ 336 Source: "/", 337 Destination: "/mnt", 338 Type: "", 339 Options: []string{"rbind", "nosuid"}, 340 }, 341 }, 342 }, 343 KernelTag: { 344 { 345 Mount: specs.Mount{ 346 Source: "proc", 347 Destination: "/proc", 348 Type: "proc", 349 Options: []string{"nosuid", "nodev"}, 350 }, 351 }, 352 { 353 Mount: specs.Mount{ 354 Source: "sysfs", 355 Destination: "/sys", 356 Type: "sysfs", 357 Options: []string{"nosuid", "nodev"}, 358 }, 359 }, 360 }, 361 SessionTag: { 362 { 363 Mount: specs.Mount{ 364 Source: "", 365 Destination: "/tmp", 366 Type: "tmpfs", 367 Options: []string{"nosuid", "nodev", "mode=1777"}, 368 }, 369 }, 370 }, 371 LayerTag: { 372 { 373 Mount: specs.Mount{ 374 Source: "", 375 Destination: "/opt", 376 Type: "overlay", 377 Options: []string{"nosuid", "nodev", "lowerdir=/", "upperdir=/upper", "workdir=/work"}, 378 }, 379 }, 380 }, 381 RootfsTag: { 382 { 383 Mount: specs.Mount{ 384 Source: "/image.simg", 385 Destination: "/tmp/image", 386 Type: "squashfs", 387 Options: []string{"nosuid", "nodev"}, 388 }, 389 InternalOptions: []string{"offset=31", "sizelimit=10"}, 390 }, 391 }, 392 } 393 if err := points.Import(validImport); err != nil { 394 t.Fatalf("%s", err) 395 } 396 if len(points.GetAll()) != len(validImport) { 397 t.Errorf("returned a wrong number of mount points %d instead of %d", len(points.GetAll()), len(validImport)) 398 } 399 image := points.GetAllImages() 400 if len(image) != 1 { 401 t.Errorf("wrong number of image mount point found") 402 } 403 overlay := points.GetAllOverlays() 404 if len(overlay) != 1 { 405 t.Errorf("wrong number of overlay mount point found") 406 } 407 bind := points.GetAllBinds() 408 if len(bind) != 1 { 409 t.Errorf("wrong number of bind mount point found") 410 } 411 fs := points.GetAllFS() 412 if len(fs) != 3 { 413 t.Errorf("wrong number of filesystem mount point found") 414 } 415 points.RemoveByDest("/mnt") 416 all := points.GetByTag(UserbindsTag) 417 if len(all) != 0 { 418 t.Errorf("returned a wrong number of mount points %d instead of 0", len(all)) 419 } 420 points.RemoveByDest("/tmp") 421 all = points.GetByTag(SessionTag) 422 if len(all) != 0 { 423 t.Errorf("returned a wrong number of mount points %d instead of 0", len(all)) 424 } 425 points.RemoveByDest("/opt") 426 all = points.GetByTag(LayerTag) 427 if len(all) != 0 { 428 t.Errorf("returned a wrong number of mount points %d instead of 0", len(all)) 429 } 430 points.RemoveBySource("/image.simg") 431 all = points.GetByTag(RootfsTag) 432 if len(all) != 0 { 433 t.Errorf("returned a wrong number of mount points %d instead of 0", len(all)) 434 } 435 436 proc := points.GetByDest("/proc") 437 if len(proc) != 1 { 438 t.Fatalf("returned a wrong number of mount points %d instead of 1", len(proc)) 439 } 440 for _, option := range proc[0].Options { 441 if option == "context="+mountLabel { 442 t.Errorf("context should not be set for proc filesystem") 443 } 444 } 445 points.RemoveByDest("/proc") 446 447 sys := points.GetByDest("/sys") 448 if len(sys) != 1 { 449 t.Fatalf("returned a wrong number of mount points %d instead of 1", len(sys)) 450 } 451 for _, option := range sys[0].Options { 452 if option == "context="+mountLabel { 453 t.Errorf("context should not be set for sysfs filesystem") 454 } 455 } 456 points.RemoveByDest("/sys") 457 458 all = points.GetByTag(KernelTag) 459 if len(all) != 0 { 460 t.Errorf("returned a wrong number of mount points %d instead of 0", len(all)) 461 } 462 points.RemoveAll() 463 464 invalidImport := map[AuthorizedTag][]Point{ 465 UserbindsTag: { 466 { 467 Mount: specs.Mount{ 468 Source: "", 469 Destination: "/mnt", 470 Type: "", 471 Options: []string{"rbind", "nosuid"}, 472 }, 473 }, 474 }, 475 } 476 if err := points.Import(invalidImport); err == nil { 477 t.Errorf("import should failed: %s", err) 478 } 479 480 validForceContextImport := map[AuthorizedTag][]Point{ 481 SessionTag: { 482 { 483 Mount: specs.Mount{ 484 Source: "/", 485 Destination: "/tmp", 486 Type: "tmpfs", 487 Options: []string{"nosuid", "nodev", "mode=1777"}, 488 }, 489 }, 490 }, 491 } 492 493 if err := points.Import(validForceContextImport); err != nil { 494 t.Fatalf("%s", err) 495 } 496 tmp := points.GetByDest("/tmp") 497 if len(tmp) != 1 { 498 t.Fatalf("returned a wrong number of mount points %d instead of 1", len(tmp)) 499 } 500 hasContext := false 501 context := fmt.Sprintf("context=%q", mountLabel) 502 for _, option := range tmp[0].Options { 503 if option == context { 504 hasContext = true 505 } 506 } 507 if !hasContext { 508 t.Errorf("context should be set /tmp mount point") 509 } 510 points.RemoveAll() 511 512 validContextImport := map[AuthorizedTag][]Point{ 513 SessionTag: { 514 { 515 Mount: specs.Mount{ 516 Source: "/", 517 Destination: "/tmp", 518 Type: "tmpfs", 519 Options: []string{"nosuid", "nodev", "mode=1777", "context=" + mountLabel}, 520 }, 521 }, 522 }, 523 } 524 525 if err := points.Import(validContextImport); err != nil { 526 t.Fatalf("%s", err) 527 } 528 tmp = points.GetByDest("/tmp") 529 if len(tmp) != 1 { 530 t.Fatalf("returned a wrong number of mount points %d instead of 1", len(tmp)) 531 } 532 numContext := 0 533 for _, option := range tmp[0].Options { 534 if option == "context="+mountLabel { 535 numContext++ 536 } 537 } 538 if numContext != 1 { 539 t.Errorf("context option is set %d times for /tmp mount point %s", numContext, tmp[0]) 540 } 541 points.RemoveAll() 542 543 points = &Points{} 544 545 validSpecs := []specs.Mount{ 546 { 547 Source: "/", 548 Destination: "/mnt", 549 Type: "", 550 Options: []string{"rbind", "nosuid", "rshared"}, 551 }, 552 { 553 Source: "", 554 Destination: "/opt", 555 Type: "overlay", 556 Options: []string{"nosuid", "nodev", "lowerdir=/", "upperdir=/upper", "workdir=/work"}, 557 }, 558 { 559 Source: "", 560 Destination: "/tmp", 561 Type: "tmpfs", 562 Options: []string{"nosuid", "nodev", "mode=1777"}, 563 }, 564 { 565 Source: "sysfs", 566 Destination: "/sys", 567 Type: "sysfs", 568 Options: []string{"nosuid", "nodev"}, 569 }, 570 { 571 Source: "", 572 Destination: "/dev/pts", 573 Type: "devpts", 574 Options: []string{"nosuid"}, 575 }, 576 } 577 if err := points.ImportFromSpec(validSpecs); err != nil { 578 t.Error(err) 579 } 580 if len(points.GetByTag(KernelTag)) != 4 { 581 t.Errorf("returned a wrong number of mount kernel mount points %d instead of 4", len(points.GetByTag(KernelTag))) 582 } 583 if len(points.GetByTag(UserbindsTag)) != 3 { 584 t.Errorf("returned a wrong number of mount kernel mount points %d instead of 3", len(points.GetByTag(UserbindsTag))) 585 } 586 points.RemoveAll() 587 588 invalidSpecs := []specs.Mount{ 589 { 590 Source: "/image.simg", 591 Destination: "/tmp/image", 592 Type: "squashfs", 593 Options: []string{"nosuid", "nodev"}, 594 }, 595 } 596 if err := points.ImportFromSpec(invalidSpecs); err == nil { 597 t.Errorf("should have failed with non authorized filesystem type") 598 } 599 } 600 601 func TestTag(t *testing.T) { 602 test.DropPrivilege(t) 603 defer test.ResetPrivilege(t) 604 605 points := &Points{} 606 607 if err := points.AddBind(AuthorizedTag("unknown"), "/", "/mnt", syscall.MS_NOSUID); err == nil { 608 t.Errorf("should have failed with a not recognized tag") 609 } 610 if err := points.AddFS(SessionTag, "/mnt", "tmpfs", syscall.MS_NOSUID, ""); err != nil { 611 t.Errorf("%s", err) 612 } 613 if err := points.AddFS(SessionTag, "/mnt2", "tmpfs", syscall.MS_NOSUID, ""); err == nil { 614 t.Errorf("should have failed, %s allow only a single mount point", SessionTag) 615 } 616 for _, tag := range GetTagList() { 617 points.RemoveByTag(tag) 618 if len(points.GetByTag(tag)) != 0 { 619 t.Fatalf("removing mount point entries by tag failed") 620 } 621 } 622 }