gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/test/e2e/integration_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 integration provides end-to-end integration tests for runsc. 16 // 17 // Each test calls docker commands to start up a container, and tests that it is 18 // behaving properly, with various runsc commands. The container is killed and 19 // deleted at the end. 20 // 21 // Setup instruction in test/README.md. 22 package integration 23 24 import ( 25 "bytes" 26 "context" 27 "flag" 28 "fmt" 29 "io/ioutil" 30 "net" 31 "net/http" 32 "os" 33 "os/exec" 34 "path/filepath" 35 "regexp" 36 "strconv" 37 "strings" 38 "testing" 39 "time" 40 41 "github.com/docker/docker/api/types/mount" 42 "golang.org/x/sys/unix" 43 "gvisor.dev/gvisor/pkg/test/dockerutil" 44 "gvisor.dev/gvisor/pkg/test/testutil" 45 ) 46 47 const ( 48 // defaultWait is the default wait time used for tests. 49 defaultWait = time.Minute 50 51 memInfoCmd = "cat /proc/meminfo | grep MemTotal: | awk '{print $2}'" 52 ) 53 54 func TestMain(m *testing.M) { 55 dockerutil.EnsureSupportedDockerVersion() 56 flag.Parse() 57 os.Exit(m.Run()) 58 } 59 60 // httpRequestSucceeds sends a request to a given url and checks that the status is OK. 61 func httpRequestSucceeds(client http.Client, server string, port int) error { 62 url := fmt.Sprintf("http://%s:%d", server, port) 63 // Ensure that content is being served. 64 resp, err := client.Get(url) 65 if err != nil { 66 return fmt.Errorf("error reaching http server: %v", err) 67 } 68 if want := http.StatusOK; resp.StatusCode != want { 69 return fmt.Errorf("wrong response code, got: %d, want: %d", resp.StatusCode, want) 70 } 71 return nil 72 } 73 74 // TestLifeCycle tests a basic Create/Start/Stop docker container life cycle. 75 func TestLifeCycle(t *testing.T) { 76 ctx := context.Background() 77 d := dockerutil.MakeContainer(ctx, t) 78 defer d.CleanUp(ctx) 79 80 // Start the container. 81 port := 80 82 if err := d.Create(ctx, dockerutil.RunOpts{ 83 Image: "basic/nginx", 84 Ports: []int{port}, 85 }); err != nil { 86 t.Fatalf("docker create failed: %v", err) 87 } 88 if err := d.Start(ctx); err != nil { 89 t.Fatalf("docker start failed: %v", err) 90 } 91 92 ip, err := d.FindIP(ctx, false) 93 if err != nil { 94 t.Fatalf("docker.FindIP failed: %v", err) 95 } 96 if err := testutil.WaitForHTTP(ip.String(), port, defaultWait); err != nil { 97 t.Fatalf("WaitForHTTP() timeout: %v", err) 98 } 99 client := http.Client{Timeout: defaultWait} 100 if err := httpRequestSucceeds(client, ip.String(), port); err != nil { 101 t.Errorf("http request failed: %v", err) 102 } 103 104 if err := d.Stop(ctx); err != nil { 105 t.Fatalf("docker stop failed: %v", err) 106 } 107 if err := d.Remove(ctx); err != nil { 108 t.Fatalf("docker rm failed: %v", err) 109 } 110 } 111 112 func TestPauseResume(t *testing.T) { 113 if !testutil.IsCheckpointSupported() { 114 t.Skip("Checkpoint is not supported.") 115 } 116 117 ctx := context.Background() 118 d := dockerutil.MakeContainer(ctx, t) 119 defer d.CleanUp(ctx) 120 121 // Start the container. 122 port := 8080 123 if err := d.Spawn(ctx, dockerutil.RunOpts{ 124 Image: "basic/python", 125 Ports: []int{port}, // See Dockerfile. 126 }); err != nil { 127 t.Fatalf("docker run failed: %v", err) 128 } 129 130 // Find container IP address. 131 ip, err := d.FindIP(ctx, false) 132 if err != nil { 133 t.Fatalf("docker.FindIP failed: %v", err) 134 } 135 136 // Wait until it's up and running. 137 if err := testutil.WaitForHTTP(ip.String(), port, defaultWait); err != nil { 138 t.Fatalf("WaitForHTTP() timeout: %v", err) 139 } 140 141 // Check that container is working. 142 client := http.Client{Timeout: defaultWait} 143 if err := httpRequestSucceeds(client, ip.String(), port); err != nil { 144 t.Error("http request failed:", err) 145 } 146 147 if err := d.Pause(ctx); err != nil { 148 t.Fatalf("docker pause failed: %v", err) 149 } 150 151 // Check if container is paused. 152 client = http.Client{Timeout: 10 * time.Millisecond} // Don't wait a minute. 153 switch _, err := client.Get(fmt.Sprintf("http://%s:%d", ip.String(), port)); v := err.(type) { 154 case nil: 155 t.Errorf("http req expected to fail but it succeeded") 156 case net.Error: 157 if !v.Timeout() { 158 t.Errorf("http req got error %v, wanted timeout", v) 159 } 160 default: 161 t.Errorf("http req got unexpected error %v", v) 162 } 163 164 if err := d.Unpause(ctx); err != nil { 165 t.Fatalf("docker unpause failed: %v", err) 166 } 167 168 // Wait until it's up and running. 169 if err := testutil.WaitForHTTP(ip.String(), port, defaultWait); err != nil { 170 t.Fatalf("WaitForHTTP() timeout: %v", err) 171 } 172 173 // Check if container is working again. 174 client = http.Client{Timeout: defaultWait} 175 if err := httpRequestSucceeds(client, ip.String(), port); err != nil { 176 t.Error("http request failed:", err) 177 } 178 } 179 180 func TestCheckpointRestore(t *testing.T) { 181 if !testutil.IsCheckpointSupported() { 182 t.Skip("Checkpoint is not supported.") 183 } 184 dockerutil.EnsureDockerExperimentalEnabled() 185 186 ctx := context.Background() 187 d := dockerutil.MakeContainer(ctx, t) 188 defer d.CleanUp(ctx) 189 190 // Start the container. 191 port := 8080 192 if err := d.Spawn(ctx, dockerutil.RunOpts{ 193 Image: "basic/python", 194 Ports: []int{port}, // See Dockerfile. 195 }); err != nil { 196 t.Fatalf("docker run failed: %v", err) 197 } 198 199 // Create a snapshot. 200 if err := d.Checkpoint(ctx, "test"); err != nil { 201 t.Fatalf("docker checkpoint failed: %v", err) 202 } 203 if err := d.WaitTimeout(ctx, defaultWait); err != nil { 204 t.Fatalf("wait failed: %v", err) 205 } 206 207 // TODO(b/143498576): Remove Poll after github.com/moby/moby/issues/38963 is fixed. 208 if err := testutil.Poll(func() error { return d.Restore(ctx, "test") }, defaultWait); err != nil { 209 t.Fatalf("docker restore failed: %v", err) 210 } 211 212 // Find container IP address. 213 ip, err := d.FindIP(ctx, false) 214 if err != nil { 215 t.Fatalf("docker.FindIP failed: %v", err) 216 } 217 218 // Wait until it's up and running. 219 if err := testutil.WaitForHTTP(ip.String(), port, defaultWait); err != nil { 220 t.Fatalf("WaitForHTTP() timeout: %v", err) 221 } 222 223 // Check if container is working again. 224 client := http.Client{Timeout: defaultWait} 225 if err := httpRequestSucceeds(client, ip.String(), port); err != nil { 226 t.Error("http request failed:", err) 227 } 228 } 229 230 // Create client and server that talk to each other using the local IP. 231 func TestConnectToSelf(t *testing.T) { 232 ctx := context.Background() 233 d := dockerutil.MakeContainer(ctx, t) 234 defer d.CleanUp(ctx) 235 236 // Creates server that replies "server" and exists. Sleeps at the end because 237 // 'docker exec' gets killed if the init process exists before it can finish. 238 if err := d.Spawn(ctx, dockerutil.RunOpts{ 239 Image: "basic/ubuntu", 240 }, "/bin/sh", "-c", "echo server | nc -l -p 8080 && sleep 1"); err != nil { 241 t.Fatalf("docker run failed: %v", err) 242 } 243 244 // Finds IP address for host. 245 ip, err := d.Exec(ctx, dockerutil.ExecOpts{}, "/bin/sh", "-c", "cat /etc/hosts | grep ${HOSTNAME} | awk '{print $1}'") 246 if err != nil { 247 t.Fatalf("docker exec failed: %v", err) 248 } 249 ip = strings.TrimRight(ip, "\n") 250 251 // Runs client that sends "client" to the server and exits. 252 reply, err := d.Exec(ctx, dockerutil.ExecOpts{}, "/bin/sh", "-c", fmt.Sprintf("echo client | nc %s 8080", ip)) 253 if err != nil { 254 t.Fatalf("docker exec failed: %v", err) 255 } 256 257 // Ensure both client and server got the message from each other. 258 if want := "server\n"; reply != want { 259 t.Errorf("Error on server, want: %q, got: %q", want, reply) 260 } 261 if _, err := d.WaitForOutput(ctx, "^client\n$", defaultWait); err != nil { 262 t.Fatalf("docker.WaitForOutput(client) timeout: %v", err) 263 } 264 } 265 266 func TestMemory(t *testing.T) { 267 // Find total amount of memory in the host. 268 host, err := exec.Command("sh", "-c", memInfoCmd).CombinedOutput() 269 if err != nil { 270 t.Fatal(err) 271 } 272 want, err := strconv.ParseUint(strings.TrimSpace(string(host)), 10, 64) 273 if err != nil { 274 t.Fatalf("failed to parse %q: %v", host, err) 275 } 276 277 ctx := context.Background() 278 d := dockerutil.MakeContainer(ctx, t) 279 defer d.CleanUp(ctx) 280 281 out, err := d.Run(ctx, dockerutil.RunOpts{Image: "basic/alpine"}, "sh", "-c", memInfoCmd) 282 if err != nil { 283 t.Fatalf("docker run failed: %v", err) 284 } 285 286 // Get memory from inside the container and ensure it matches the host. 287 got, err := strconv.ParseUint(strings.TrimSpace(out), 10, 64) 288 if err != nil { 289 t.Fatalf("failed to parse %q: %v", out, err) 290 } 291 if got != want { 292 t.Errorf("MemTotal got: %d, want: %d", got, want) 293 } 294 } 295 296 func TestMemLimit(t *testing.T) { 297 ctx := context.Background() 298 d := dockerutil.MakeContainer(ctx, t) 299 defer d.CleanUp(ctx) 300 301 allocMemoryKb := 128 * 1024 302 opts := dockerutil.RunOpts{ 303 Image: "basic/alpine", 304 Memory: allocMemoryKb * 1024, // In bytes. 305 } 306 out, err := d.Run(ctx, opts, "sh", "-c", memInfoCmd) 307 if err != nil { 308 t.Fatalf("docker run failed: %v", err) 309 } 310 311 // Remove warning message that swap isn't present. 312 if strings.HasPrefix(out, "WARNING") { 313 lines := strings.Split(out, "\n") 314 if len(lines) != 3 { 315 t.Fatalf("invalid output: %s", out) 316 } 317 out = lines[1] 318 } 319 320 // Ensure the memory matches what we want. 321 got, err := strconv.ParseUint(strings.TrimSpace(out), 10, 64) 322 if err != nil { 323 t.Fatalf("failed to parse %q: %v", out, err) 324 } 325 if want := uint64(allocMemoryKb); got != want { 326 t.Errorf("MemTotal got: %d, want: %d", got, want) 327 } 328 } 329 330 func TestNumCPU(t *testing.T) { 331 ctx := context.Background() 332 d := dockerutil.MakeContainer(ctx, t) 333 defer d.CleanUp(ctx) 334 335 // Read how many cores are in the container. 336 out, err := d.Run(ctx, dockerutil.RunOpts{ 337 Image: "basic/alpine", 338 CpusetCpus: "0", 339 }, "sh", "-c", "cat /proc/cpuinfo | grep 'processor.*:' | wc -l") 340 if err != nil { 341 t.Fatalf("docker run failed: %v", err) 342 } 343 344 // Ensure it matches what we want. 345 got, err := strconv.Atoi(strings.TrimSpace(out)) 346 if err != nil { 347 t.Fatalf("failed to parse %q: %v", out, err) 348 } 349 if want := 1; got != want { 350 t.Errorf("MemTotal got: %d, want: %d", got, want) 351 } 352 } 353 354 // TestJobControl tests that job control characters are handled properly. 355 func TestJobControl(t *testing.T) { 356 ctx := context.Background() 357 d := dockerutil.MakeContainer(ctx, t) 358 defer d.CleanUp(ctx) 359 360 // Start the container with an attached PTY. 361 p, err := d.SpawnProcess(ctx, dockerutil.RunOpts{ 362 Image: "basic/alpine", 363 }, "sh", "-c", "sleep 100 | cat") 364 if err != nil { 365 t.Fatalf("docker run failed: %v", err) 366 } 367 // Give shell a few seconds to start executing the sleep. 368 time.Sleep(2 * time.Second) 369 370 if _, err := p.Write(time.Second, []byte{0x03}); err != nil { 371 t.Fatalf("error exit: %v", err) 372 } 373 374 if err := d.WaitTimeout(ctx, 3*time.Second); err != nil { 375 t.Fatalf("WaitTimeout failed: %v", err) 376 } 377 378 want := 130 379 got, err := p.WaitExitStatus(ctx) 380 if err != nil { 381 t.Fatalf("wait for exit failed with: %v", err) 382 } else if got != want { 383 t.Fatalf("got: %d want: %d", got, want) 384 } 385 } 386 387 // TestWorkingDirCreation checks that working dir is created if it doesn't exit. 388 func TestWorkingDirCreation(t *testing.T) { 389 for _, tc := range []struct { 390 name string 391 workingDir string 392 }{ 393 {name: "root", workingDir: "/foo"}, 394 {name: "tmp", workingDir: "/tmp/foo"}, 395 } { 396 for _, readonly := range []bool{true, false} { 397 name := tc.name 398 if readonly { 399 name += "-readonly" 400 } 401 t.Run(name, func(t *testing.T) { 402 ctx := context.Background() 403 d := dockerutil.MakeContainer(ctx, t) 404 defer d.CleanUp(ctx) 405 406 opts := dockerutil.RunOpts{ 407 Image: "basic/alpine", 408 WorkDir: tc.workingDir, 409 ReadOnly: readonly, 410 } 411 got, err := d.Run(ctx, opts, "sh", "-c", "echo ${PWD}") 412 if err != nil { 413 t.Fatalf("docker run failed: %v", err) 414 } 415 if want := tc.workingDir + "\n"; want != got { 416 t.Errorf("invalid working dir, want: %q, got: %q", want, got) 417 } 418 }) 419 } 420 } 421 } 422 423 // TestTmpFile checks that files inside '/tmp' are not overridden. 424 func TestTmpFile(t *testing.T) { 425 ctx := context.Background() 426 d := dockerutil.MakeContainer(ctx, t) 427 defer d.CleanUp(ctx) 428 429 opts := dockerutil.RunOpts{Image: "basic/tmpfile"} 430 got, err := d.Run(ctx, opts, "cat", "/tmp/foo/file.txt") 431 if err != nil { 432 t.Fatalf("docker run failed: %v", err) 433 } 434 if want := "123\n"; want != got { 435 t.Errorf("invalid file content, want: %q, got: %q", want, got) 436 } 437 } 438 439 // TestTmpMount checks that mounts inside '/tmp' are not overridden. 440 func TestTmpMount(t *testing.T) { 441 dir, err := ioutil.TempDir(testutil.TmpDir(), "tmp-mount") 442 if err != nil { 443 t.Fatalf("TempDir(): %v", err) 444 } 445 const want = "123" 446 if err := ioutil.WriteFile(filepath.Join(dir, "file.txt"), []byte("123"), 0666); err != nil { 447 t.Fatalf("WriteFile(): %v", err) 448 } 449 ctx := context.Background() 450 d := dockerutil.MakeContainer(ctx, t) 451 defer d.CleanUp(ctx) 452 453 opts := dockerutil.RunOpts{ 454 Image: "basic/alpine", 455 Mounts: []mount.Mount{ 456 { 457 Type: mount.TypeBind, 458 Source: dir, 459 Target: "/tmp/foo", 460 }, 461 }, 462 } 463 got, err := d.Run(ctx, opts, "cat", "/tmp/foo/file.txt") 464 if err != nil { 465 t.Fatalf("docker run failed: %v", err) 466 } 467 if want != got { 468 t.Errorf("invalid file content, want: %q, got: %q", want, got) 469 } 470 } 471 472 // Test that it is allowed to mount a file on top of /dev files, e.g. 473 // /dev/random. 474 func TestMountOverDev(t *testing.T) { 475 random, err := ioutil.TempFile(testutil.TmpDir(), "random") 476 if err != nil { 477 t.Fatal("ioutil.TempFile() failed:", err) 478 } 479 const want = "123" 480 if _, err := random.WriteString(want); err != nil { 481 t.Fatalf("WriteString() to %q: %v", random.Name(), err) 482 } 483 484 ctx := context.Background() 485 d := dockerutil.MakeContainer(ctx, t) 486 defer d.CleanUp(ctx) 487 488 opts := dockerutil.RunOpts{ 489 Image: "basic/alpine", 490 Mounts: []mount.Mount{ 491 { 492 Type: mount.TypeBind, 493 Source: random.Name(), 494 Target: "/dev/random", 495 }, 496 }, 497 } 498 cmd := "dd count=1 bs=5 if=/dev/random 2> /dev/null" 499 got, err := d.Run(ctx, opts, "sh", "-c", cmd) 500 if err != nil { 501 t.Fatalf("docker run failed: %v", err) 502 } 503 if want != got { 504 t.Errorf("invalid file content, want: %q, got: %q", want, got) 505 } 506 } 507 508 // TestSyntheticDirs checks that submounts can be created inside a readonly 509 // mount even if the target path does not exist. 510 func TestSyntheticDirs(t *testing.T) { 511 ctx := context.Background() 512 d := dockerutil.MakeContainer(ctx, t) 513 defer d.CleanUp(ctx) 514 515 opts := dockerutil.RunOpts{ 516 Image: "basic/alpine", 517 // Make the root read-only to force use of synthetic dirs 518 // inside the root gofer mount. 519 ReadOnly: true, 520 Mounts: []mount.Mount{ 521 // Mount inside read-only gofer-backed root. 522 { 523 Type: mount.TypeTmpfs, 524 Target: "/foo/bar/baz", 525 }, 526 // Mount inside sysfs, which always uses synthetic dirs 527 // for submounts. 528 { 529 Type: mount.TypeTmpfs, 530 Target: "/sys/foo/bar/baz", 531 }, 532 }, 533 } 534 // Make sure the directories exist. 535 if _, err := d.Run(ctx, opts, "ls", "/foo/bar/baz", "/sys/foo/bar/baz"); err != nil { 536 t.Fatalf("docker run failed: %v", err) 537 } 538 539 } 540 541 // TestHostOverlayfsCopyUp tests that the --overlayfs-stale-read option causes 542 // runsc to hide the incoherence of FDs opened before and after overlayfs 543 // copy-up on the host. 544 func TestHostOverlayfsCopyUp(t *testing.T) { 545 runIntegrationTest(t, nil, "./test_copy_up") 546 } 547 548 // TestHostOverlayfsRewindDir tests that rewinddir() "causes the directory 549 // stream to refer to the current state of the corresponding directory, as a 550 // call to opendir() would have done" as required by POSIX, when the directory 551 // in question is host overlayfs. 552 // 553 // This test specifically targets host overlayfs because, per POSIX, "if a file 554 // is removed from or added to the directory after the most recent call to 555 // opendir() or rewinddir(), whether a subsequent call to readdir() returns an 556 // entry for that file is unspecified"; the host filesystems used by other 557 // automated tests yield newly-added files from readdir() even if the fsgofer 558 // does not explicitly rewinddir(), but overlayfs does not. 559 func TestHostOverlayfsRewindDir(t *testing.T) { 560 runIntegrationTest(t, nil, "./test_rewinddir") 561 } 562 563 // Basic test for linkat(2). Syscall tests requires CAP_DAC_READ_SEARCH and it 564 // cannot use tricks like userns as root. For this reason, run a basic link test 565 // to ensure some coverage. 566 func TestLink(t *testing.T) { 567 runIntegrationTest(t, nil, "./link_test") 568 } 569 570 // This test ensures we can run ping without errors. 571 func TestPing4Loopback(t *testing.T) { 572 runIntegrationTest(t, nil, "./ping4.sh") 573 } 574 575 // This test ensures we can enable ipv6 on loopback and run ping6 without 576 // errors. 577 func TestPing6Loopback(t *testing.T) { 578 if testutil.IsRunningWithHostNet() { 579 // TODO(gvisor.dev/issue/5011): support ICMP sockets in hostnet and enable 580 // this test. 581 t.Skip("hostnet only supports TCP/UDP sockets, so ping6 is not supported.") 582 } 583 584 // The CAP_NET_ADMIN capability is required to use the `ip` utility, which 585 // we use to enable ipv6 on loopback. 586 // 587 // By default, ipv6 loopback is not enabled by runsc, because docker does 588 // not assign an ipv6 address to the test container. 589 runIntegrationTest(t, []string{"NET_ADMIN"}, "./ping6.sh") 590 } 591 592 // This test checks that the owner of the sticky directory can delete files 593 // inside it belonging to other users. It also checks that the owner of a file 594 // can always delete its file when the file is inside a sticky directory owned 595 // by another user. 596 func TestStickyDir(t *testing.T) { 597 runIntegrationTest(t, nil, "./test_sticky") 598 } 599 600 func TestHostFD(t *testing.T) { 601 t.Run("regular", func(t *testing.T) { 602 runIntegrationTest(t, nil, "./host_fd") 603 }) 604 t.Run("tty", func(t *testing.T) { 605 ctx := context.Background() 606 d := dockerutil.MakeContainer(ctx, t) 607 defer d.CleanUp(ctx) 608 609 // Start the container with an attached PTY. 610 p, err := d.SpawnProcess(ctx, dockerutil.RunOpts{ 611 Image: "basic/integrationtest", 612 WorkDir: "/root", 613 }, "./host_fd", "-t") 614 if err != nil { 615 t.Fatalf("docker run failed: %v", err) 616 } 617 618 if err := d.Wait(ctx); err != nil { 619 t.Fatalf("Wait failed: %v", err) 620 } 621 if out, err := p.Logs(); err != nil { 622 t.Fatal(err) 623 } else if len(out) > 0 { 624 t.Errorf("test failed:\n%s", out) 625 } 626 }) 627 } 628 629 func runIntegrationTest(t *testing.T, capAdd []string, args ...string) { 630 ctx := context.Background() 631 d := dockerutil.MakeContainer(ctx, t) 632 defer d.CleanUp(ctx) 633 634 opts := dockerutil.RunOpts{ 635 Image: "basic/integrationtest", 636 WorkDir: "/root", 637 CapAdd: capAdd, 638 } 639 if got, err := d.Run(ctx, opts, args...); err != nil { 640 t.Fatalf("docker run failed: %v", err) 641 } else if got != "" { 642 t.Errorf("test failed:\n%s", got) 643 } 644 } 645 646 // Test that UDS can be created using overlay when parent directory is in lower 647 // layer only (b/134090485). 648 // 649 // Prerequisite: the directory where the socket file is created must not have 650 // been open for write before bind(2) is called. 651 func TestBindOverlay(t *testing.T) { 652 ctx := context.Background() 653 d := dockerutil.MakeContainer(ctx, t) 654 defer d.CleanUp(ctx) 655 656 // Run the container. 657 got, err := d.Run(ctx, dockerutil.RunOpts{ 658 Image: "basic/ubuntu", 659 }, "bash", "-c", "nc -q -1 -l -U /var/run/sock & p=$! && sleep 1 && echo foobar-asdf | nc -q 0 -U /var/run/sock && wait $p") 660 if err != nil { 661 t.Fatalf("docker run failed: %v", err) 662 } 663 664 // Check the output contains what we want. 665 if want := "foobar-asdf"; !strings.Contains(got, want) { 666 t.Fatalf("docker run output is missing %q: %s", want, got) 667 } 668 } 669 670 func TestStdios(t *testing.T) { 671 ctx := context.Background() 672 d := dockerutil.MakeContainer(ctx, t) 673 defer d.CleanUp(ctx) 674 675 testStdios(t, func(user string, args ...string) (string, error) { 676 defer d.CleanUp(ctx) 677 opts := dockerutil.RunOpts{ 678 Image: "basic/alpine", 679 User: user, 680 } 681 return d.Run(ctx, opts, args...) 682 }) 683 } 684 685 func TestStdiosExec(t *testing.T) { 686 ctx := context.Background() 687 d := dockerutil.MakeContainer(ctx, t) 688 defer d.CleanUp(ctx) 689 690 runOpts := dockerutil.RunOpts{Image: "basic/alpine"} 691 if err := d.Spawn(ctx, runOpts, "sleep", "100"); err != nil { 692 t.Fatalf("docker run failed: %v", err) 693 } 694 695 testStdios(t, func(user string, args ...string) (string, error) { 696 opts := dockerutil.ExecOpts{User: user} 697 return d.Exec(ctx, opts, args...) 698 }) 699 } 700 701 func testStdios(t *testing.T, run func(string, ...string) (string, error)) { 702 const cmd = "stat -L /proc/self/fd/0 /proc/self/fd/1 /proc/self/fd/2 | grep 'Uid:'" 703 got, err := run("123", "/bin/sh", "-c", cmd) 704 if err != nil { 705 t.Fatalf("docker exec failed: %v", err) 706 } 707 if len(got) == 0 { 708 t.Errorf("Unexpected empty output from %q", cmd) 709 } 710 re := regexp.MustCompile(`Uid: \(\s*(\w+)\/.*\)`) 711 for _, line := range strings.SplitN(got, "\n", 3) { 712 t.Logf("stat -L: %s", line) 713 matches := re.FindSubmatch([]byte(line)) 714 if len(matches) != 2 { 715 t.Fatalf("wrong output format: %q: matches: %v", line, matches) 716 } 717 if want, got := "123", string(matches[1]); want != got { 718 t.Errorf("wrong user, want: %q, got: %q", want, got) 719 } 720 } 721 722 // Check that stdout and stderr can be open and written to. This checks 723 // that ownership and permissions are correct inside gVisor. 724 got, err = run("456", "/bin/sh", "-c", "echo foobar | tee /proc/self/fd/1 > /proc/self/fd/2") 725 if err != nil { 726 t.Fatalf("docker run failed: %v", err) 727 } 728 t.Logf("echo foobar: %q", got) 729 // Check it repeats twice, once for stdout and once for stderr. 730 if want := "foobar\nfoobar\n"; want != got { 731 t.Errorf("Wrong echo output, want: %q, got: %q", want, got) 732 } 733 734 // Check that timestamps can be changed. Setting timestamps require an extra 735 // write check _after_ the file was opened, and may fail if the underlying 736 // host file is not setup correctly. 737 if _, err := run("789", "touch", "/proc/self/fd/0", "/proc/self/fd/1", "/proc/self/fd/2"); err != nil { 738 t.Fatalf("docker run failed: %v", err) 739 } 740 } 741 742 func TestStdiosChown(t *testing.T) { 743 ctx := context.Background() 744 d := dockerutil.MakeContainer(ctx, t) 745 defer d.CleanUp(ctx) 746 747 opts := dockerutil.RunOpts{Image: "basic/alpine"} 748 if _, err := d.Run(ctx, opts, "chown", "123", "/proc/self/fd/0", "/proc/self/fd/1", "/proc/self/fd/2"); err != nil { 749 t.Fatalf("docker run failed: %v", err) 750 } 751 } 752 753 func TestUnmount(t *testing.T) { 754 ctx := context.Background() 755 d := dockerutil.MakeContainer(ctx, t) 756 defer d.CleanUp(ctx) 757 758 dir, err := ioutil.TempDir(testutil.TmpDir(), "sub-mount") 759 if err != nil { 760 t.Fatalf("TempDir(): %v", err) 761 } 762 opts := dockerutil.RunOpts{ 763 Image: "basic/alpine", 764 Privileged: true, // Required for umount 765 Mounts: []mount.Mount{ 766 { 767 Type: mount.TypeBind, 768 Source: dir, 769 Target: "/foo", 770 }, 771 }, 772 } 773 if _, err := d.Run(ctx, opts, "umount", "/foo"); err != nil { 774 t.Fatalf("docker run failed: %v", err) 775 } 776 } 777 778 func TestDeleteInterface(t *testing.T) { 779 ctx := context.Background() 780 d := dockerutil.MakeContainer(ctx, t) 781 defer d.CleanUp(ctx) 782 783 opts := dockerutil.RunOpts{ 784 Image: "basic/alpine", 785 CapAdd: []string{"NET_ADMIN"}, 786 } 787 if err := d.Spawn(ctx, opts, "sleep", "1000"); err != nil { 788 t.Fatalf("docker run failed: %v", err) 789 } 790 791 // We should be able to remove eth0. 792 output, err := d.Exec(ctx, dockerutil.ExecOpts{}, "/bin/sh", "-c", "ip link del dev eth0") 793 if err != nil { 794 t.Fatalf("failed to remove eth0: %s, output: %s", err, output) 795 } 796 // Verify that eth0 is no longer there. 797 output, err = d.Exec(ctx, dockerutil.ExecOpts{}, "/bin/sh", "-c", "ip link show") 798 if err != nil { 799 t.Fatalf("docker exec ip link show failed: %s, output: %s", err, output) 800 } 801 if strings.Contains(output, "eth0") { 802 t.Fatalf("failed to remove eth0") 803 } 804 805 // Loopback device can't be removed. 806 output, err = d.Exec(ctx, dockerutil.ExecOpts{}, "/bin/sh", "-c", "ip link del dev lo") 807 if err == nil { 808 t.Fatalf("should not remove the loopback device: %v", output) 809 } 810 // Verify that lo is still there. 811 output, err = d.Exec(ctx, dockerutil.ExecOpts{}, "/bin/sh", "-c", "ip link show") 812 if err != nil { 813 t.Fatalf("docker exec ip link show failed: %s, output: %s", err, output) 814 } 815 if !strings.Contains(output, "lo") { 816 t.Fatalf("loopback interface is removed") 817 } 818 } 819 820 func TestProductName(t *testing.T) { 821 want, err := ioutil.ReadFile("/sys/devices/virtual/dmi/id/product_name") 822 if err != nil { 823 t.Fatal(err) 824 } 825 826 ctx := context.Background() 827 d := dockerutil.MakeContainer(ctx, t) 828 defer d.CleanUp(ctx) 829 830 opts := dockerutil.RunOpts{Image: "basic/alpine"} 831 got, err := d.Run(ctx, opts, "cat", "/sys/devices/virtual/dmi/id/product_name") 832 if err != nil { 833 t.Fatalf("docker run failed: %v", err) 834 } 835 if string(want) != got { 836 t.Errorf("invalid product name, want: %q, got: %q", want, got) 837 } 838 } 839 840 // TestRevalidateSymlinkChain tests that when a symlink in the middle of chain 841 // gets updated externally, the change is noticed and the internal cache is 842 // updated accordingly. 843 func TestRevalidateSymlinkChain(t *testing.T) { 844 ctx := context.Background() 845 d := dockerutil.MakeContainer(ctx, t) 846 defer d.CleanUp(ctx) 847 848 // Create the following structure: 849 // dir 850 // + gen1 851 // | + file [content: 123] 852 // | 853 // + gen2 854 // | + file [content: 456] 855 // | 856 // + file -> sym1/file 857 // + sym1 -> sym2 858 // + sym2 -> gen1 859 // 860 dir, err := ioutil.TempDir(testutil.TmpDir(), "sub-mount") 861 if err != nil { 862 t.Fatalf("TempDir(): %v", err) 863 } 864 if err := os.Mkdir(filepath.Join(dir, "gen1"), 0777); err != nil { 865 t.Fatal(err) 866 } 867 if err := os.Mkdir(filepath.Join(dir, "gen2"), 0777); err != nil { 868 t.Fatal(err) 869 } 870 if err := os.WriteFile(filepath.Join(dir, "gen1", "file"), []byte("123"), 0666); err != nil { 871 t.Fatal(err) 872 } 873 if err := os.WriteFile(filepath.Join(dir, "gen2", "file"), []byte("456"), 0666); err != nil { 874 t.Fatal(err) 875 } 876 if err := os.Symlink("sym1/file", filepath.Join(dir, "file")); err != nil { 877 t.Fatal(err) 878 } 879 if err := os.Symlink("sym2", filepath.Join(dir, "sym1")); err != nil { 880 t.Fatal(err) 881 } 882 if err := os.Symlink("gen1", filepath.Join(dir, "sym2")); err != nil { 883 t.Fatal(err) 884 } 885 886 // Mount dir inside the container so that external changes are propagated to 887 // the container. 888 opts := dockerutil.RunOpts{ 889 Image: "basic/alpine", 890 Privileged: true, // Required for umount 891 Mounts: []mount.Mount{ 892 { 893 Type: mount.TypeBind, 894 Source: dir, 895 Target: "/foo", 896 }, 897 }, 898 } 899 if err := d.Create(ctx, opts, "sleep", "1000"); err != nil { 900 t.Fatalf("docker run failed: %v", err) 901 } 902 if err := d.Start(ctx); err != nil { 903 t.Fatalf("docker run failed: %v", err) 904 } 905 906 // Read and cache symlinks pointing to gen1/file. 907 got, err := d.Exec(ctx, dockerutil.ExecOpts{}, "cat", "/foo/file") 908 if err != nil { 909 t.Fatalf("docker run failed: %v", err) 910 } 911 if want := "123"; got != want { 912 t.Fatalf("Read wrong file, want: %q, got: %q", want, got) 913 } 914 915 // Change the symlink to point to gen2 file. 916 if err := os.Remove(filepath.Join(dir, "sym2")); err != nil { 917 t.Fatal(err) 918 } 919 if err := os.Symlink("gen2", filepath.Join(dir, "sym2")); err != nil { 920 t.Fatal(err) 921 } 922 923 // Read symlink chain again and check that it got updated to gen2/file. 924 got, err = d.Exec(ctx, dockerutil.ExecOpts{}, "cat", "/foo/file") 925 if err != nil { 926 t.Fatalf("docker run failed: %v", err) 927 } 928 if want := "456"; got != want { 929 t.Fatalf("Read wrong file, want: %q, got: %q", want, got) 930 } 931 } 932 933 // TestTmpMountWithSize checks when 'tmpfs' is mounted 934 // with size option the limit is not exceeded. 935 func TestTmpMountWithSize(t *testing.T) { 936 ctx := context.Background() 937 d := dockerutil.MakeContainer(ctx, t) 938 defer d.CleanUp(ctx) 939 940 opts := dockerutil.RunOpts{ 941 Image: "basic/alpine", 942 Mounts: []mount.Mount{ 943 { 944 Type: mount.TypeTmpfs, 945 Target: "/tmp/foo", 946 TmpfsOptions: &mount.TmpfsOptions{ 947 SizeBytes: 4096, 948 }, 949 }, 950 }, 951 } 952 if err := d.Create(ctx, opts, "sleep", "1000"); err != nil { 953 t.Fatalf("docker create failed: %v", err) 954 } 955 if err := d.Start(ctx); err != nil { 956 t.Fatalf("docker start failed: %v", err) 957 } 958 959 if _, err := d.Exec(ctx, dockerutil.ExecOpts{}, "/bin/sh", "-c", "echo hello > /tmp/foo/test1.txt"); err != nil { 960 t.Fatalf("docker exec failed: %v", err) 961 } 962 echoOutput, err := d.Exec(ctx, dockerutil.ExecOpts{}, "/bin/sh", "-c", "echo world > /tmp/foo/test2.txt") 963 if err == nil { 964 t.Fatalf("docker exec size check unexpectedly succeeded (output: %v)", echoOutput) 965 } 966 wantErr := "No space left on device" 967 if !strings.Contains(echoOutput, wantErr) { 968 t.Errorf("unexpected echo error:Expected: %v, Got: %v", wantErr, echoOutput) 969 } 970 } 971 972 // NOTE(b/236028361): Regression test. Check we can handle a working directory 973 // without execute permissions. See comment in 974 // pkg/sentry/kernel/kernel.go:CreateProcess() for more context. 975 func TestNonSearchableWorkingDirectory(t *testing.T) { 976 dir, err := os.MkdirTemp(testutil.TmpDir(), "tmp-mount") 977 if err != nil { 978 t.Fatalf("MkdirTemp() failed: %v", err) 979 } 980 defer os.RemoveAll(dir) 981 982 // The container will run as a non-root user. Make dir not searchable by 983 // others by removing execute bit for others. 984 if err := os.Chmod(dir, 0766); err != nil { 985 t.Fatalf("Chmod() failed: %v", err) 986 } 987 ctx := context.Background() 988 d := dockerutil.MakeContainer(ctx, t) 989 defer d.CleanUp(ctx) 990 991 targetMount := "/foo" 992 opts := dockerutil.RunOpts{ 993 Image: "basic/alpine", 994 Mounts: []mount.Mount{ 995 { 996 Type: mount.TypeBind, 997 Source: dir, 998 Target: targetMount, 999 }, 1000 }, 1001 WorkDir: targetMount, 1002 User: "nobody", 1003 } 1004 1005 echoPhrase := "All izz well" 1006 got, err := d.Run(ctx, opts, "sh", "-c", "echo "+echoPhrase+" && (ls || true)") 1007 if err != nil { 1008 t.Fatalf("docker run failed: %v", err) 1009 } 1010 if !strings.Contains(got, echoPhrase) { 1011 t.Errorf("echo output not found, want: %q, got: %q", echoPhrase, got) 1012 } 1013 if wantErrorMsg := "Permission denied"; !strings.Contains(got, wantErrorMsg) { 1014 t.Errorf("ls error message not found, want: %q, got: %q", wantErrorMsg, got) 1015 } 1016 } 1017 1018 func TestCharDevice(t *testing.T) { 1019 if testutil.IsRunningWithOverlay() { 1020 t.Skip("files are not available outside the sandbox with overlay.") 1021 } 1022 1023 ctx := context.Background() 1024 d := dockerutil.MakeContainer(ctx, t) 1025 defer d.CleanUp(ctx) 1026 1027 dir, err := os.MkdirTemp(testutil.TmpDir(), "tmp-mount") 1028 if err != nil { 1029 t.Fatalf("MkdirTemp() failed: %v", err) 1030 } 1031 defer os.RemoveAll(dir) 1032 1033 opts := dockerutil.RunOpts{ 1034 Image: "basic/alpine", 1035 Mounts: []mount.Mount{ 1036 { 1037 Type: mount.TypeBind, 1038 Source: "/dev/zero", 1039 Target: "/test/zero", 1040 }, 1041 { 1042 Type: mount.TypeBind, 1043 Source: dir, 1044 Target: "/out", 1045 }, 1046 }, 1047 } 1048 1049 const size = 1024 * 1024 1050 1051 // `docker logs` encodes the string, making it hard to compare. Write the 1052 // result to a file that is available to the test. 1053 cmd := fmt.Sprintf("head -c %d /test/zero > /out/result", size) 1054 if _, err := d.Run(ctx, opts, "sh", "-c", cmd); err != nil { 1055 t.Fatalf("docker run failed: %v", err) 1056 } 1057 got, err := os.ReadFile(filepath.Join(dir, "result")) 1058 if err != nil { 1059 t.Fatal(err) 1060 } 1061 if want := [size]byte{}; !bytes.Equal(want[:], got) { 1062 t.Errorf("Wrong bytes, want: [all zeros], got: %v", got) 1063 } 1064 } 1065 1066 func TestBlockHostUds(t *testing.T) { 1067 ctx := context.Background() 1068 d := dockerutil.MakeContainer(ctx, t) 1069 defer d.CleanUp(ctx) 1070 1071 dir, err := os.MkdirTemp(testutil.TmpDir(), "tmp-mount") 1072 if err != nil { 1073 t.Fatalf("MkdirTemp() failed: %v", err) 1074 } 1075 defer os.RemoveAll(dir) 1076 1077 dirFD, err := unix.Open(dir, unix.O_PATH, 0) 1078 if err != nil { 1079 t.Fatalf("failed to open %s: %v", dir, err) 1080 } 1081 defer unix.Close(dirFD) 1082 // Use /proc/self/fd to generate path to avoid EINVAL on large path. 1083 l, err := net.Listen("unix", filepath.Join("/proc/self/fd", strconv.Itoa(dirFD), "test.sock")) 1084 if err != nil { 1085 t.Fatalf("listen error: %v", err) 1086 } 1087 defer l.Close() 1088 1089 opts := dockerutil.RunOpts{ 1090 Image: "basic/integrationtest", 1091 WorkDir: "/root", 1092 Mounts: []mount.Mount{ 1093 { 1094 Type: mount.TypeBind, 1095 Source: dir, 1096 Target: "/dir", 1097 }, 1098 }, 1099 } 1100 if err := d.Spawn(ctx, opts, "sleep", "infinity"); err != nil { 1101 t.Fatalf("docker run failed: %v", err) 1102 } 1103 // Application should be able to walk/stat the UDS ... 1104 if got, err := d.Exec(ctx, dockerutil.ExecOpts{}, "stat", "/dir/test.sock"); err != nil { 1105 t.Fatalf("stat(2)-ing the UDS failed: output = %q, err = %v", got, err) 1106 } 1107 // ... but not connect to it. 1108 const want = "connect: Connection refused" 1109 if got, err := d.Exec(ctx, dockerutil.ExecOpts{}, "./host_connect", "/dir/test.sock"); err == nil || !strings.Contains(got, want) { 1110 t.Errorf("err should be non-nil and output should contain %q, but got err = %v and output = %q", want, err, got) 1111 } 1112 } 1113 1114 func readLogs(logs string, position int) (int, error) { 1115 if len(logs) == 0 { 1116 return 0, fmt.Errorf("error no content was read") 1117 } 1118 1119 nums := strings.Split(logs, "\n") 1120 if position >= len(nums) { 1121 return 0, fmt.Errorf("position %v is not within the length of content %v", position, nums) 1122 } 1123 if position == -1 { 1124 // Expectation of newline at the end of last position. 1125 position = len(nums) - 2 1126 } 1127 num, err := strconv.Atoi(nums[position]) 1128 if err != nil { 1129 return 0, fmt.Errorf("error getting number from file: %v", err) 1130 } 1131 1132 return num, nil 1133 } 1134 1135 func checkLogs(logs string, oldPos int) error { 1136 if len(logs) == 0 { 1137 return fmt.Errorf("error no content was read") 1138 } 1139 1140 nums := strings.Split(logs, "\n") 1141 // Expectation of newline at the end of last position. 1142 if oldPos >= len(nums)-2 { 1143 return fmt.Errorf("oldPos %v is not within the length of content %v", oldPos, nums) 1144 } 1145 for i := oldPos + 1; i < len(nums)-1; i++ { 1146 num, err := strconv.Atoi(nums[i]) 1147 if err != nil { 1148 return fmt.Errorf("error getting number from file: %v", err) 1149 } 1150 if num != oldPos+1 { 1151 return fmt.Errorf("error in save/resume, numbers not in order, previous: %d, next: %d", oldPos, num) 1152 } 1153 oldPos++ 1154 } 1155 return nil 1156 } 1157 1158 // Checkpoint the container and continue running. 1159 func TestCheckpointResume(t *testing.T) { 1160 if !testutil.IsCheckpointSupported() { 1161 t.Skip("Checkpoint is not supported.") 1162 } 1163 dockerutil.EnsureDockerExperimentalEnabled() 1164 1165 ctx := context.Background() 1166 d := dockerutil.MakeContainer(ctx, t) 1167 defer d.CleanUp(ctx) 1168 1169 // Start the container. 1170 if err := d.Spawn(ctx, dockerutil.RunOpts{ 1171 Image: "basic/alpine", 1172 }, "sh", "-c", "i=0; while true; do echo \"$i\"; i=\"$(expr \"$i\" + 1)\"; sleep .01; done"); err != nil { 1173 t.Fatalf("docker run failed: %v", err) 1174 } 1175 1176 time.Sleep(2 * time.Second) 1177 1178 // Get the logs before checkpointing. 1179 logs, err := d.Logs(ctx) 1180 if err != nil { 1181 t.Fatalf("docker logs failed: %v", err) 1182 } 1183 1184 // Get the last position of the logs printed. 1185 pos, err := readLogs(logs, -1) 1186 if err != nil { 1187 t.Fatalf("readLogs failed: %v", err) 1188 } 1189 1190 // Create a snapshot and continue running. 1191 if err := d.CheckpointResume(ctx, "test"); err != nil { 1192 t.Fatalf("docker checkpoint failed: %v", err) 1193 } 1194 1195 var newLogs string 1196 // Wait for the container to resume running and print new logs. 1197 if err := testutil.Poll(func() error { 1198 // Get the logs after checkpointing to check if the container resumed. 1199 newLogs, err = d.Logs(ctx) 1200 if err != nil { 1201 t.Fatalf("docker logs failed: %v", err) 1202 } 1203 return nil 1204 }, defaultWait); err != nil { 1205 t.Fatalf("container read logs failed after resume: %v", err) 1206 } 1207 1208 if err := checkLogs(newLogs, pos); err != nil { 1209 t.Fatalf("checkLogs failed: %v", err) 1210 } 1211 if err := d.Kill(ctx); err != nil { 1212 t.Fatalf("docker kill failed: %v", err) 1213 } 1214 }