github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/runsc/boot/loader_test.go (about) 1 // Copyright 2018 The gVisor Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package boot 16 17 import ( 18 "fmt" 19 "math/rand" 20 "os" 21 "reflect" 22 "testing" 23 "time" 24 25 specs "github.com/opencontainers/runtime-spec/specs-go" 26 "golang.org/x/sys/unix" 27 "github.com/SagerNet/gvisor/pkg/control/server" 28 "github.com/SagerNet/gvisor/pkg/fd" 29 "github.com/SagerNet/gvisor/pkg/fspath" 30 "github.com/SagerNet/gvisor/pkg/log" 31 "github.com/SagerNet/gvisor/pkg/p9" 32 "github.com/SagerNet/gvisor/pkg/sentry/contexttest" 33 "github.com/SagerNet/gvisor/pkg/sentry/fs" 34 "github.com/SagerNet/gvisor/pkg/sentry/vfs" 35 "github.com/SagerNet/gvisor/pkg/sync" 36 "github.com/SagerNet/gvisor/pkg/unet" 37 "github.com/SagerNet/gvisor/runsc/config" 38 "github.com/SagerNet/gvisor/runsc/fsgofer" 39 ) 40 41 func init() { 42 log.SetLevel(log.Debug) 43 rand.Seed(time.Now().UnixNano()) 44 if err := fsgofer.OpenProcSelfFD(); err != nil { 45 panic(err) 46 } 47 config.RegisterFlags() 48 } 49 50 func testConfig() *config.Config { 51 conf, err := config.NewFromFlags() 52 if err != nil { 53 panic(err) 54 } 55 // Change test defaults. 56 conf.RootDir = "unused_root_dir" 57 conf.Network = config.NetworkNone 58 conf.DisableSeccomp = true 59 return conf 60 } 61 62 // testSpec returns a simple spec that can be used in tests. 63 func testSpec() *specs.Spec { 64 return &specs.Spec{ 65 // The host filesystem root is the sandbox root. 66 Root: &specs.Root{ 67 Path: "/", 68 Readonly: true, 69 }, 70 Process: &specs.Process{ 71 Args: []string{"/bin/true"}, 72 }, 73 } 74 } 75 76 // startGofer starts a new gofer routine serving 'root' path. It returns the 77 // sandbox side of the connection, and a function that when called will stop the 78 // gofer. 79 func startGofer(root string) (int, func(), error) { 80 fds, err := unix.Socketpair(unix.AF_UNIX, unix.SOCK_STREAM|unix.SOCK_CLOEXEC, 0) 81 if err != nil { 82 return 0, nil, err 83 } 84 sandboxEnd, goferEnd := fds[0], fds[1] 85 86 socket, err := unet.NewSocket(goferEnd) 87 if err != nil { 88 unix.Close(sandboxEnd) 89 unix.Close(goferEnd) 90 return 0, nil, fmt.Errorf("error creating server on FD %d: %v", goferEnd, err) 91 } 92 at, err := fsgofer.NewAttachPoint(root, fsgofer.Config{ROMount: true}) 93 if err != nil { 94 return 0, nil, err 95 } 96 go func() { 97 s := p9.NewServer(at) 98 if err := s.Handle(socket); err != nil { 99 log.Infof("Gofer is stopping. FD: %d, err: %v\n", goferEnd, err) 100 } 101 }() 102 // Closing the gofer socket will stop the gofer and exit goroutine above. 103 cleanup := func() { 104 if err := socket.Close(); err != nil { 105 log.Warningf("Error closing gofer socket: %v", err) 106 } 107 } 108 return sandboxEnd, cleanup, nil 109 } 110 111 func createLoader(vfsEnabled bool, spec *specs.Spec) (*Loader, func(), error) { 112 fd, err := server.CreateSocket(ControlSocketAddr(fmt.Sprintf("%010d", rand.Int())[:10])) 113 if err != nil { 114 return nil, nil, err 115 } 116 conf := testConfig() 117 conf.VFS2 = vfsEnabled 118 119 sandEnd, cleanup, err := startGofer(spec.Root.Path) 120 if err != nil { 121 return nil, nil, err 122 } 123 124 // Loader takes ownership of stdio. 125 var stdio []int 126 for _, f := range []*os.File{os.Stdin, os.Stdout, os.Stderr} { 127 newFd, err := unix.Dup(int(f.Fd())) 128 if err != nil { 129 return nil, nil, err 130 } 131 stdio = append(stdio, newFd) 132 } 133 134 args := Args{ 135 ID: "foo", 136 Spec: spec, 137 Conf: conf, 138 ControllerFD: fd, 139 GoferFDs: []int{sandEnd}, 140 StdioFDs: stdio, 141 } 142 l, err := New(args) 143 if err != nil { 144 cleanup() 145 return nil, nil, err 146 } 147 return l, cleanup, nil 148 } 149 150 // TestRun runs a simple application in a sandbox and checks that it succeeds. 151 func TestRun(t *testing.T) { 152 doRun(t, false) 153 } 154 155 // TestRunVFS2 runs TestRun in VFSv2. 156 func TestRunVFS2(t *testing.T) { 157 doRun(t, true) 158 } 159 160 func doRun(t *testing.T, vfsEnabled bool) { 161 l, cleanup, err := createLoader(vfsEnabled, testSpec()) 162 if err != nil { 163 t.Fatalf("error creating loader: %v", err) 164 } 165 166 defer l.Destroy() 167 defer cleanup() 168 169 // Start a goroutine to read the start chan result, otherwise Run will 170 // block forever. 171 var resultChanErr error 172 var wg sync.WaitGroup 173 wg.Add(1) 174 go func() { 175 resultChanErr = <-l.ctrl.manager.startResultChan 176 wg.Done() 177 }() 178 179 // Run the container. 180 if err := l.Run(); err != nil { 181 t.Errorf("error running container: %v", err) 182 } 183 184 // We should have not gotten an error on the startResultChan. 185 wg.Wait() 186 if resultChanErr != nil { 187 t.Errorf("error on startResultChan: %v", resultChanErr) 188 } 189 190 // Wait for the application to exit. It should succeed. 191 if status := l.WaitExit(); status.Code != 0 || status.Signo != 0 { 192 t.Errorf("application exited with status %+v, want 0", status) 193 } 194 } 195 196 // TestStartSignal tests that the controller Start message will cause 197 // WaitForStartSignal to return. 198 func TestStartSignal(t *testing.T) { 199 doStartSignal(t, false) 200 } 201 202 // TestStartSignalVFS2 does TestStartSignal with VFS2. 203 func TestStartSignalVFS2(t *testing.T) { 204 doStartSignal(t, true) 205 } 206 207 func doStartSignal(t *testing.T, vfsEnabled bool) { 208 l, cleanup, err := createLoader(vfsEnabled, testSpec()) 209 if err != nil { 210 t.Fatalf("error creating loader: %v", err) 211 } 212 defer l.Destroy() 213 defer cleanup() 214 215 // We aren't going to wait on this application, so the control server 216 // needs to be shut down manually. 217 defer l.ctrl.srv.Stop(time.Hour) 218 219 // Start a goroutine that calls WaitForStartSignal and writes to a 220 // channel when it returns. 221 waitFinished := make(chan struct{}) 222 go func() { 223 l.WaitForStartSignal() 224 // Pretend that Run() executed and returned no error. 225 l.ctrl.manager.startResultChan <- nil 226 waitFinished <- struct{}{} 227 }() 228 229 // Nothing has been written to the channel, so waitFinished should not 230 // return. Give it a little bit of time to make sure the goroutine has 231 // started. 232 select { 233 case <-waitFinished: 234 t.Errorf("WaitForStartSignal completed but it should not have") 235 case <-time.After(50 * time.Millisecond): 236 // OK. 237 } 238 239 // Trigger the control server StartRoot method. 240 cid := "foo" 241 if err := l.ctrl.manager.StartRoot(&cid, nil); err != nil { 242 t.Errorf("error calling StartRoot: %v", err) 243 } 244 245 // Now WaitForStartSignal should return (within a short amount of 246 // time). 247 select { 248 case <-waitFinished: 249 // OK. 250 case <-time.After(50 * time.Millisecond): 251 t.Errorf("WaitForStartSignal did not complete but it should have") 252 } 253 254 } 255 256 type CreateMountTestcase struct { 257 name string 258 // Spec that will be used to create the mount manager. Note 259 // that we can't mount procfs without a kernel, so each spec 260 // MUST contain something other than procfs mounted at /proc. 261 spec specs.Spec 262 // Paths that are expected to exist in the resulting fs. 263 expectedPaths []string 264 } 265 266 func createMountTestcases() []*CreateMountTestcase { 267 testCases := []*CreateMountTestcase{ 268 { 269 // Only proc. 270 name: "only proc mount", 271 spec: specs.Spec{ 272 Root: &specs.Root{ 273 Path: os.TempDir(), 274 Readonly: true, 275 }, 276 Mounts: []specs.Mount{ 277 { 278 Destination: "/proc", 279 Type: "tmpfs", 280 }, 281 }, 282 }, 283 // /proc, /dev, and /sys should always be mounted. 284 expectedPaths: []string{"/proc", "/dev", "/sys"}, 285 }, 286 { 287 // Mount at a deep path, with many components that do 288 // not exist in the root. 289 name: "deep mount path", 290 spec: specs.Spec{ 291 Root: &specs.Root{ 292 Path: os.TempDir(), 293 Readonly: true, 294 }, 295 Mounts: []specs.Mount{ 296 { 297 Destination: "/some/very/very/deep/path", 298 Type: "tmpfs", 299 }, 300 { 301 Destination: "/proc", 302 Type: "tmpfs", 303 }, 304 }, 305 }, 306 // /some/deep/path should be mounted, along with /proc, /dev, and /sys. 307 expectedPaths: []string{"/some/very/very/deep/path", "/proc", "/dev", "/sys"}, 308 }, 309 { 310 // Mounts are nested inside each other. 311 name: "nested mounts", 312 spec: specs.Spec{ 313 Root: &specs.Root{ 314 Path: os.TempDir(), 315 Readonly: true, 316 }, 317 Mounts: []specs.Mount{ 318 { 319 Destination: "/proc", 320 Type: "tmpfs", 321 }, 322 { 323 Destination: "/foo", 324 Type: "tmpfs", 325 }, 326 { 327 Destination: "/foo/qux", 328 Type: "tmpfs", 329 }, 330 { 331 // File mounts with the same prefix. 332 Destination: "/foo/qux-quz", 333 Type: "tmpfs", 334 }, 335 { 336 Destination: "/foo/bar", 337 Type: "tmpfs", 338 }, 339 { 340 Destination: "/foo/bar/baz", 341 Type: "tmpfs", 342 }, 343 { 344 // A deep path that is in foo but not the other mounts. 345 Destination: "/foo/some/very/very/deep/path", 346 Type: "tmpfs", 347 }, 348 }, 349 }, 350 expectedPaths: []string{"/foo", "/foo/bar", "/foo/bar/baz", "/foo/qux", 351 "/foo/qux-quz", "/foo/some/very/very/deep/path", "/proc", "/dev", "/sys"}, 352 }, 353 { 354 name: "mount inside /dev", 355 spec: specs.Spec{ 356 Root: &specs.Root{ 357 Path: os.TempDir(), 358 Readonly: true, 359 }, 360 Mounts: []specs.Mount{ 361 { 362 Destination: "/proc", 363 Type: "tmpfs", 364 }, 365 { 366 Destination: "/dev", 367 Type: "tmpfs", 368 }, 369 { 370 // Mounted by runsc by default. 371 Destination: "/dev/fd", 372 Type: "tmpfs", 373 }, 374 { 375 // Mount with the same prefix. 376 Destination: "/dev/fd-foo", 377 Type: "tmpfs", 378 }, 379 { 380 // Unsupported fs type. 381 Destination: "/dev/mqueue", 382 Type: "mqueue", 383 }, 384 { 385 Destination: "/dev/foo", 386 Type: "tmpfs", 387 }, 388 { 389 Destination: "/dev/bar", 390 Type: "tmpfs", 391 }, 392 }, 393 }, 394 expectedPaths: []string{"/proc", "/dev", "/dev/fd-foo", "/dev/foo", "/dev/bar", "/sys"}, 395 }, 396 { 397 name: "mounts inside mandatory mounts", 398 spec: specs.Spec{ 399 Root: &specs.Root{ 400 Path: os.TempDir(), 401 Readonly: true, 402 }, 403 Mounts: []specs.Mount{ 404 { 405 Destination: "/proc", 406 Type: "tmpfs", 407 }, 408 { 409 Destination: "/sys/bar", 410 Type: "tmpfs", 411 }, 412 { 413 Destination: "/tmp/baz", 414 Type: "tmpfs", 415 }, 416 { 417 Destination: "/dev/goo", 418 Type: "tmpfs", 419 }, 420 }, 421 }, 422 expectedPaths: []string{"/proc", "/sys", "/sys/bar", "/tmp", "/tmp/baz", "/dev/goo"}, 423 }, 424 } 425 426 return testCases 427 } 428 429 // Test that MountNamespace can be created with various specs. 430 func TestCreateMountNamespace(t *testing.T) { 431 for _, tc := range createMountTestcases() { 432 t.Run(tc.name, func(t *testing.T) { 433 conf := testConfig() 434 ctx := contexttest.Context(t) 435 436 sandEnd, cleanup, err := startGofer(tc.spec.Root.Path) 437 if err != nil { 438 t.Fatalf("failed to create gofer: %v", err) 439 } 440 defer cleanup() 441 442 info := containerInfo{ 443 conf: conf, 444 spec: &tc.spec, 445 goferFDs: []*fd.FD{fd.New(sandEnd)}, 446 } 447 448 mntr := newContainerMounter(&info, nil, &podMountHints{}, false /* vfs2Enabled */) 449 mns, err := mntr.createMountNamespace(ctx, conf) 450 if err != nil { 451 t.Fatalf("failed to create mount namespace: %v", err) 452 } 453 ctx = fs.WithRoot(ctx, mns.Root()) 454 if err := mntr.mountSubmounts(ctx, conf, mns); err != nil { 455 t.Fatalf("failed to create mount namespace: %v", err) 456 } 457 458 root := mns.Root() 459 defer root.DecRef(ctx) 460 for _, p := range tc.expectedPaths { 461 maxTraversals := uint(0) 462 if d, err := mns.FindInode(ctx, root, root, p, &maxTraversals); err != nil { 463 t.Errorf("expected path %v to exist with spec %v, but got error %v", p, tc.spec, err) 464 } else { 465 d.DecRef(ctx) 466 } 467 } 468 }) 469 } 470 } 471 472 // Test that MountNamespace can be created with various specs. 473 func TestCreateMountNamespaceVFS2(t *testing.T) { 474 for _, tc := range createMountTestcases() { 475 t.Run(tc.name, func(t *testing.T) { 476 spec := testSpec() 477 spec.Mounts = tc.spec.Mounts 478 spec.Root = tc.spec.Root 479 480 t.Logf("Using root: %q", spec.Root.Path) 481 l, loaderCleanup, err := createLoader(true /* VFS2 Enabled */, spec) 482 if err != nil { 483 t.Fatalf("failed to create loader: %v", err) 484 } 485 defer l.Destroy() 486 defer loaderCleanup() 487 488 mntr := newContainerMounter(&l.root, l.k, l.mountHints, true /* vfs2Enabled */) 489 if err := mntr.processHints(l.root.conf, l.root.procArgs.Credentials); err != nil { 490 t.Fatalf("failed process hints: %v", err) 491 } 492 493 ctx := l.k.SupervisorContext() 494 mns, err := mntr.mountAll(l.root.conf, &l.root.procArgs) 495 if err != nil { 496 t.Fatalf("mountAll: %v", err) 497 } 498 499 root := mns.Root() 500 root.IncRef() 501 defer root.DecRef(ctx) 502 for _, p := range tc.expectedPaths { 503 target := &vfs.PathOperation{ 504 Root: root, 505 Start: root, 506 Path: fspath.Parse(p), 507 } 508 509 if d, err := l.k.VFS().GetDentryAt(ctx, l.root.procArgs.Credentials, target, &vfs.GetDentryOptions{}); err != nil { 510 t.Errorf("expected path %v to exist with spec %v, but got error %v", p, tc.spec, err) 511 } else { 512 d.DecRef(ctx) 513 } 514 } 515 }) 516 } 517 } 518 519 // TestRestoreEnvironment tests that the correct mounts are collected from the spec and config 520 // in order to build the environment for restoring. 521 func TestRestoreEnvironment(t *testing.T) { 522 testCases := []struct { 523 name string 524 spec *specs.Spec 525 ioFDs []int 526 errorExpected bool 527 expectedRenv fs.RestoreEnvironment 528 }{ 529 { 530 name: "basic spec test", 531 spec: &specs.Spec{ 532 Root: &specs.Root{ 533 Path: os.TempDir(), 534 Readonly: true, 535 }, 536 Mounts: []specs.Mount{ 537 { 538 Destination: "/some/very/very/deep/path", 539 Type: "tmpfs", 540 }, 541 { 542 Destination: "/proc", 543 Type: "tmpfs", 544 }, 545 }, 546 }, 547 ioFDs: []int{0}, 548 errorExpected: false, 549 expectedRenv: fs.RestoreEnvironment{ 550 MountSources: map[string][]fs.MountArgs{ 551 "9p": { 552 { 553 Dev: "9pfs-/", 554 Flags: fs.MountSourceFlags{ReadOnly: true}, 555 DataString: "trans=fd,rfdno=0,wfdno=0,privateunixsocket=true", 556 }, 557 }, 558 "tmpfs": { 559 { 560 Dev: "none", 561 }, 562 { 563 Dev: "none", 564 }, 565 { 566 Dev: "none", 567 }, 568 }, 569 "devtmpfs": { 570 { 571 Dev: "none", 572 }, 573 }, 574 "devpts": { 575 { 576 Dev: "none", 577 }, 578 }, 579 "sysfs": { 580 { 581 Dev: "none", 582 }, 583 }, 584 }, 585 }, 586 }, 587 { 588 name: "bind type test", 589 spec: &specs.Spec{ 590 Root: &specs.Root{ 591 Path: os.TempDir(), 592 Readonly: true, 593 }, 594 Mounts: []specs.Mount{ 595 { 596 Destination: "/dev/fd-foo", 597 Type: "bind", 598 }, 599 }, 600 }, 601 ioFDs: []int{0, 1}, 602 errorExpected: false, 603 expectedRenv: fs.RestoreEnvironment{ 604 MountSources: map[string][]fs.MountArgs{ 605 "9p": { 606 { 607 Dev: "9pfs-/", 608 Flags: fs.MountSourceFlags{ReadOnly: true}, 609 DataString: "trans=fd,rfdno=0,wfdno=0,privateunixsocket=true", 610 }, 611 { 612 Dev: "9pfs-/dev/fd-foo", 613 DataString: "trans=fd,rfdno=1,wfdno=1,privateunixsocket=true,cache=remote_revalidating", 614 }, 615 }, 616 "tmpfs": { 617 { 618 Dev: "none", 619 }, 620 }, 621 "devtmpfs": { 622 { 623 Dev: "none", 624 }, 625 }, 626 "devpts": { 627 { 628 Dev: "none", 629 }, 630 }, 631 "proc": { 632 { 633 Dev: "none", 634 }, 635 }, 636 "sysfs": { 637 { 638 Dev: "none", 639 }, 640 }, 641 }, 642 }, 643 }, 644 { 645 name: "options test", 646 spec: &specs.Spec{ 647 Root: &specs.Root{ 648 Path: os.TempDir(), 649 Readonly: true, 650 }, 651 Mounts: []specs.Mount{ 652 { 653 Destination: "/dev/fd-foo", 654 Type: "tmpfs", 655 Options: []string{"uid=1022", "noatime"}, 656 }, 657 }, 658 }, 659 ioFDs: []int{0}, 660 errorExpected: false, 661 expectedRenv: fs.RestoreEnvironment{ 662 MountSources: map[string][]fs.MountArgs{ 663 "9p": { 664 { 665 Dev: "9pfs-/", 666 Flags: fs.MountSourceFlags{ReadOnly: true}, 667 DataString: "trans=fd,rfdno=0,wfdno=0,privateunixsocket=true", 668 }, 669 }, 670 "tmpfs": { 671 { 672 Dev: "none", 673 Flags: fs.MountSourceFlags{NoAtime: true}, 674 DataString: "uid=1022", 675 }, 676 { 677 Dev: "none", 678 }, 679 }, 680 "devtmpfs": { 681 { 682 Dev: "none", 683 }, 684 }, 685 "devpts": { 686 { 687 Dev: "none", 688 }, 689 }, 690 "proc": { 691 { 692 Dev: "none", 693 }, 694 }, 695 "sysfs": { 696 { 697 Dev: "none", 698 }, 699 }, 700 }, 701 }, 702 }, 703 } 704 for _, tc := range testCases { 705 t.Run(tc.name, func(t *testing.T) { 706 conf := testConfig() 707 var ioFDs []*fd.FD 708 for _, ioFD := range tc.ioFDs { 709 ioFDs = append(ioFDs, fd.New(ioFD)) 710 } 711 info := containerInfo{ 712 conf: conf, 713 spec: tc.spec, 714 goferFDs: ioFDs, 715 } 716 mntr := newContainerMounter(&info, nil, &podMountHints{}, false /* vfs2Enabled */) 717 actualRenv, err := mntr.createRestoreEnvironment(conf) 718 if !tc.errorExpected && err != nil { 719 t.Fatalf("could not create restore environment for test:%s", tc.name) 720 } else if tc.errorExpected { 721 if err == nil { 722 t.Errorf("expected an error, but no error occurred.") 723 } 724 } else { 725 if !reflect.DeepEqual(*actualRenv, tc.expectedRenv) { 726 t.Errorf("restore environments did not match for test:%s\ngot:%+v\nwant:%+v\n", tc.name, *actualRenv, tc.expectedRenv) 727 } 728 } 729 }) 730 } 731 }