Procházet zdrojové kódy

Merge pull request #41893 from AkihiroSuda/fix-41457

rootless: support --pid=host
Akihiro Suda před 4 roky
rodič
revize
c8ff7305f6

+ 0 - 22
integration-cli/docker_cli_run_test.go

@@ -2425,28 +2425,6 @@ func (s *DockerSuite) TestContainerNetworkMode(c *testing.T) {
 	}
 }
 
-func (s *DockerSuite) TestRunModePIDHost(c *testing.T) {
-	// Not applicable on Windows as uses Unix-specific capabilities
-	testRequires(c, testEnv.IsLocalDaemon, DaemonIsLinux, NotUserNamespace)
-
-	hostPid, err := os.Readlink("/proc/1/ns/pid")
-	if err != nil {
-		c.Fatal(err)
-	}
-
-	out, _ := dockerCmd(c, "run", "--pid=host", "busybox", "readlink", "/proc/self/ns/pid")
-	out = strings.Trim(out, "\n")
-	if hostPid != out {
-		c.Fatalf("PID different with --pid=host %s != %s\n", hostPid, out)
-	}
-
-	out, _ = dockerCmd(c, "run", "busybox", "readlink", "/proc/self/ns/pid")
-	out = strings.Trim(out, "\n")
-	if hostPid == out {
-		c.Fatalf("PID should be different without --pid=host %s == %s\n", hostPid, out)
-	}
-}
-
 func (s *DockerSuite) TestRunModeUTSHost(c *testing.T) {
 	// Not applicable on Windows as uses Unix-specific capabilities
 	testRequires(c, testEnv.IsLocalDaemon, DaemonIsLinux)

+ 37 - 0
integration/container/pidmode_linux_test.go

@@ -0,0 +1,37 @@
+package container // import "github.com/docker/docker/integration/container"
+
+import (
+	"context"
+	"os"
+	"testing"
+	"time"
+
+	"github.com/docker/docker/integration/internal/container"
+	"gotest.tools/v3/assert"
+	"gotest.tools/v3/poll"
+	"gotest.tools/v3/skip"
+)
+
+func TestPidHost(t *testing.T) {
+	skip.If(t, testEnv.DaemonInfo.OSType != "linux")
+	skip.If(t, testEnv.IsRemoteDaemon())
+
+	hostPid, err := os.Readlink("/proc/1/ns/pid")
+	assert.NilError(t, err)
+
+	defer setupTest(t)()
+	client := testEnv.APIClient()
+	ctx := context.Background()
+
+	cID := container.Run(ctx, t, client, func(c *container.TestContainerConfig) {
+		c.HostConfig.PidMode = "host"
+	})
+	poll.WaitOn(t, container.IsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond))
+	cPid := container.GetContainerNS(ctx, t, client, cID, "pid")
+	assert.Assert(t, hostPid == cPid)
+
+	cID = container.Run(ctx, t, client)
+	poll.WaitOn(t, container.IsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond))
+	cPid = container.GetContainerNS(ctx, t, client, cID, "pid")
+	assert.Assert(t, hostPid != cPid)
+}

+ 2 - 13
integration/container/run_cgroupns_linux_test.go

