gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/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 "strings" 22 "testing" 23 "time" 24 25 specs "github.com/opencontainers/runtime-spec/specs-go" 26 "github.com/syndtr/gocapability/capability" 27 "golang.org/x/sys/unix" 28 "gvisor.dev/gvisor/pkg/control/server" 29 "gvisor.dev/gvisor/pkg/cpuid" 30 "gvisor.dev/gvisor/pkg/fspath" 31 "gvisor.dev/gvisor/pkg/log" 32 "gvisor.dev/gvisor/pkg/sentry/kernel/auth" 33 "gvisor.dev/gvisor/pkg/sentry/seccheck" 34 "gvisor.dev/gvisor/pkg/sentry/vfs" 35 "gvisor.dev/gvisor/pkg/sync" 36 "gvisor.dev/gvisor/pkg/unet" 37 "gvisor.dev/gvisor/runsc/config" 38 "gvisor.dev/gvisor/runsc/flag" 39 "gvisor.dev/gvisor/runsc/fsgofer" 40 ) 41 42 func init() { 43 log.SetLevel(log.Debug) 44 rand.Seed(time.Now().UnixNano()) 45 if err := fsgofer.OpenProcSelfFD("/proc/self/fd"); err != nil { 46 panic(err) 47 } 48 } 49 50 func testConfig() *config.Config { 51 testFlags := flag.NewFlagSet("test", flag.ContinueOnError) 52 config.RegisterFlags(testFlags) 53 conf, err := config.NewFromFlags(testFlags) 54 if err != nil { 55 panic(err) 56 } 57 // Change test defaults. 58 conf.RootDir = "unused_root_dir" 59 conf.Network = config.NetworkNone 60 conf.DisableSeccomp = true 61 return conf 62 } 63 64 // testSpec returns a simple spec that can be used in tests. 65 func testSpec() *specs.Spec { 66 return &specs.Spec{ 67 // The host filesystem root is the sandbox root. 68 Root: &specs.Root{ 69 Path: "/", 70 Readonly: true, 71 }, 72 Process: &specs.Process{ 73 Args: []string{"/bin/true"}, 74 }, 75 } 76 } 77 78 // startGofer starts a new gofer routine serving 'root' path. It returns the 79 // sandbox side of the connection, and a function that when called will stop the 80 // gofer. 81 func startGofer(root string, conf *config.Config) (int, func(), error) { 82 fds, err := unix.Socketpair(unix.AF_UNIX, unix.SOCK_STREAM|unix.SOCK_CLOEXEC, 0) 83 if err != nil { 84 return 0, nil, err 85 } 86 sandboxEnd, goferEnd := fds[0], fds[1] 87 88 socket, err := unet.NewSocket(goferEnd) 89 if err != nil { 90 unix.Close(sandboxEnd) 91 unix.Close(goferEnd) 92 return 0, nil, fmt.Errorf("error creating server on FD %d: %v", goferEnd, err) 93 } 94 server := fsgofer.NewLisafsServer(fsgofer.Config{ 95 HostUDS: conf.GetHostUDS(), 96 HostFifo: conf.HostFifo, 97 DonateMountPointFD: conf.DirectFS, 98 }) 99 c, err := server.CreateConnection(socket, root, true /* readonly */) 100 if err != nil { 101 return 0, nil, err 102 } 103 server.StartConnection(c) 104 // Closing the gofer socket will stop the gofer and exit goroutine above. 105 cleanup := func() { 106 if err := socket.Close(); err != nil { 107 log.Warningf("Error closing gofer socket: %v", err) 108 } 109 server.Wait() 110 server.Destroy() 111 } 112 return sandboxEnd, cleanup, nil 113 } 114 115 func createLoader(conf *config.Config, spec *specs.Spec) (*Loader, func(), error) { 116 sock := fmt.Sprintf("\x00loader-test.%010d", rand.Int()) 117 fd, err := server.CreateSocket(sock) 118 if err != nil { 119 return nil, nil, err 120 } 121 sandEnd, cleanup, err := startGofer(spec.Root.Path, conf) 122 if err != nil { 123 return nil, nil, err 124 } 125 126 // Loader takes ownership of stdio. 127 var stdio []int 128 for _, f := range []*os.File{os.Stdin, os.Stdout, os.Stderr} { 129 newFd, err := unix.Dup(int(f.Fd())) 130 if err != nil { 131 return nil, nil, err 132 } 133 stdio = append(stdio, newFd) 134 } 135 136 args := Args{ 137 ID: "foo", 138 Spec: spec, 139 Conf: conf, 140 ControllerFD: fd, 141 GoferFDs: []int{sandEnd}, 142 DevGoferFD: -1, 143 StdioFDs: stdio, 144 GoferMountConfs: []GoferMountConf{{Lower: Lisafs, Upper: NoOverlay}}, 145 PodInitConfigFD: -1, 146 ExecFD: -1, 147 } 148 l, err := New(args) 149 if err != nil { 150 cleanup() 151 return nil, nil, err 152 } 153 return l, cleanup, nil 154 } 155 156 // TestRun runs a simple application in a sandbox and checks that it succeeds. 157 func TestRun(t *testing.T) { 158 l, cleanup, err := createLoader(testConfig(), testSpec()) 159 if err != nil { 160 t.Fatalf("error creating loader: %v", err) 161 } 162 163 defer l.Destroy() 164 defer cleanup() 165 166 // Start a goroutine to read the start chan result, otherwise Run will 167 // block forever. 168 var resultChanErr error 169 var wg sync.WaitGroup 170 wg.Add(1) 171 go func() { 172 resultChanErr = <-l.ctrl.manager.startResultChan 173 wg.Done() 174 }() 175 176 // Run the container. 177 if err := l.Run(); err != nil { 178 t.Errorf("error running container: %v", err) 179 } 180 181 // We should have not gotten an error on the startResultChan. 182 wg.Wait() 183 if resultChanErr != nil { 184 t.Errorf("error on startResultChan: %v", resultChanErr) 185 } 186 187 // Wait for the application to exit. It should succeed. 188 if status := l.WaitExit(); !status.Exited() || status.ExitStatus() != 0 { 189 t.Errorf("application exited with %s, want exit status 0", status) 190 } 191 } 192 193 // TestStartSignal tests that the controller Start message will cause 194 // WaitForStartSignal to return. 195 func TestStartSignal(t *testing.T) { 196 l, cleanup, err := createLoader(testConfig(), testSpec()) 197 if err != nil { 198 t.Fatalf("error creating loader: %v", err) 199 } 200 defer l.Destroy() 201 defer cleanup() 202 203 // We aren't going to wait on this application, so the control server 204 // needs to be shut down manually. 205 defer l.ctrl.srv.Stop(time.Hour) 206 207 // Start a goroutine that calls WaitForStartSignal and writes to a 208 // channel when it returns. 209 waitFinished := make(chan struct{}) 210 go func() { 211 l.WaitForStartSignal() 212 // Pretend that Run() executed and returned no error. 213 l.ctrl.manager.startResultChan <- nil 214 waitFinished <- struct{}{} 215 }() 216 217 // Nothing has been written to the channel, so waitFinished should not 218 // return. Give it a little bit of time to make sure the goroutine has 219 // started. 220 select { 221 case <-waitFinished: 222 t.Errorf("WaitForStartSignal completed but it should not have") 223 case <-time.After(50 * time.Millisecond): 224 // OK. 225 } 226 227 // Trigger the control server StartRoot method. 228 cid := "foo" 229 if err := l.ctrl.manager.StartRoot(&cid, nil); err != nil { 230 t.Errorf("error calling StartRoot: %v", err) 231 } 232 233 // Now WaitForStartSignal should return (within a short amount of 234 // time). 235 select { 236 case <-waitFinished: 237 // OK. 238 case <-time.After(50 * time.Millisecond): 239 t.Errorf("WaitForStartSignal did not complete but it should have") 240 } 241 } 242 243 // Test that network=host with raw sockets enabled requires CAP_NET_RAW on the 244 // host. 245 func TestHostnetWithRawSockets(t *testing.T) { 246 // Drop CAP_NET_RAW from effective capabilities, if we have it. 247 pid := os.Getpid() 248 caps, err := capability.NewPid2(os.Getpid()) 249 if err != nil { 250 t.Fatalf("error getting capabilities for pid %d: %v", pid, err) 251 } 252 if err := caps.Load(); err != nil { 253 t.Fatalf("error loading capabilities: %v", err) 254 } 255 if caps.Get(capability.EFFECTIVE, capability.CAP_NET_RAW) { 256 caps.Unset(capability.EFFECTIVE, capability.CAP_NET_RAW) 257 if err := caps.Apply(capability.EFFECTIVE); err != nil { 258 t.Fatalf("error applying capabilities") 259 } 260 // Be nice and add it back when we are done. 261 defer func() { 262 caps.Set(capability.EFFECTIVE, capability.CAP_NET_RAW) 263 if err := caps.Apply(capability.EFFECTIVE); err != nil { 264 t.Fatalf("error restoring capabilities") 265 } 266 }() 267 } 268 269 // Configure host network with raw sockets. 270 conf := testConfig() 271 conf.Network = config.NetworkHost 272 conf.EnableRaw = true 273 274 // Creating loader should fail. 275 l, err := New(Args{ 276 ID: "should-fail", 277 Spec: testSpec(), 278 Conf: conf, 279 }) 280 if err == nil { 281 l.Destroy() 282 t.Fatalf("expected loader.New() to fail but it did not") 283 } 284 // Error message must be about CAP_NET_RAW. 285 if !strings.Contains(err.Error(), "CAP_NET_RAW") { 286 t.Errorf("expected error to contain CAP_NET_RAW but got %q", err) 287 } 288 } 289 290 type CreateMountTestcase struct { 291 name string 292 // Spec that will be used to create the mount manager. Note 293 // that we can't mount procfs without a kernel, so each spec 294 // MUST contain something other than procfs mounted at /proc. 295 spec specs.Spec 296 // Paths that are expected to exist in the resulting fs. 297 expectedPaths []string 298 } 299 300 func createMountTestcases() []*CreateMountTestcase { 301 testCases := []*CreateMountTestcase{ 302 { 303 // Only proc. 304 name: "only proc mount", 305 spec: specs.Spec{ 306 Root: &specs.Root{ 307 Path: os.TempDir(), 308 Readonly: true, 309 }, 310 Mounts: []specs.Mount{ 311 { 312 Destination: "/proc", 313 Type: "tmpfs", 314 }, 315 }, 316 }, 317 // /proc, /dev, and /sys should always be mounted. 318 expectedPaths: []string{"/proc", "/dev", "/sys"}, 319 }, 320 { 321 // Mount at a deep path, with many components that do 322 // not exist in the root. 323 name: "deep mount path", 324 spec: specs.Spec{ 325 Root: &specs.Root{ 326 Path: os.TempDir(), 327 Readonly: true, 328 }, 329 Mounts: []specs.Mount{ 330 { 331 Destination: "/some/very/very/deep/path", 332 Type: "tmpfs", 333 }, 334 { 335 Destination: "/proc", 336 Type: "tmpfs", 337 }, 338 }, 339 }, 340 // /some/deep/path should be mounted, along with /proc, /dev, and /sys. 341 expectedPaths: []string{"/some/very/very/deep/path", "/proc", "/dev", "/sys"}, 342 }, 343 { 344 // Mounts are nested inside each other. 345 name: "nested mounts", 346 spec: specs.Spec{ 347 Root: &specs.Root{ 348 Path: os.TempDir(), 349 Readonly: true, 350 }, 351 Mounts: []specs.Mount{ 352 { 353 Destination: "/proc", 354 Type: "tmpfs", 355 }, 356 { 357 Destination: "/foo", 358 Type: "tmpfs", 359 }, 360 { 361 Destination: "/foo/qux", 362 Type: "tmpfs", 363 }, 364 { 365 // File mounts with the same prefix. 366 Destination: "/foo/qux-quz", 367 Type: "tmpfs", 368 }, 369 { 370 Destination: "/foo/bar", 371 Type: "tmpfs", 372 }, 373 { 374 Destination: "/foo/bar/baz", 375 Type: "tmpfs", 376 }, 377 { 378 // A deep path that is in foo but not the other mounts. 379 Destination: "/foo/some/very/very/deep/path", 380 Type: "tmpfs", 381 }, 382 }, 383 }, 384 expectedPaths: []string{"/foo", "/foo/bar", "/foo/bar/baz", "/foo/qux", 385 "/foo/qux-quz", "/foo/some/very/very/deep/path", "/proc", "/dev", "/sys"}, 386 }, 387 { 388 name: "mount inside /dev", 389 spec: specs.Spec{ 390 Root: &specs.Root{ 391 Path: os.TempDir(), 392 Readonly: true, 393 }, 394 Mounts: []specs.Mount{ 395 { 396 Destination: "/proc", 397 Type: "tmpfs", 398 }, 399 { 400 Destination: "/dev", 401 Type: "tmpfs", 402 }, 403 { 404 // Mounted by runsc by default. 405 Destination: "/dev/fd", 406 Type: "tmpfs", 407 }, 408 { 409 // Mount with the same prefix. 410 Destination: "/dev/fd-foo", 411 Type: "tmpfs", 412 }, 413 { 414 // Unsupported fs type. 415 Destination: "/dev/mqueue", 416 Type: "mqueue", 417 }, 418 { 419 Destination: "/dev/foo", 420 Type: "tmpfs", 421 }, 422 { 423 Destination: "/dev/bar", 424 Type: "tmpfs", 425 }, 426 }, 427 }, 428 expectedPaths: []string{"/proc", "/dev", "/dev/fd-foo", "/dev/foo", "/dev/bar", "/sys"}, 429 }, 430 { 431 name: "mounts inside mandatory mounts", 432 spec: specs.Spec{ 433 Root: &specs.Root{ 434 Path: os.TempDir(), 435 Readonly: true, 436 }, 437 Mounts: []specs.Mount{ 438 { 439 Destination: "/proc", 440 Type: "tmpfs", 441 }, 442 { 443 Destination: "/sys/bar", 444 Type: "tmpfs", 445 }, 446 { 447 Destination: "/tmp/baz", 448 Type: "tmpfs", 449 }, 450 { 451 Destination: "/dev/goo", 452 Type: "tmpfs", 453 }, 454 }, 455 }, 456 expectedPaths: []string{"/proc", "/sys", "/sys/bar", "/tmp", "/tmp/baz", "/dev/goo"}, 457 }, 458 } 459 460 return testCases 461 } 462 463 // Test that MountNamespace can be created with various specs. 464 func TestCreateMountNamespace(t *testing.T) { 465 for _, tc := range createMountTestcases() { 466 t.Run(tc.name, func(t *testing.T) { 467 spec := testSpec() 468 spec.Mounts = tc.spec.Mounts 469 spec.Root = tc.spec.Root 470 471 t.Logf("Using root: %q", spec.Root.Path) 472 l, loaderCleanup, err := createLoader(testConfig(), spec) 473 if err != nil { 474 t.Fatalf("failed to create loader: %v", err) 475 } 476 defer l.Destroy() 477 defer loaderCleanup() 478 479 mntr := newContainerMounter(&l.root, l.k, l.mountHints, l.sharedMounts, "", l.sandboxID) 480 ctx := l.k.SupervisorContext() 481 creds := auth.NewRootCredentials(l.root.procArgs.Credentials.UserNamespace) 482 mns, err := mntr.mountAll(ctx, creds, l.root.spec, l.root.conf, &l.root.procArgs) 483 if err != nil { 484 t.Fatalf("mountAll: %v", err) 485 } 486 487 root := mns.Root(ctx) 488 defer root.DecRef(ctx) 489 for _, p := range tc.expectedPaths { 490 target := &vfs.PathOperation{ 491 Root: root, 492 Start: root, 493 Path: fspath.Parse(p), 494 } 495 496 if d, err := l.k.VFS().GetDentryAt(ctx, l.root.procArgs.Credentials, target, &vfs.GetDentryOptions{}); err != nil { 497 t.Errorf("expected path %v to exist with spec %v, but got error %v", p, tc.spec, err) 498 } else { 499 d.DecRef(ctx) 500 } 501 } 502 }) 503 } 504 } 505 506 func TestMain(m *testing.M) { 507 cpuid.Initialize() 508 seccheck.Initialize() 509 os.Exit(m.Run()) 510 }