github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/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 "flag" 21 "fmt" 22 "io/ioutil" 23 "os" 24 "os/exec" 25 "os/signal" 26 "path/filepath" 27 "strings" 28 "syscall" 29 "testing" 30 "time" 31 32 specs "github.com/opencontainers/runtime-spec/specs-go" 33 "github.com/syndtr/gocapability/capability" 34 "golang.org/x/sys/unix" 35 "github.com/SagerNet/gvisor/pkg/log" 36 "github.com/SagerNet/gvisor/pkg/test/testutil" 37 "github.com/SagerNet/gvisor/runsc/specutils" 38 "github.com/SagerNet/gvisor/test/runner/gtest" 39 "github.com/SagerNet/gvisor/test/uds" 40 ) 41 42 var ( 43 debug = flag.Bool("debug", false, "enable debug logs") 44 strace = flag.Bool("strace", false, "enable strace logs") 45 platform = flag.String("platform", "ptrace", "platform to run on") 46 network = flag.String("network", "none", "network stack to run on (sandbox, host, none)") 47 useTmpfs = flag.Bool("use-tmpfs", false, "mounts tmpfs for /tmp") 48 fileAccess = flag.String("file-access", "exclusive", "mounts root in exclusive or shared mode") 49 overlay = flag.Bool("overlay", false, "wrap filesystem mounts with writable tmpfs overlay") 50 vfs2 = flag.Bool("vfs2", false, "enable VFS2") 51 fuse = flag.Bool("fuse", false, "enable FUSE") 52 container = flag.Bool("container", false, "run tests in their own namespaces (user ns, network ns, etc), pretending to be root") 53 setupContainerPath = flag.String("setup-container", "", "path to setup_container binary (for use with --container)") 54 runscPath = flag.String("runsc", "", "path to runsc binary") 55 56 addUDSTree = flag.Bool("add-uds-tree", false, "expose a tree of UDS utilities for use in tests") 57 // TODO(github.com/SagerNet/issue/4572): properly support leak checking for runsc, and 58 // set to true as the default for the test runner. 59 leakCheck = flag.Bool("leak-check", false, "check for reference leaks") 60 ) 61 62 // runTestCaseNative runs the test case directly on the host machine. 63 func runTestCaseNative(testBin string, tc gtest.TestCase, t *testing.T) { 64 // These tests might be running in parallel, so make sure they have a 65 // unique test temp dir. 66 tmpDir, err := ioutil.TempDir(testutil.TmpDir(), "") 67 if err != nil { 68 t.Fatalf("could not create temp dir: %v", err) 69 } 70 defer os.RemoveAll(tmpDir) 71 72 // Replace TEST_TMPDIR in the current environment with something 73 // unique. 74 env := os.Environ() 75 newEnvVar := "TEST_TMPDIR=" + tmpDir 76 var found bool 77 for i, kv := range env { 78 if strings.HasPrefix(kv, "TEST_TMPDIR=") { 79 env[i] = newEnvVar 80 found = true 81 break 82 } 83 } 84 if !found { 85 env = append(env, newEnvVar) 86 } 87 // Remove shard env variables so that the gunit binary does not try to 88 // interpret them. 89 env = filterEnv(env, []string{"TEST_SHARD_INDEX", "TEST_TOTAL_SHARDS", "GTEST_SHARD_INDEX", "GTEST_TOTAL_SHARDS"}) 90 91 if *addUDSTree { 92 socketDir, cleanup, err := uds.CreateSocketTree("/tmp") 93 if err != nil { 94 t.Fatalf("failed to create socket tree: %v", err) 95 } 96 defer cleanup() 97 98 env = append(env, "TEST_UDS_TREE="+socketDir) 99 // On Linux, the concept of "attach" location doesn't exist. 100 // Just pass the same path to make these test identical. 101 env = append(env, "TEST_UDS_ATTACH_TREE="+socketDir) 102 } 103 104 cmd := exec.Command(testBin, tc.Args()...) 105 cmd.Env = env 106 cmd.Stdout = os.Stdout 107 cmd.Stderr = os.Stderr 108 cmd.SysProcAttr = &unix.SysProcAttr{} 109 110 if *container { 111 // setup_container takes in its target argv as positional arguments. 112 cmd.Path = *setupContainerPath 113 cmd.Args = append([]string{cmd.Path}, cmd.Args...) 114 cmd.SysProcAttr = &unix.SysProcAttr{ 115 Cloneflags: unix.CLONE_NEWUSER | unix.CLONE_NEWNET | unix.CLONE_NEWIPC | unix.CLONE_NEWUTS, 116 // Set current user/group as root inside the namespace. 117 UidMappings: []syscall.SysProcIDMap{ 118 {ContainerID: 0, HostID: os.Getuid(), Size: 1}, 119 }, 120 GidMappings: []syscall.SysProcIDMap{ 121 {ContainerID: 0, HostID: os.Getgid(), Size: 1}, 122 }, 123 GidMappingsEnableSetgroups: false, 124 Credential: &syscall.Credential{ 125 Uid: 0, 126 Gid: 0, 127 }, 128 } 129 } 130 131 if specutils.HasCapabilities(capability.CAP_SYS_ADMIN) { 132 cmd.SysProcAttr.Cloneflags |= unix.CLONE_NEWUTS 133 } 134 135 if specutils.HasCapabilities(capability.CAP_NET_ADMIN) { 136 cmd.SysProcAttr.Cloneflags |= unix.CLONE_NEWNET 137 } 138 139 if err := cmd.Run(); err != nil { 140 ws := err.(*exec.ExitError).Sys().(syscall.WaitStatus) 141 t.Errorf("test %q exited with status %d, want 0", tc.FullName(), ws.ExitStatus()) 142 } 143 } 144 145 // runRunsc runs spec in runsc in a standard test configuration. 146 // 147 // runsc logs will be saved to a path in TEST_UNDECLARED_OUTPUTS_DIR. 148 // 149 // Returns an error if the sandboxed application exits non-zero. 150 func runRunsc(tc gtest.TestCase, spec *specs.Spec) error { 151 bundleDir, cleanup, err := testutil.SetupBundleDir(spec) 152 if err != nil { 153 return fmt.Errorf("SetupBundleDir failed: %v", err) 154 } 155 defer cleanup() 156 157 rootDir, cleanup, err := testutil.SetupRootDir() 158 if err != nil { 159 return fmt.Errorf("SetupRootDir failed: %v", err) 160 } 161 defer cleanup() 162 163 name := tc.FullName() 164 id := testutil.RandomContainerID() 165 log.Infof("Running test %q in container %q", name, id) 166 specutils.LogSpec(spec) 167 168 args := []string{ 169 "-root", rootDir, 170 "-network", *network, 171 "-log-format=text", 172 "-TESTONLY-unsafe-nonroot=true", 173 "-net-raw=true", 174 fmt.Sprintf("-panic-signal=%d", unix.SIGTERM), 175 "-watchdog-action=panic", 176 "-platform", *platform, 177 "-file-access", *fileAccess, 178 } 179 if *overlay { 180 args = append(args, "-overlay") 181 } 182 if *vfs2 { 183 args = append(args, "-vfs2") 184 if *fuse { 185 args = append(args, "-fuse") 186 } 187 } 188 if *debug { 189 args = append(args, "-debug", "-log-packets=true") 190 } 191 if *strace { 192 args = append(args, "-strace") 193 } 194 if *addUDSTree { 195 args = append(args, "-fsgofer-host-uds") 196 } 197 if *leakCheck { 198 args = append(args, "-ref-leak-mode=log-names") 199 } 200 201 testLogDir := "" 202 if undeclaredOutputsDir, ok := unix.Getenv("TEST_UNDECLARED_OUTPUTS_DIR"); ok { 203 // Create log directory dedicated for this test. 204 testLogDir = filepath.Join(undeclaredOutputsDir, strings.Replace(name, "/", "_", -1)) 205 if err := os.MkdirAll(testLogDir, 0755); err != nil { 206 return fmt.Errorf("could not create test dir: %v", err) 207 } 208 debugLogDir, err := ioutil.TempDir(testLogDir, "runsc") 209 if err != nil { 210 return fmt.Errorf("could not create temp dir: %v", err) 211 } 212 debugLogDir += "/" 213 log.Infof("runsc logs: %s", debugLogDir) 214 args = append(args, "-debug-log", debugLogDir) 215 args = append(args, "-coverage-report", debugLogDir) 216 217 // Default -log sends messages to stderr which makes reading the test log 218 // difficult. Instead, drop them when debug log is enabled given it's a 219 // better place for these messages. 220 args = append(args, "-log=/dev/null") 221 } 222 223 // Current process doesn't have CAP_SYS_ADMIN, create user namespace and run 224 // as root inside that namespace to get it. 225 rArgs := append(args, "run", "--bundle", bundleDir, id) 226 cmd := exec.Command(*runscPath, rArgs...) 227 cmd.SysProcAttr = &unix.SysProcAttr{ 228 Cloneflags: unix.CLONE_NEWUSER | unix.CLONE_NEWNS, 229 // Set current user/group as root inside the namespace. 230 UidMappings: []syscall.SysProcIDMap{ 231 {ContainerID: 0, HostID: os.Getuid(), Size: 1}, 232 }, 233 GidMappings: []syscall.SysProcIDMap{ 234 {ContainerID: 0, HostID: os.Getgid(), Size: 1}, 235 }, 236 GidMappingsEnableSetgroups: false, 237 Credential: &syscall.Credential{ 238 Uid: 0, 239 Gid: 0, 240 }, 241 } 242 cmd.Stdout = os.Stdout 243 cmd.Stderr = os.Stderr 244 sig := make(chan os.Signal, 1) 245 defer close(sig) 246 signal.Notify(sig, unix.SIGTERM) 247 defer signal.Stop(sig) 248 go func() { 249 s, ok := <-sig 250 if !ok { 251 return 252 } 253 log.Warningf("%s: Got signal: %v", name, s) 254 done := make(chan bool, 1) 255 dArgs := append([]string{}, args...) 256 dArgs = append(dArgs, "-alsologtostderr=true", "debug", "--stacks", id) 257 go func(dArgs []string) { 258 debug := exec.Command(*runscPath, dArgs...) 259 debug.Stdout = os.Stdout 260 debug.Stderr = os.Stderr 261 debug.Run() 262 done <- true 263 }(dArgs) 264 265 timeout := time.After(3 * time.Second) 266 select { 267 case <-timeout: 268 log.Infof("runsc debug --stacks is timeouted") 269 case <-done: 270 } 271 272 log.Warningf("Send SIGTERM to the sandbox process") 273 dArgs = append(args, "debug", 274 fmt.Sprintf("--signal=%d", unix.SIGTERM), 275 id) 276 signal := exec.Command(*runscPath, dArgs...) 277 signal.Stdout = os.Stdout 278 signal.Stderr = os.Stderr 279 signal.Run() 280 }() 281 282 err = cmd.Run() 283 if err == nil && len(testLogDir) > 0 { 284 // If the test passed, then we erase the log directory. This speeds up 285 // uploading logs in continuous integration & saves on disk space. 286 os.RemoveAll(testLogDir) 287 } 288 289 return err 290 } 291 292 // setupUDSTree updates the spec to expose a UDS tree for gofer socket testing. 293 func setupUDSTree(spec *specs.Spec) (cleanup func(), err error) { 294 socketDir, cleanup, err := uds.CreateSocketTree("/tmp") 295 if err != nil { 296 return nil, fmt.Errorf("failed to create socket tree: %v", err) 297 } 298 299 // Standard access to entire tree. 300 spec.Mounts = append(spec.Mounts, specs.Mount{ 301 Destination: "/tmp/sockets", 302 Source: socketDir, 303 Type: "bind", 304 }) 305 306 // Individial attach points for each socket to test mounts that attach 307 // directly to the sockets. 308 spec.Mounts = append(spec.Mounts, specs.Mount{ 309 Destination: "/tmp/sockets-attach/stream/echo", 310 Source: filepath.Join(socketDir, "stream/echo"), 311 Type: "bind", 312 }) 313 spec.Mounts = append(spec.Mounts, specs.Mount{ 314 Destination: "/tmp/sockets-attach/stream/nonlistening", 315 Source: filepath.Join(socketDir, "stream/nonlistening"), 316 Type: "bind", 317 }) 318 spec.Mounts = append(spec.Mounts, specs.Mount{ 319 Destination: "/tmp/sockets-attach/seqpacket/echo", 320 Source: filepath.Join(socketDir, "seqpacket/echo"), 321 Type: "bind", 322 }) 323 spec.Mounts = append(spec.Mounts, specs.Mount{ 324 Destination: "/tmp/sockets-attach/seqpacket/nonlistening", 325 Source: filepath.Join(socketDir, "seqpacket/nonlistening"), 326 Type: "bind", 327 }) 328 spec.Mounts = append(spec.Mounts, specs.Mount{ 329 Destination: "/tmp/sockets-attach/dgram/null", 330 Source: filepath.Join(socketDir, "dgram/null"), 331 Type: "bind", 332 }) 333 334 spec.Process.Env = append(spec.Process.Env, "TEST_UDS_TREE=/tmp/sockets") 335 spec.Process.Env = append(spec.Process.Env, "TEST_UDS_ATTACH_TREE=/tmp/sockets-attach") 336 337 return cleanup, nil 338 } 339 340 // runsTestCaseRunsc runs the test case in runsc. 341 func runTestCaseRunsc(testBin string, tc gtest.TestCase, t *testing.T) { 342 // Run a new container with the test executable and filter for the 343 // given test suite and name. 344 spec := testutil.NewSpecWithArgs(append([]string{testBin}, tc.Args()...)...) 345 346 // Mark the root as writeable, as some tests attempt to 347 // write to the rootfs, and expect EACCES, not EROFS. 348 spec.Root.Readonly = false 349 350 // Test spec comes with pre-defined mounts that we don't want. Reset it. 351 spec.Mounts = nil 352 testTmpDir := "/tmp" 353 if *useTmpfs { 354 // Forces '/tmp' to be mounted as tmpfs, otherwise test that rely on 355 // features only available in gVisor's internal tmpfs may fail. 356 spec.Mounts = append(spec.Mounts, specs.Mount{ 357 Destination: "/tmp", 358 Type: "tmpfs", 359 }) 360 } else { 361 // Use a gofer-backed directory as '/tmp'. 362 // 363 // Tests might be running in parallel, so make sure each has a 364 // unique test temp dir. 365 // 366 // Some tests (e.g., sticky) access this mount from other 367 // users, so make sure it is world-accessible. 368 tmpDir, err := ioutil.TempDir(testutil.TmpDir(), "") 369 if err != nil { 370 t.Fatalf("could not create temp dir: %v", err) 371 } 372 defer os.RemoveAll(tmpDir) 373 374 if err := os.Chmod(tmpDir, 0777); err != nil { 375 t.Fatalf("could not chmod temp dir: %v", err) 376 } 377 378 // "/tmp" is not replaced with a tmpfs mount inside the sandbox 379 // when it's not empty. This ensures that testTmpDir uses gofer 380 // in exclusive mode. 381 testTmpDir = tmpDir 382 if *fileAccess == "shared" { 383 // All external mounts except the root mount are shared. 384 spec.Mounts = append(spec.Mounts, specs.Mount{ 385 Type: "bind", 386 Destination: "/tmp", 387 Source: tmpDir, 388 }) 389 testTmpDir = "/tmp" 390 } 391 } 392 393 // Set environment variables that indicate we are running in gVisor with 394 // the given platform, network, and filesystem stack. 395 platformVar := "TEST_ON_GVISOR" 396 networkVar := "GVISOR_NETWORK" 397 env := append(os.Environ(), platformVar+"="+*platform, networkVar+"="+*network) 398 vfsVar := "GVISOR_VFS" 399 if *vfs2 { 400 env = append(env, vfsVar+"=VFS2") 401 fuseVar := "FUSE_ENABLED" 402 if *fuse { 403 env = append(env, fuseVar+"=TRUE") 404 } else { 405 env = append(env, fuseVar+"=FALSE") 406 } 407 } else { 408 env = append(env, vfsVar+"=VFS1") 409 } 410 411 // Remove shard env variables so that the gunit binary does not try to 412 // interpret them. 413 env = filterEnv(env, []string{"TEST_SHARD_INDEX", "TEST_TOTAL_SHARDS", "GTEST_SHARD_INDEX", "GTEST_TOTAL_SHARDS"}) 414 415 // Set TEST_TMPDIR to /tmp, as some of the syscall tests require it to 416 // be backed by tmpfs. 417 env = filterEnv(env, []string{"TEST_TMPDIR"}) 418 env = append(env, fmt.Sprintf("TEST_TMPDIR=%s", testTmpDir)) 419 420 spec.Process.Env = env 421 422 if *addUDSTree { 423 cleanup, err := setupUDSTree(spec) 424 if err != nil { 425 t.Fatalf("error creating UDS tree: %v", err) 426 } 427 defer cleanup() 428 } 429 430 if err := runRunsc(tc, spec); err != nil { 431 t.Errorf("test %q failed with error %v, want nil", tc.FullName(), err) 432 } 433 } 434 435 // filterEnv returns an environment with the excluded variables removed. 436 func filterEnv(env, exclude []string) []string { 437 var out []string 438 for _, kv := range env { 439 ok := true 440 for _, k := range exclude { 441 if strings.HasPrefix(kv, k+"=") { 442 ok = false 443 break 444 } 445 } 446 if ok { 447 out = append(out, kv) 448 } 449 } 450 return out 451 } 452 453 func fatalf(s string, args ...interface{}) { 454 fmt.Fprintf(os.Stderr, s+"\n", args...) 455 os.Exit(1) 456 } 457 458 func matchString(a, b string) (bool, error) { 459 return a == b, nil 460 } 461 462 func main() { 463 flag.Parse() 464 if flag.NArg() != 1 { 465 fatalf("test must be provided") 466 } 467 testBin := flag.Args()[0] // Only argument. 468 469 log.SetLevel(log.Info) 470 if *debug { 471 log.SetLevel(log.Debug) 472 } 473 474 if *platform != "native" && *runscPath == "" { 475 if err := testutil.ConfigureExePath(); err != nil { 476 panic(err.Error()) 477 } 478 *runscPath = specutils.ExePath 479 } 480 if *container && *setupContainerPath == "" { 481 setupContainer, err := testutil.FindFile("test/runner/setup_container/setup_container") 482 if err != nil { 483 fatalf("cannot find setup_container: %v", err) 484 } 485 *setupContainerPath = setupContainer 486 } 487 488 // Make sure stdout and stderr are opened with O_APPEND, otherwise logs 489 // from outside the sandbox can (and will) stomp on logs from inside 490 // the sandbox. 491 for _, f := range []*os.File{os.Stdout, os.Stderr} { 492 flags, err := unix.FcntlInt(f.Fd(), unix.F_GETFL, 0) 493 if err != nil { 494 fatalf("error getting file flags for %v: %v", f, err) 495 } 496 if flags&unix.O_APPEND == 0 { 497 flags |= unix.O_APPEND 498 if _, err := unix.FcntlInt(f.Fd(), unix.F_SETFL, flags); err != nil { 499 fatalf("error setting file flags for %v: %v", f, err) 500 } 501 } 502 } 503 504 // Get all test cases in each binary. 505 testCases, err := gtest.ParseTestCases(testBin, true) 506 if err != nil { 507 fatalf("ParseTestCases(%q) failed: %v", testBin, err) 508 } 509 510 // Get subset of tests corresponding to shard. 511 indices, err := testutil.TestIndicesForShard(len(testCases)) 512 if err != nil { 513 fatalf("TestsForShard() failed: %v", err) 514 } 515 516 // Resolve the absolute path for the binary. 517 testBin, err = filepath.Abs(testBin) 518 if err != nil { 519 fatalf("Abs() failed: %v", err) 520 } 521 522 // Run the tests. 523 var tests []testing.InternalTest 524 for _, tci := range indices { 525 // Capture tc. 526 tc := testCases[tci] 527 tests = append(tests, testing.InternalTest{ 528 Name: fmt.Sprintf("%s_%s", tc.Suite, tc.Name), 529 F: func(t *testing.T) { 530 if *platform == "native" { 531 // Run the test case on host. 532 runTestCaseNative(testBin, tc, t) 533 } else { 534 // Run the test case in runsc. 535 runTestCaseRunsc(testBin, tc, t) 536 } 537 }, 538 }) 539 } 540 541 testing.Main(matchString, tests, nil, nil) 542 }