@@ -2,7 +2,6 @@ package container // import "github.com/docker/docker/integration/container"
 
 import (
 	"context"
-	"strings"
 	"testing"
 	"time"
 
@@ -11,20 +10,10 @@ import (
 	"github.com/docker/docker/integration/internal/requirement"
 	"github.com/docker/docker/testutil/daemon"
 	"gotest.tools/v3/assert"
-	is "gotest.tools/v3/assert/cmp"
 	"gotest.tools/v3/poll"
 	"gotest.tools/v3/skip"
 )
 
-// Gets the value of the cgroup namespace for pid 1 of a container
-func containerCgroupNamespace(ctx context.Context, t *testing.T, client *client.Client, cID string) string {
-	res, err := container.Exec(ctx, client, cID, []string{"readlink", "/proc/1/ns/cgroup"})
-	assert.NilError(t, err)
-	assert.Assert(t, is.Len(res.Stderr(), 0))
-	assert.Equal(t, 0, res.ExitCode)
-	return strings.TrimSpace(res.Stdout())
-}
-
 // Bring up a daemon with the specified default cgroup namespace mode, and then create a container with the container options
 func testRunWithCgroupNs(t *testing.T, daemonNsMode string, containerOpts ...func(*container.TestContainerConfig)) (string, string) {
 	d := daemon.New(t, daemon.WithDefaultCgroupNamespaceMode(daemonNsMode))
@@ -38,7 +27,7 @@ func testRunWithCgroupNs(t *testing.T, daemonNsMode string, containerOpts ...fun
 	poll.WaitOn(t, container.IsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond))
 
 	daemonCgroup := d.CgroupNamespace(t)
-	containerCgroup := containerCgroupNamespace(ctx, t, client, cID)
+	containerCgroup := container.GetContainerNS(ctx, t, client, cID, "cgroup")
 	return containerCgroup, daemonCgroup
 }
 
@@ -147,7 +136,7 @@ func TestCgroupNamespacesRunOlderClient(t *testing.T) {
 	poll.WaitOn(t, container.IsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond))
 
 	daemonCgroup := d.CgroupNamespace(t)
-	containerCgroup := containerCgroupNamespace(ctx, t, client, cID)
+	containerCgroup := container.GetContainerNS(ctx, t, client, cID, "cgroup")
 	if testEnv.DaemonInfo.CgroupVersion != "2" {
 		assert.Assert(t, daemonCgroup == containerCgroup)
 	} else {

+ 21 - 0
integration/internal/container/ns.go

@@ -0,0 +1,21 @@
+package container
+
+import (
+	"context"
+	"strings"
+	"testing"
+
+	"github.com/docker/docker/client"
+	"gotest.tools/v3/assert"
+	is "gotest.tools/v3/assert/cmp"
+)
+
+// GetContainerNS gets the value of the specified namespace of a container
+func GetContainerNS(ctx context.Context, t *testing.T, client client.APIClient, cID, nsName string) string {
+	t.Helper()
+	res, err := Exec(ctx, client, cID, []string{"readlink", "/proc/self/ns/" + nsName})
+	assert.NilError(t, err)
+	assert.Assert(t, is.Len(res.Stderr(), 0))
+	assert.Equal(t, 0, res.ExitCode)
+	return strings.TrimSpace(res.Stdout())
+}

+ 60 - 0
rootless/specconv/specconv_linux.go

@@ -2,6 +2,8 @@ package specconv // import "github.com/docker/docker/rootless/specconv"
 
 import (
 	"io/ioutil"
+	"os"
+	"path"
 	"strconv"
 	"strings"
 
@@ -12,6 +14,7 @@ import (
 // ToRootless converts spec to be compatible with "rootless" runc.
 // * Remove non-supported cgroups
 // * Fix up OOMScoreAdj
+// * Fix up /proc if --pid=host
 //
 // v2Controllers should be non-nil only if running with v2 and systemd.
 func ToRootless(spec *specs.Spec, v2Controllers []string) error {
@@ -75,5 +78,62 @@ func toRootless(spec *specs.Spec, v2Controllers []string, currentOOMScoreAdj int
 	if spec.Process.OOMScoreAdj != nil && *spec.Process.OOMScoreAdj < currentOOMScoreAdj {
 		*spec.Process.OOMScoreAdj = currentOOMScoreAdj
 	}
+
+	// Fix up /proc if --pid=host
+	pidHost, err := isPidHost(spec)
+	if err != nil {
+		return err
+	}
+	if !pidHost {
+		return nil
+	}
+	return bindMountHostProcfs(spec)
+}
+
+func isPidHost(spec *specs.Spec) (bool, error) {
+	for _, ns := range spec.Linux.Namespaces {
+		if ns.Type == specs.PIDNamespace {
+			if ns.Path == "" {
+				return false, nil
+			}
+			pidNS, err := os.Readlink(ns.Path)
+			if err != nil {
+				return false, err
+			}
+			selfPidNS, err := os.Readlink("/proc/self/ns/pid")
+			if err != nil {
+				return false, err
+			}
+			return pidNS == selfPidNS, nil
+		}
+	}
+	return true, nil
+}
+
+func bindMountHostProcfs(spec *specs.Spec) error {
+	// Replace procfs mount with rbind
+	// https://github.com/containers/podman/blob/v3.0.0-rc1/pkg/specgen/generate/oci.go#L248-L257
+	for i, m := range spec.Mounts {
+		if path.Clean(m.Destination) == "/proc" {
+			newM := specs.Mount{
+				Destination: "/proc",
+				Type:        "bind",
+				Source:      "/proc",
+				Options:     []string{"rbind", "nosuid", "noexec", "nodev"},
+			}
+			spec.Mounts[i] = newM
+		}
+	}
+
+	// Remove ReadonlyPaths for /proc/*
+	newROP := spec.Linux.ReadonlyPaths[:0]
+	for _, s := range spec.Linux.ReadonlyPaths {
+		s = path.Clean(s)
+		if !strings.HasPrefix(s, "/proc/") {
+			newROP = append(newROP, s)
+		}
+	}
+	spec.Linux.ReadonlyPaths = newROP
+
 	return nil
 }