mounts_linux_test.go 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. package container // import "github.com/docker/docker/integration/container"
  2. import (
  3. "bytes"
  4. "context"
  5. "fmt"
  6. "testing"
  7. "github.com/docker/docker/api/types"
  8. "github.com/docker/docker/api/types/container"
  9. "github.com/docker/docker/api/types/mount"
  10. "github.com/docker/docker/api/types/network"
  11. "github.com/docker/docker/client"
  12. "github.com/docker/docker/integration-cli/daemon"
  13. "github.com/docker/docker/pkg/stdcopy"
  14. "github.com/docker/docker/pkg/system"
  15. "github.com/gotestyourself/gotestyourself/fs"
  16. "github.com/gotestyourself/gotestyourself/skip"
  17. "github.com/stretchr/testify/assert"
  18. "github.com/stretchr/testify/require"
  19. )
  20. func TestContainerShmNoLeak(t *testing.T) {
  21. t.Parallel()
  22. d := daemon.New(t, "docker", "dockerd", daemon.Config{})
  23. client, err := d.NewClient()
  24. if err != nil {
  25. t.Fatal(err)
  26. }
  27. d.StartWithBusybox(t)
  28. defer d.Stop(t)
  29. ctx := context.Background()
  30. cfg := container.Config{
  31. Image: "busybox",
  32. Cmd: []string{"top"},
  33. }
  34. ctr, err := client.ContainerCreate(ctx, &cfg, nil, nil, "")
  35. if err != nil {
  36. t.Fatal(err)
  37. }
  38. defer client.ContainerRemove(ctx, ctr.ID, types.ContainerRemoveOptions{Force: true})
  39. if err := client.ContainerStart(ctx, ctr.ID, types.ContainerStartOptions{}); err != nil {
  40. t.Fatal(err)
  41. }
  42. // this should recursively bind mount everything in the test daemons root
  43. // except of course we are hoping that the previous containers /dev/shm mount did not leak into this new container
  44. hc := container.HostConfig{
  45. Mounts: []mount.Mount{
  46. {
  47. Type: mount.TypeBind,
  48. Source: d.Root,
  49. Target: "/testdaemonroot",
  50. BindOptions: &mount.BindOptions{Propagation: mount.PropagationRPrivate}},
  51. },
  52. }
  53. cfg.Cmd = []string{"/bin/sh", "-c", fmt.Sprintf("mount | grep testdaemonroot | grep containers | grep %s", ctr.ID)}
  54. cfg.AttachStdout = true
  55. cfg.AttachStderr = true
  56. ctrLeak, err := client.ContainerCreate(ctx, &cfg, &hc, nil, "")
  57. if err != nil {
  58. t.Fatal(err)
  59. }
  60. attach, err := client.ContainerAttach(ctx, ctrLeak.ID, types.ContainerAttachOptions{
  61. Stream: true,
  62. Stdout: true,
  63. Stderr: true,
  64. })
  65. if err != nil {
  66. t.Fatal(err)
  67. }
  68. if err := client.ContainerStart(ctx, ctrLeak.ID, types.ContainerStartOptions{}); err != nil {
  69. t.Fatal(err)
  70. }
  71. buf := bytes.NewBuffer(nil)
  72. if _, err := stdcopy.StdCopy(buf, buf, attach.Reader); err != nil {
  73. t.Fatal(err)
  74. }
  75. out := bytes.TrimSpace(buf.Bytes())
  76. if !bytes.Equal(out, []byte{}) {
  77. t.Fatalf("mount leaked: %s", string(out))
  78. }
  79. }
  80. func TestContainerNetworkMountsNoChown(t *testing.T) {
  81. // chown only applies to Linux bind mounted volumes; must be same host to verify
  82. skip.If(t, testEnv.DaemonInfo.OSType != "linux" || !testEnv.IsLocalDaemon())
  83. defer setupTest(t)()
  84. ctx := context.Background()
  85. tmpDir := fs.NewDir(t, "network-file-mounts", fs.WithMode(0755), fs.WithFile("nwfile", "network file bind mount", fs.WithMode(0644)))
  86. defer tmpDir.Remove()
  87. tmpNWFileMount := tmpDir.Join("nwfile")
  88. config := container.Config{
  89. Image: "busybox",
  90. }
  91. hostConfig := container.HostConfig{
  92. Mounts: []mount.Mount{
  93. {
  94. Type: "bind",
  95. Source: tmpNWFileMount,
  96. Target: "/etc/resolv.conf",
  97. },
  98. {
  99. Type: "bind",
  100. Source: tmpNWFileMount,
  101. Target: "/etc/hostname",
  102. },
  103. {
  104. Type: "bind",
  105. Source: tmpNWFileMount,
  106. Target: "/etc/hosts",
  107. },
  108. },
  109. }
  110. cli, err := client.NewEnvClient()
  111. require.NoError(t, err)
  112. defer cli.Close()
  113. ctrCreate, err := cli.ContainerCreate(ctx, &config, &hostConfig, &network.NetworkingConfig{}, "")
  114. require.NoError(t, err)
  115. // container will exit immediately because of no tty, but we only need the start sequence to test the condition
  116. err = cli.ContainerStart(ctx, ctrCreate.ID, types.ContainerStartOptions{})
  117. require.NoError(t, err)
  118. // Check that host-located bind mount network file did not change ownership when the container was started
  119. // Note: If the user specifies a mountpath from the host, we should not be
  120. // attempting to chown files outside the daemon's metadata directory
  121. // (represented by `daemon.repository` at init time).
  122. // This forces users who want to use user namespaces to handle the
  123. // ownership needs of any external files mounted as network files
  124. // (/etc/resolv.conf, /etc/hosts, /etc/hostname) separately from the
  125. // daemon. In all other volume/bind mount situations we have taken this
  126. // same line--we don't chown host file content.
  127. // See GitHub PR 34224 for details.
  128. statT, err := system.Stat(tmpNWFileMount)
  129. require.NoError(t, err)
  130. assert.Equal(t, uint32(0), statT.UID(), "bind mounted network file should not change ownership from root")
  131. }