gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/test/runner/main.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 // Binary syscall_test_runner runs the syscall test suites in gVisor 16 // containers and on the host platform. 17 package main 18 19 import ( 20 "bufio" 21 "bytes" 22 "encoding/json" 23 "flag" 24 "fmt" 25 "io/ioutil" 26 "os" 27 "os/exec" 28 "os/signal" 29 "path/filepath" 30 "strings" 31 "syscall" 32 "testing" 33 "time" 34 35 specs "github.com/opencontainers/runtime-spec/specs-go" 36 "github.com/syndtr/gocapability/capability" 37 "golang.org/x/sys/unix" 38 "gvisor.dev/gvisor/pkg/cleanup" 39 "gvisor.dev/gvisor/pkg/log" 40 "gvisor.dev/gvisor/pkg/sentry/seccheck" 41 "gvisor.dev/gvisor/pkg/state/pretty" 42 "gvisor.dev/gvisor/pkg/state/statefile" 43 "gvisor.dev/gvisor/pkg/test/testutil" 44 "gvisor.dev/gvisor/runsc/specutils" 45 "gvisor.dev/gvisor/test/runner/gtest" 46 "gvisor.dev/gvisor/test/trace/config" 47 "gvisor.dev/gvisor/test/uds" 48 ) 49 50 var ( 51 debug = flag.Bool("debug", false, "enable debug logs") 52 oneSandbox = flag.Bool("one-sandbox", false, "run all test cases in one sandbox") 53 strace = flag.Bool("strace", false, "enable strace logs") 54 platform = flag.String("platform", "ptrace", "platform to run on") 55 platformSupport = flag.String("platform-support", "", "String passed to the test as GVISOR_PLATFORM_SUPPORT environment variable. Used to determine which syscall tests are expected to work with the current platform.") 56 network = flag.String("network", "none", "network stack to run on (sandbox, host, none)") 57 useTmpfs = flag.Bool("use-tmpfs", false, "mounts tmpfs for /tmp") 58 fusefs = flag.Bool("fusefs", false, "mounts a fusefs for /tmp") 59 fileAccess = flag.String("file-access", "exclusive", "mounts root in exclusive or shared mode") 60 overlay = flag.Bool("overlay", false, "wrap filesystem mounts with writable tmpfs overlay") 61 container = flag.Bool("container", false, "run tests in their own namespaces (user ns, network ns, etc), pretending to be root. Implicitly enabled if network=host, or if using network namespaces") 62 setupContainerPath = flag.String("setup-container", "", "path to setup_container binary (for use with --container)") 63 trace = flag.Bool("trace", false, "enables all trace points") 64 directfs = flag.Bool("directfs", false, "enables directfs (for all gofer mounts)") 65 66 addHostUDS = flag.Bool("add-host-uds", false, "expose a tree of UDS to test communication with the host") 67 addHostConnector = flag.Bool("add-host-connector", false, "create goroutines that connect to bound UDS that will be created by sandbox") 68 addHostFIFO = flag.Bool("add-host-fifo", false, "expose a tree of FIFO to test communication with the host") 69 ioUring = flag.Bool("iouring", false, "Enables IO_URING API for asynchronous I/O") 70 leakCheck = flag.Bool("leak-check", false, "check for reference leaks") 71 waitForPid = flag.Duration("delay-for-debugger", 0, "Print out the sandbox PID and wait for the specified duration to start the test. This is useful for attaching a debugger to the runsc-sandbox process.") 72 save = flag.Bool("save", false, "enables save restore") 73 saveResume = flag.Bool("save-resume", false, "enables save resume") 74 ) 75 76 const ( 77 // Environment variable used by platform_util.cc to determine platform capabilities. 78 platformSupportEnvVar = "GVISOR_PLATFORM_SUPPORT" 79 80 // checkpointFile is the name of the checkpoint/save state file. 81 checkpointFile = "checkpoint.img" 82 ) 83 84 // getSetupContainerPath returns the path to the setup_container binary. 85 func getSetupContainerPath() string { 86 if *setupContainerPath != "" { 87 return *setupContainerPath 88 } 89 setupContainer, err := testutil.FindFile("test/runner/setup_container/setup_container") 90 if err != nil { 91 fatalf("cannot find setup_container: %v", err) 92 } 93 return setupContainer 94 } 95 96 // runTestCaseNative runs the test case directly on the host machine. 97 func runTestCaseNative(testBin string, tc *gtest.TestCase, args []string, t *testing.T) { 98 // These tests might be running in parallel, so make sure they have a 99 // unique test temp dir. 100 tmpDir, err := ioutil.TempDir(testutil.TmpDir(), "") 101 if err != nil { 102 t.Fatalf("could not create temp dir: %v", err) 103 } 104 defer os.RemoveAll(tmpDir) 105 106 // Replace TEST_TMPDIR in the current environment with something 107 // unique. 108 env := os.Environ() 109 newEnvVar := "TEST_TMPDIR=" + tmpDir 110 var found bool 111 for i, kv := range env { 112 if strings.HasPrefix(kv, "TEST_TMPDIR=") { 113 env[i] = newEnvVar 114 found = true 115 break 116 } 117 } 118 if !found { 119 env = append(env, newEnvVar) 120 } 121 // Remove shard env variables so that the gunit binary does not try to 122 // interpret them. 123 env = filterEnv(env, []string{"TEST_SHARD_INDEX", "TEST_TOTAL_SHARDS", "GTEST_SHARD_INDEX", "GTEST_TOTAL_SHARDS"}) 124 125 if *addHostUDS { 126 socketDir, cleanup, err := uds.CreateBoundUDSTree("/tmp") 127 if err != nil { 128 t.Fatalf("failed to create socket tree: %v", err) 129 } 130 defer cleanup() 131 132 env = append(env, "TEST_UDS_TREE="+socketDir) 133 // On Linux, the concept of "attach" location doesn't exist. 134 // Just pass the same path to make these tests identical. 135 env = append(env, "TEST_UDS_ATTACH_TREE="+socketDir) 136 } 137 138 if *addHostConnector { 139 connectorDir, cleanup, err := uds.CreateSocketConnectors("/tmp") 140 if err != nil { 141 t.Fatalf("failed to create socket connectors: %v", err) 142 } 143 defer cleanup() 144 145 env = append(env, "TEST_CONNECTOR_TREE="+connectorDir) 146 } 147 148 if *addHostFIFO { 149 pipeDir, cleanup, err := uds.CreateFifoTree("/tmp") 150 if err != nil { 151 t.Fatalf("failed to create pipe tree: %v", err) 152 } 153 defer cleanup() 154 155 env = append(env, "TEST_FIFO_TREE="+pipeDir) 156 // On Linux, the concept of "attach" location doesn't exist. 157 // Just pass the same path to make these tests identical. 158 env = append(env, "TEST_FIFO_ATTACH_TREE="+pipeDir) 159 } 160 161 if *platformSupport != "" { 162 env = append(env, fmt.Sprintf("%s=%s", platformSupportEnvVar, *platformSupport)) 163 } 164 165 if args == nil { 166 args = tc.Args() 167 } 168 169 args = append(args, gtest.TestFlags...) 170 cmd := exec.Command(testBin, args...) 171 cmd.Env = env 172 cmd.Stdout = os.Stdout 173 cmd.Stderr = os.Stderr 174 cmd.SysProcAttr = &unix.SysProcAttr{} 175 176 if specutils.HasCapabilities(capability.CAP_SYS_ADMIN) { 177 cmd.SysProcAttr.Cloneflags |= unix.CLONE_NEWUTS 178 } 179 180 if specutils.HasCapabilities(capability.CAP_NET_ADMIN) { 181 cmd.SysProcAttr.Cloneflags |= unix.CLONE_NEWNET 182 } 183 184 if *container || (cmd.SysProcAttr.Cloneflags&unix.CLONE_NEWNET != 0) { 185 // setup_container takes in its target argv as positional arguments. 186 cmd.Path = getSetupContainerPath() 187 cmd.Args = append([]string{cmd.Path}, cmd.Args...) 188 cmd.SysProcAttr.Cloneflags |= unix.CLONE_NEWUSER | unix.CLONE_NEWNET | unix.CLONE_NEWIPC | unix.CLONE_NEWUTS 189 // Set current user/group as root inside the namespace. 190 cmd.SysProcAttr.UidMappings = []syscall.SysProcIDMap{ 191 {ContainerID: 0, HostID: os.Getuid(), Size: 1}, 192 } 193 cmd.SysProcAttr.GidMappings = []syscall.SysProcIDMap{ 194 {ContainerID: 0, HostID: os.Getgid(), Size: 1}, 195 } 196 cmd.SysProcAttr.GidMappingsEnableSetgroups = false 197 cmd.SysProcAttr.Credential = &syscall.Credential{ 198 Uid: 0, 199 Gid: 0, 200 } 201 } 202 203 if err := cmd.Run(); err != nil { 204 ws := err.(*exec.ExitError).Sys().(syscall.WaitStatus) 205 t.Errorf("test %q exited with status %d, want 0", tc.FullName(), ws.ExitStatus()) 206 } 207 } 208 209 func deleteIfEmptyFile(dir string) (bool, error) { 210 fName := filepath.Join(dir, checkpointFile) 211 fi, err := os.Stat(fName) 212 if err != nil { 213 return false, fmt.Errorf("stat error: %v", err) 214 } 215 if fi.Size() > 0 { 216 return false, nil 217 } 218 os.RemoveAll(dir) 219 return true, nil 220 } 221 222 func printAll(dirs []string) { 223 for _, dir := range dirs { 224 f := filepath.Join(dir, checkpointFile) 225 printOne(dir, f, false, ".txt") 226 printOne(dir, f, true, ".html") 227 } 228 } 229 230 func printOne(dir string, file string, html bool, postfix string) { 231 f, err := os.Open(file) 232 if err != nil { 233 return 234 } 235 defer f.Close() 236 r, m, err := statefile.NewReader(f, nil) 237 if err != nil { 238 return 239 } 240 w, err := os.Create(dir + postfix) 241 if err != nil { 242 return 243 } 244 defer w.Close() 245 246 cu := cleanup.Make(func() { 247 os.Remove(dir + postfix) 248 }) 249 defer cu.Clean() 250 if html { 251 // Print just the HTML stream. 252 if err := pretty.PrintHTML(w, r); err != nil { 253 return 254 } 255 } else { 256 // Print the metadata first. 257 if _, err := fmt.Fprintf(w, "%v\n", m); err != nil { 258 return 259 } 260 // Then print the rest of the text. 261 if err := pretty.PrintText(w, r); err != nil { 262 return 263 } 264 } 265 cu.Release() 266 } 267 268 func removeAll(dirs []string) { 269 for _, dir := range dirs { 270 os.RemoveAll(dir) 271 } 272 } 273 274 func prepareSave(args []string, undeclaredOutputsDir string, dirs []string, index int) ([]string, []string, error) { 275 // Create the state file directory. 276 dir, err := os.MkdirTemp(undeclaredOutputsDir, fmt.Sprintf("state.%v.", index)) 277 if err != nil { 278 return args, dirs, fmt.Errorf("failed to create state file directory: %v", err) 279 } 280 // Pass the directory path of the state file to the sandbox. 281 args = append(args, "-TESTONLY-autosave-image-path", dir) 282 dirs = append(dirs, dir) 283 return args, dirs, nil 284 } 285 286 func deleteSandbox(args []string, id string) error { 287 deleteArgs := append(args, "delete", "-force=true", id) 288 deleteCmd := exec.Command(specutils.ExePath, deleteArgs...) 289 if err := deleteCmd.Run(); err != nil { 290 return fmt.Errorf("delete error: %v", err) 291 } 292 return nil 293 } 294 295 // runRunsc runs spec in runsc in a standard test configuration. 296 // 297 // runsc logs will be saved to a path in TEST_UNDECLARED_OUTPUTS_DIR. 298 // 299 // Returns an error if the sandboxed application exits non-zero. 300 func runRunsc(tc *gtest.TestCase, spec *specs.Spec) error { 301 bundleDir, cleanup, err := testutil.SetupBundleDir(spec) 302 if err != nil { 303 return fmt.Errorf("SetupBundleDir failed: %v", err) 304 } 305 defer cleanup() 306 307 rootDir, cleanup, err := testutil.SetupRootDir() 308 if err != nil { 309 return fmt.Errorf("SetupRootDir failed: %v", err) 310 } 311 defer cleanup() 312 313 name := tc.FullName() 314 id := testutil.RandomContainerID() 315 log.Infof("Running test %q in container %q", name, id) 316 specutils.LogSpecDebug(spec, false) 317 318 args := []string{ 319 "-root", rootDir, 320 "-network", *network, 321 "-log-format=text", 322 "-TESTONLY-unsafe-nonroot=true", 323 "-TESTONLY-allow-packet-endpoint-write=true", 324 fmt.Sprintf("-panic-signal=%d", unix.SIGTERM), 325 fmt.Sprintf("-iouring=%t", *ioUring), 326 "-watchdog-action=panic", 327 "-platform", *platform, 328 "-file-access", *fileAccess, 329 "-gvisor-gro", 330 } 331 332 if *network == "host" && !testutil.TestEnvSupportsNetAdmin { 333 log.Warningf("Testing with network=host but test environment does not support net admin or raw sockets. Raw sockets will not be enabled.") 334 } else { 335 args = append(args, "-net-raw") 336 } 337 if *overlay { 338 args = append(args, "-overlay2=all:dir=/tmp") 339 } else { 340 args = append(args, "-overlay2=none") 341 } 342 if *debug { 343 args = append(args, "-debug", "-log-packets=true") 344 } 345 if *strace { 346 args = append(args, "-strace") 347 } 348 if *addHostUDS { 349 args = append(args, "-host-uds=open") 350 } 351 if *addHostConnector { 352 args = append(args, "-host-uds=create") 353 } 354 if *addHostFIFO { 355 args = append(args, "-host-fifo=open") 356 } 357 if *leakCheck { 358 args = append(args, "-ref-leak-mode=log-names") 359 } 360 if *trace { 361 flag, err := enableAllTraces(rootDir) 362 if err != nil { 363 return fmt.Errorf("enabling all traces: %w", err) 364 } 365 log.Infof("Enabling all trace points: %s", flag) 366 args = append(args, flag) 367 } 368 if *directfs { 369 args = append(args, "-directfs") 370 } else { 371 args = append(args, "-directfs=false") 372 } 373 374 testLogDir := "" 375 runscLogDir := "" 376 undeclaredOutputsDir := "" 377 dirs := []string{} 378 saveArgs := []string{} 379 var ok bool 380 undeclaredOutputsDir, ok = unix.Getenv("TEST_UNDECLARED_OUTPUTS_DIR") 381 if ok { 382 // Create log directory dedicated for this test. 383 testLogDir = filepath.Join(undeclaredOutputsDir, strings.Replace(name, "/", "_", -1)) 384 if err := os.MkdirAll(testLogDir, 0755); err != nil { 385 return fmt.Errorf("could not create test dir: %v", err) 386 } 387 debugLogDir, err := ioutil.TempDir(testLogDir, "runsc") 388 if err != nil { 389 return fmt.Errorf("could not create temp dir: %v", err) 390 } 391 debugLogDir += "/" 392 runscLogDir = debugLogDir + "/runsc.log" 393 log.Infof("runsc logs: %s", debugLogDir) 394 args = append(args, "-debug-log", runscLogDir) 395 args = append(args, "-coverage-report", debugLogDir) 396 397 // Default -log sends messages to stderr which makes reading the test log 398 // difficult. Instead, drop them when debug log is enabled given it's a 399 // better place for these messages. 400 args = append(args, "-log=/dev/null") 401 402 // Create the state file. 403 if *save || *saveResume { 404 saveArgs = args 405 args, dirs, err = prepareSave(args, undeclaredOutputsDir, dirs, 0) 406 if err != nil { 407 return fmt.Errorf("prepareSave error: %v", err) 408 } 409 if *saveResume { 410 args = append(args, "-TESTONLY-autosave-resume=true") 411 } 412 } 413 } else if *save || *saveResume { 414 // TEST_UNDECLARED_OUTPUTS_DIR directory should be present with S/R to create 415 // the state file. 416 return fmt.Errorf("TEST_UNDECLARED_OUTPUTS_DIR is not set with S/R enabled") 417 } 418 419 // Current process doesn't have CAP_SYS_ADMIN, create user namespace and run 420 // as root inside that namespace to get it. 421 sysProcAttr := &unix.SysProcAttr{ 422 Cloneflags: unix.CLONE_NEWUSER | unix.CLONE_NEWNS, 423 // Set current user/group as root inside the namespace. 424 UidMappings: []syscall.SysProcIDMap{ 425 {ContainerID: 0, HostID: os.Getuid(), Size: 1}, 426 }, 427 GidMappings: []syscall.SysProcIDMap{ 428 {ContainerID: 0, HostID: os.Getgid(), Size: 1}, 429 }, 430 GidMappingsEnableSetgroups: false, 431 Credential: &syscall.Credential{ 432 Uid: 0, 433 Gid: 0, 434 }, 435 } 436 var cmdArgs []string 437 if *waitForPid != 0 { 438 createArgs := append(args, "create", "-pid-file", filepath.Join(testLogDir, "pid"), "--bundle", bundleDir, id) 439 defer os.Remove(filepath.Join(testLogDir, "pid")) 440 createCmd := exec.Command(specutils.ExePath, createArgs...) 441 createCmd.SysProcAttr = sysProcAttr 442 createCmd.Stdout = os.Stdout 443 createCmd.Stderr = os.Stderr 444 if err := createCmd.Run(); err != nil { 445 return fmt.Errorf("could not create sandbox: %v", err) 446 } 447 448 sandboxPidBytes, err := os.ReadFile(filepath.Join(testLogDir, "pid")) 449 if err != nil { 450 return fmt.Errorf("could not read pid file: %v", err) 451 } 452 msg := ` 453 454 Sandbox is running. You can now attach to it from a debugger of your choice. 455 For example, with Delve you can call: $ dlv attach %s. 456 The test will automatically start after %s. 457 You may also signal the test process to start the test immediately: $ kill -SIGUSR1 %d. 458 459 If you're running a test using Make/docker, you'll have to obtain the runsc and test PIDs manually. 460 To attach run: $ dlv attach $(ps aux | grep -m 1 -e 'runsc-sandbox' | awk '{print $2}') 461 To signal the test process run: $ kill -SIGUSR1 $(ps aux | grep -m 1 -e 'bash.*test/syscalls' | awk '{print $2}')` 462 log.Infof(msg, sandboxPidBytes, *waitForPid, os.Getpid()) 463 464 sigCh := make(chan os.Signal, 1) 465 signal.Notify(sigCh, unix.SIGUSR1) 466 select { 467 case <-sigCh: 468 case <-time.After(*waitForPid): 469 } 470 signal.Reset(unix.SIGUSR1) 471 472 cmdArgs = append(args, "start", id) 473 } else { 474 cmdArgs = append(args, "run", "--bundle", bundleDir, id) 475 } 476 cmd := exec.Command(specutils.ExePath, cmdArgs...) 477 cmd.SysProcAttr = sysProcAttr 478 if *container || *network == "host" || (cmd.SysProcAttr.Cloneflags&unix.CLONE_NEWNET != 0) { 479 cmd.SysProcAttr.Cloneflags |= unix.CLONE_NEWNET 480 cmd.Path = getSetupContainerPath() 481 cmd.Args = append([]string{cmd.Path}, cmd.Args...) 482 } 483 cmd.Stdout = os.Stdout 484 cmd.Stderr = os.Stderr 485 sig := make(chan os.Signal, 1) 486 defer close(sig) 487 signal.Notify(sig, unix.SIGTERM) 488 defer signal.Stop(sig) 489 go func() { 490 s, ok := <-sig 491 if !ok { 492 return 493 } 494 log.Warningf("%s: Got signal: %v", name, s) 495 done := make(chan bool, 1) 496 dArgs := append([]string{}, args...) 497 dArgs = append(dArgs, "debug", "--stacks", id) 498 go func(dArgs []string) { 499 debug := exec.Command(specutils.ExePath, dArgs...) 500 debug.Stdout = os.Stdout 501 debug.Stderr = os.Stderr 502 debug.Run() 503 done <- true 504 }(dArgs) 505 506 timeout := time.After(3 * time.Second) 507 select { 508 case <-timeout: 509 log.Infof("runsc debug --stacks is timeouted") 510 case <-done: 511 } 512 513 log.Warningf("Send SIGTERM to the sandbox process") 514 dArgs = append(args, "debug", 515 fmt.Sprintf("--signal=%d", unix.SIGTERM), 516 id) 517 signal := exec.Command(specutils.ExePath, dArgs...) 518 signal.Stdout = os.Stdout 519 signal.Stderr = os.Stderr 520 signal.Run() 521 }() 522 523 if *save { 524 err = cmd.Run() 525 if err != nil { 526 return fmt.Errorf("run error: %v", err) 527 } 528 529 // Restore the sandbox with the previous state file. 530 for i := 1; ; i++ { 531 // Check if the latest state file is valid. If the file 532 // is empty, delete it and exit the loop. 533 isEmpty, err := deleteIfEmptyFile(dirs[i-1]) 534 if err != nil { 535 return err 536 } 537 if isEmpty { 538 dirs = dirs[:i-1] 539 break 540 } 541 542 // Delete the existing sandbox. 543 if err := deleteSandbox(saveArgs, id); err != nil { 544 printAll(dirs) 545 removeAll(dirs) 546 return fmt.Errorf("deleteSandbox error %v", err) 547 } 548 549 // Restore into new sandbox. 550 restoreArgs := saveArgs 551 restoreArgs, dirs, err = prepareSave(restoreArgs, undeclaredOutputsDir, dirs, i) 552 if err != nil { 553 printAll(dirs) 554 removeAll(dirs) 555 return fmt.Errorf("prepareSave error: %v", err) 556 } 557 restoreArgs = append(restoreArgs, "restore", "--image-path", dirs[i-1], "--bundle", bundleDir, id) 558 restoreCmd := exec.Command(specutils.ExePath, restoreArgs...) 559 restoreCmd.SysProcAttr = sysProcAttr 560 if *container || *network == "host" || (restoreCmd.SysProcAttr.Cloneflags&unix.CLONE_NEWNET != 0) { 561 restoreCmd.SysProcAttr.Cloneflags |= unix.CLONE_NEWNET 562 restoreCmd.Path = getSetupContainerPath() 563 restoreCmd.Args = append([]string{restoreCmd.Path}, restoreCmd.Args...) 564 } 565 restoreCmd.Stdout = os.Stdout 566 restoreCmd.Stderr = os.Stderr 567 if err := restoreCmd.Run(); err != nil { 568 printAll(dirs) 569 removeAll(dirs) 570 return fmt.Errorf("after restore error: %v", err) 571 } 572 } 573 // Do not output state files when the test succeeds. 574 removeAll(dirs) 575 } else if *saveResume { 576 err = cmd.Run() 577 if err != nil { 578 printAll(dirs) 579 removeAll(dirs) 580 return fmt.Errorf("run error: %v", err) 581 } 582 removeAll(dirs) 583 } else { 584 err = cmd.Run() 585 if *waitForPid != 0 { 586 if err != nil { 587 return fmt.Errorf("could not start container: %v", err) 588 } 589 waitArgs := append(args, "wait", id) 590 waitCmd := exec.Command(specutils.ExePath, waitArgs...) 591 waitCmd.SysProcAttr = sysProcAttr 592 waitCmd.Stderr = os.Stderr 593 594 buf := bytes.NewBuffer(nil) 595 waitCmd.Stdout = buf 596 err = waitCmd.Run() 597 wres := struct { 598 ID string `json:"id"` 599 ExitStatus int `json:"exitStatus"` 600 }{} 601 if err := json.NewDecoder(buf).Decode(&wres); err != nil { 602 return fmt.Errorf("could not decode wait result: %v", err) 603 } 604 if wres.ExitStatus != 0 { 605 return fmt.Errorf("test failed with status: %d", wres.ExitStatus) 606 } 607 } 608 } 609 if err == nil && len(testLogDir) > 0 { 610 var warningsFound []string 611 f, err := os.Open(runscLogDir) 612 if err != nil { 613 return err 614 } 615 scanner := bufio.NewScanner(f) 616 for scanner.Scan() { 617 // This is trivial match for Google's log file format. 618 line := scanner.Text() 619 if len(line) >= 5 && line[:5] == "panic" { 620 warningsFound = append(warningsFound, strings.TrimSpace(line)) 621 } 622 if len(line) >= 2 && (line[0] == 'E' || line[0] == 'W') && (line[1] >= '0' && line[1] <= '9') { 623 // Ignore a basic set of warnings that we've 624 // determined to be fine. We want these to stay 625 // as warnings, even if they are constant. 626 switch { 627 // Reasonable warnings, allowed during tests. 628 case strings.Contains(line, "Will try waiting on the sandbox process instead."): 629 case strings.Contains(line, "lisafs: batch closing FDs"): 630 case strings.Contains(line, "This is only safe in tests!"): 631 case strings.Contains(line, "Capability \"checkpoint_restore\" is not permitted, dropping it."): 632 case strings.Contains(line, "syscall filters less restrictive!"): 633 case strings.Contains(line, "Getdent64: skipping file"): 634 // Capability "perfmon" is not permitted, dropping it. 635 case strings.Contains(line, "is not permitted, dropping it."): 636 case strings.Contains(line, "sndPrepopulatedMsg failed"): 637 case strings.Contains(line, "PR_SET_NO_NEW_PRIVS is assumed to always be set."): 638 case strings.Contains(line, "TSC snapshot unavailable"): 639 case strings.Contains(line, "copy up failed to copy up contents"): 640 case strings.Contains(line, "populate failed for"): 641 case strings.Contains(line, "ASAN is enabled: syscall filters less restrictive"): 642 case strings.Contains(line, "MSAN is enabled: syscall filters less restrictive"): 643 case strings.Contains(line, "TSAN is enabled: syscall filters less restrictive"): 644 case strings.Contains(line, "Optional feature EnablePCID not supported"): 645 case strings.Contains(line, "Optional feature EnableSMEP not supported"): 646 case strings.Contains(line, "Optional feature EnableVPID not supported"): 647 case strings.Contains(line, "Optional feature GMPWithVPID not supported"): 648 case strings.Contains(line, "Optional feature ValidateGMPPF not supported"): 649 case strings.Contains(line, "Pass-through networking enabled"): 650 // Expected in some tests that create files as 0755, 651 // ex. /gvisor/test/syscalls/linux/exec.cc 652 case strings.Contains(line, "Opened a writable executable"): 653 // Expected in some tests, eg. /gvisor/test/syscalls/linux/sysret.cc 654 case strings.Contains(line, "invalid rip for 64 bit mode"): 655 // Expected in some tests that create pipes or sockets. 656 case strings.Contains(line, "Rejecting attempt to open fifo/pipe"): 657 case strings.Contains(line, "Rejecting attempt to open unix domain socket"): 658 case strings.Contains(line, "Rejecting attempt to connect to unix domain socket"): 659 case strings.Contains(line, "Rejecting attempt to create unix domain socket"): 660 661 // Ignore clock frequency adjustment messages. 662 case strings.Contains(line, "adjusted frequency from"): 663 664 // FIXME(b/70990997): URPC error: possible race? 665 case strings.Contains(line, "urpc: error decoding: bad file descriptor"): 666 667 // FIXME(b/147228315): GVISOR_PREEMPTION_INTERRUPT not yet supported on AMD. 668 case strings.Contains(line, "Optional feature PreemptionInterrupt not supported"): 669 670 // Ignore denied dirty timestamp writebacks. It occurs because, 671 // in tests, gofer doesn't have permission to change atime. 672 case strings.Contains(line, "gofer.dentry.destroyLocked: failed to close file with write dirty timestamps: operation not permitted"): 673 case strings.Contains(line, "Tsetattrclunk failed, losing FID"): 674 // gsys_get_timekeeping_params hasn't been implemented for ARM. 675 case strings.Contains(line, "Error retrieving TSC snapshot, unable to save TSC: function not implemented"): 676 677 case *save: 678 // Ignore these warnings for S/R tests as we try to delete the sandbox 679 // after the sandbox has exited and before attempting to restore it. 680 if strings.Contains(line, "couldn't find container") || 681 strings.Contains(line, "Container not found, creating new one, cid:") || 682 strings.Contains(line, "Error sending signal") || 683 strings.Contains(line, "Cannot signal container") { 684 continue 685 } 686 687 default: 688 warningsFound = append(warningsFound, strings.TrimSpace(line)) 689 } 690 } 691 } 692 if len(warningsFound) > 0 { 693 return fmt.Errorf("%s", warningsFound) 694 } 695 // If the test passed, then we erase the log directory. This speeds up 696 // uploading logs in continuous integration & saves on disk space. 697 os.RemoveAll(testLogDir) 698 } 699 700 return err 701 } 702 703 // setupHostUDSTree updates the spec to expose a UDS files tree for testing 704 // communication with the host. 705 func setupHostUDSTree(spec *specs.Spec) (cleanup func(), err error) { 706 socketDir, cleanup, err := uds.CreateBoundUDSTree("/tmp") 707 if err != nil { 708 return nil, fmt.Errorf("failed to create socket tree: %v", err) 709 } 710 711 // Standard access to entire tree. 712 spec.Mounts = append(spec.Mounts, specs.Mount{ 713 Destination: "/tmp/sockets", 714 Source: socketDir, 715 Type: "bind", 716 }) 717 718 // Individial attach points for each socket to test mounts that attach 719 // directly to the sockets. 720 for _, protocol := range []string{"stream", "seqpacket"} { 721 for _, name := range []string{"echo", "nonlistening"} { 722 spec.Mounts = append(spec.Mounts, specs.Mount{ 723 Destination: filepath.Join("/tmp/sockets-attach", protocol, name), 724 Source: filepath.Join(socketDir, protocol, name), 725 Type: "bind", 726 }) 727 } 728 } 729 spec.Mounts = append(spec.Mounts, specs.Mount{ 730 Destination: "/tmp/sockets-attach/dgram/null", 731 Source: filepath.Join(socketDir, "dgram/null"), 732 Type: "bind", 733 }) 734 735 spec.Process.Env = append(spec.Process.Env, "TEST_UDS_TREE=/tmp/sockets") 736 spec.Process.Env = append(spec.Process.Env, "TEST_UDS_ATTACH_TREE=/tmp/sockets-attach") 737 738 return cleanup, nil 739 } 740 741 // setupHostFifoTree starts goroutines that will attempt to connect to sockets 742 // in a directory that will be bind mounted into the container. 743 func setupHostConnectorTree(spec *specs.Spec) (cleanup func(), err error) { 744 connectorDir, cleanup, err := uds.CreateSocketConnectors("/tmp") 745 if err != nil { 746 return nil, fmt.Errorf("failed to create connector tree: %v", err) 747 } 748 749 // Standard access to entire tree. 750 spec.Mounts = append(spec.Mounts, specs.Mount{ 751 Destination: "/tmp/connectors", 752 Source: connectorDir, 753 Type: "bind", 754 }) 755 // We can not create individual attach points for sockets that have not been 756 // created yet. 757 spec.Process.Env = append(spec.Process.Env, "TEST_CONNECTOR_TREE=/tmp/connectors") 758 return cleanup, nil 759 } 760 761 // setupHostFifoTree updates the spec to expose FIFO file tree for testing 762 // communication with the host. 763 func setupHostFifoTree(spec *specs.Spec) (cleanup func(), err error) { 764 fifoDir, cleanup, err := uds.CreateFifoTree("/tmp") 765 if err != nil { 766 return nil, fmt.Errorf("failed to create FIFO tree: %v", err) 767 } 768 769 // Standard access to entire tree. 770 spec.Mounts = append(spec.Mounts, specs.Mount{ 771 Destination: "/tmp/pipes", 772 Source: fifoDir, 773 Type: "bind", 774 }) 775 776 // Individual attach points for each pipe to test mounts that attach 777 // directly to the pipe. 778 for _, name := range []string{"in", "out"} { 779 spec.Mounts = append(spec.Mounts, specs.Mount{ 780 Destination: filepath.Join("/tmp/pipes-attach", name), 781 Source: filepath.Join(fifoDir, name), 782 Type: "bind", 783 }) 784 } 785 786 spec.Process.Env = append(spec.Process.Env, "TEST_FIFO_TREE=/tmp/pipes") 787 spec.Process.Env = append(spec.Process.Env, "TEST_FIFO_ATTACH_TREE=/tmp/pipes-attach") 788 789 return cleanup, nil 790 } 791 792 // runsTestCaseRunsc runs the test case in runsc. 793 func runTestCaseRunsc(testBin string, tc *gtest.TestCase, args []string, t *testing.T) { 794 // Run a new container with the test executable and filter for the 795 // given test suite and name. 796 if args == nil { 797 args = tc.Args() 798 } 799 args = append(args, gtest.TestFlags...) 800 var spec *specs.Spec 801 if *fusefs { 802 fuseServer, err := testutil.FindFile("test/runner/fuse/fuse") 803 if err != nil { 804 fatalf("cannot find fuse: %v", err) 805 } 806 cmdArgs := append([]string{testBin}, args...) 807 cmd := strings.Join(cmdArgs, " ") 808 spec = testutil.NewSpecWithArgs([]string{fuseServer, fmt.Sprintf("--debug=%t", *debug), fmt.Sprintf("--cmd=\"%s\"", cmd)}...) 809 } else { 810 spec = testutil.NewSpecWithArgs(append([]string{testBin}, args...)...) 811 } 812 // Mark the root as writeable, as some tests attempt to 813 // write to the rootfs, and expect EACCES, not EROFS. 814 spec.Root.Readonly = false 815 816 // Test spec comes with pre-defined mounts that we don't want. Reset it. 817 spec.Mounts = nil 818 testTmpDir := "/tmp" 819 if *useTmpfs { 820 // Forces '/tmp' to be mounted as tmpfs, otherwise test that rely on 821 // features only available in gVisor's internal tmpfs may fail. 822 spec.Mounts = append(spec.Mounts, specs.Mount{ 823 Destination: "/tmp", 824 Type: "tmpfs", 825 }) 826 } else { 827 // Use a gofer-backed directory for $TEST_TMPDIR. 828 // 829 // Tests might be running in parallel, so make sure each has a 830 // unique test temp dir. 831 // 832 // Some tests (e.g., sticky) access this mount from other 833 // users, so make sure it is world-accessible. 834 tmpDir, err := ioutil.TempDir(testutil.TmpDir(), "") 835 if err != nil { 836 t.Fatalf("could not create temp dir: %v", err) 837 } 838 defer os.RemoveAll(tmpDir) 839 840 if err := os.Chmod(tmpDir, 0777); err != nil { 841 t.Fatalf("could not chmod temp dir: %v", err) 842 } 843 844 testTmpDir = tmpDir 845 // Note that tmpDir exists in container rootfs mount, whose cacheability is 846 // set by fileAccess flag appropriately. 847 } 848 if *fusefs { 849 // In fuse tests, the fuse server forwards all filesystem ops from /tmp 850 // to /fuse. 851 spec.Mounts = append(spec.Mounts, specs.Mount{ 852 Destination: "/fuse", 853 Type: "tmpfs", 854 }) 855 } 856 if *network == "host" && !testutil.TestEnvSupportsNetAdmin { 857 log.Warningf("Testing with network=host but test environment does not support net admin or raw sockets. Dropping CAP_NET_ADMIN and CAP_NET_RAW.") 858 specutils.DropCapability(spec.Process.Capabilities, "CAP_NET_ADMIN") 859 specutils.DropCapability(spec.Process.Capabilities, "CAP_NET_RAW") 860 } 861 862 // Set environment variables that indicate we are running in gVisor with 863 // the given platform, network, and filesystem stack. 864 const ( 865 platformVar = "TEST_ON_GVISOR" 866 networkVar = "GVISOR_NETWORK" 867 ioUringVar = "IOURING_ENABLED" 868 fuseVar = "GVISOR_FUSE_TEST" 869 saveVar = "GVISOR_SAVE_TEST" 870 ) 871 env := append(os.Environ(), platformVar+"="+*platform, networkVar+"="+*network) 872 if *platformSupport != "" { 873 env = append(env, fmt.Sprintf("%s=%s", platformSupportEnvVar, *platformSupport)) 874 } 875 if *ioUring { 876 env = append(env, ioUringVar+"=TRUE") 877 } else { 878 env = append(env, ioUringVar+"=FALSE") 879 } 880 if *fusefs { 881 env = append(env, fuseVar+"=TRUE") 882 } else { 883 env = append(env, fuseVar+"=FALSE") 884 } 885 if *save || *saveResume { 886 env = append(env, saveVar+"=TRUE") 887 } else { 888 env = append(env, saveVar+"=FALSE") 889 } 890 891 // Remove shard env variables so that the gunit binary does not try to 892 // interpret them. 893 env = filterEnv(env, []string{"TEST_SHARD_INDEX", "TEST_TOTAL_SHARDS", "GTEST_SHARD_INDEX", "GTEST_TOTAL_SHARDS"}) 894 895 // Set TEST_TMPDIR to testTmpDir, which has been appropriately configured. 896 env = filterEnv(env, []string{"TEST_TMPDIR"}) 897 env = append(env, fmt.Sprintf("TEST_TMPDIR=%s", testTmpDir)) 898 899 spec.Process.Env = env 900 901 if *addHostUDS { 902 cleanup, err := setupHostUDSTree(spec) 903 if err != nil { 904 t.Fatalf("error creating UDS tree: %v", err) 905 } 906 defer cleanup() 907 } 908 if *addHostConnector { 909 cleanup, err := setupHostConnectorTree(spec) 910 if err != nil { 911 t.Fatalf("error creating connector tree: %v", err) 912 } 913 defer cleanup() 914 } 915 if *addHostFIFO { 916 cleanup, err := setupHostFifoTree(spec) 917 if err != nil { 918 t.Fatalf("error creating FIFO tree: %v", err) 919 } 920 defer cleanup() 921 } 922 923 // Add cgroup mount to enable cgroups for all tests. 924 spec.Mounts = append(spec.Mounts, specs.Mount{ 925 Destination: "/sys/fs/cgroup", 926 Type: "cgroup", 927 }) 928 if err := runRunsc(tc, spec); err != nil { 929 t.Errorf("test %q failed with error %v, want nil", tc.FullName(), err) 930 } 931 } 932 933 // filterEnv returns an environment with the excluded variables removed. 934 func filterEnv(env, exclude []string) []string { 935 var out []string 936 for _, kv := range env { 937 ok := true 938 for _, k := range exclude { 939 if strings.HasPrefix(kv, k+"=") { 940 ok = false 941 break 942 } 943 } 944 if ok { 945 out = append(out, kv) 946 } 947 } 948 return out 949 } 950 951 func fatalf(s string, args ...any) { 952 fmt.Fprintf(os.Stderr, s+"\n", args...) 953 os.Exit(1) 954 } 955 956 func matchString(a, b string) (bool, error) { 957 return a == b, nil 958 } 959 960 func main() { 961 flag.Parse() 962 if flag.NArg() != 1 { 963 fatalf("test must be provided") 964 } 965 testBin := flag.Args()[0] // Only argument. 966 967 log.SetLevel(log.Info) 968 if *debug { 969 log.SetLevel(log.Debug) 970 } 971 972 if *platform != "native" { 973 if err := testutil.ConfigureExePath(); err != nil { 974 panic(err.Error()) 975 } 976 } 977 978 // Make sure stdout and stderr are opened with O_APPEND, otherwise logs 979 // from outside the sandbox can (and will) stomp on logs from inside 980 // the sandbox. 981 for _, f := range []*os.File{os.Stdout, os.Stderr} { 982 flags, err := unix.FcntlInt(f.Fd(), unix.F_GETFL, 0) 983 if err != nil { 984 fatalf("error getting file flags for %v: %v", f, err) 985 } 986 if flags&unix.O_APPEND == 0 { 987 flags |= unix.O_APPEND 988 if _, err := unix.FcntlInt(f.Fd(), unix.F_SETFL, flags); err != nil { 989 fatalf("error setting file flags for %v: %v", f, err) 990 } 991 } 992 } 993 994 // Get all test cases in each binary. 995 testCases, err := gtest.ParseTestCases(testBin, true) 996 if err != nil { 997 fatalf("ParseTestCases(%q) failed: %v", testBin, err) 998 } 999 1000 // Get subset of tests corresponding to shard. 1001 indices, err := testutil.TestIndicesForShard(len(testCases)) 1002 if err != nil { 1003 fatalf("TestsForShard() failed: %v", err) 1004 } 1005 1006 // Resolve the absolute path for the binary. 1007 testBin, err = filepath.Abs(testBin) 1008 if err != nil { 1009 fatalf("Abs() failed: %v", err) 1010 } 1011 1012 var tests []testing.InternalTest 1013 if *oneSandbox { 1014 tc := gtest.TestCase{ 1015 Suite: "main", 1016 Name: "test", 1017 } 1018 1019 tests = append(tests, testing.InternalTest{ 1020 Name: fmt.Sprintf("%s_%s", tc.Suite, tc.Name), 1021 F: func(t *testing.T) { 1022 args := gtest.BuildTestArgs(indices, testCases) 1023 if *platform == "native" { 1024 // Run the test case on host. 1025 runTestCaseNative(testBin, &tc, args, t) 1026 } else { 1027 // Run the test case in runsc. 1028 runTestCaseRunsc(testBin, &tc, args, t) 1029 } 1030 }, 1031 }) 1032 } else { 1033 // Run the tests. 1034 for _, tci := range indices { 1035 // Capture tc. 1036 tc := testCases[tci] 1037 tests = append(tests, testing.InternalTest{ 1038 Name: fmt.Sprintf("%s_%s", tc.Suite, tc.Name), 1039 F: func(t *testing.T) { 1040 if *platform == "native" { 1041 // Run the test case on host. 1042 runTestCaseNative(testBin, &tc, nil, t) 1043 } else { 1044 // Run the test case in runsc. 1045 runTestCaseRunsc(testBin, &tc, nil, t) 1046 } 1047 }, 1048 }) 1049 } 1050 } 1051 1052 testing.Main(matchString, tests, nil, nil) 1053 } 1054 1055 func enableAllTraces(dir string) (string, error) { 1056 builder := config.Builder{} 1057 if err := builder.LoadAllPoints(specutils.ExePath); err != nil { 1058 return "", err 1059 } 1060 builder.AddSink(seccheck.SinkConfig{ 1061 Name: "null", 1062 }) 1063 path := filepath.Join(dir, "pod_init.json") 1064 cfgFile, err := os.Create(path) 1065 if err != nil { 1066 return "", err 1067 } 1068 defer cfgFile.Close() 1069 1070 if err := builder.WriteInitConfig(cfgFile); err != nil { 1071 return "", fmt.Errorf("writing config file: %w", err) 1072 } 1073 return "--pod-init-config=" + path, nil 1074 }