diff --git a/runsc/boot/loader.go b/runsc/boot/loader.go index 3490376e2..1d974d1e4 100644 --- a/runsc/boot/loader.go +++ b/runsc/boot/loader.go @@ -801,12 +801,14 @@ func (l *Loader) startSubcontainer(spec *specs.Spec, conf *config.Config, cid st if ns.Path != "" { for _, p := range l.processes { if ns.Path == p.pidnsPath { + log.Debugf("Joining PID namespace named %q", ns.Path) pidns = p.tg.PIDNamespace() break } } } if pidns == nil { + log.Warningf("PID namespace %q not found, running in new PID namespace", ns.Path) pidns = l.k.RootPIDNamespace().NewChild(l.k.RootUserNamespace()) } ep.pidnsPath = ns.Path diff --git a/runsc/cmd/create.go b/runsc/cmd/create.go index a1643ce25..b6d3dea54 100644 --- a/runsc/cmd/create.go +++ b/runsc/cmd/create.go @@ -45,7 +45,7 @@ type Create struct { // userLog is the path to send user-visible logs to. This log is different // from debug logs. The former is meant to be consumed by the users and should // contain only information that is relevant to the person running the - // container, e.g. unsuported syscalls, while the later is more verbose and + // container, e.g. unsupported syscalls, while the later is more verbose and // consumed by developers. userLog string } diff --git a/runsc/container/multi_container_test.go b/runsc/container/multi_container_test.go index c7628eda3..15d4f6500 100644 --- a/runsc/container/multi_container_test.go +++ b/runsc/container/multi_container_test.go @@ -176,7 +176,7 @@ func TestMultiContainerSanity(t *testing.T) { // TestMultiPIDNS checks that it is possible to run 2 dead-simple containers in // the same sandbox with different pidns. func TestMultiPIDNS(t *testing.T) { - for name, conf := range configs(t, false /* noOverlay */) { + for name, conf := range configs(t, true /* noOverlay */) { t.Run(name, func(t *testing.T) { rootDir, cleanup, err := testutil.SetupRootDir() if err != nil { @@ -248,7 +248,7 @@ func TestMultiPIDNS(t *testing.T) { // TestMultiPIDNSPath checks the pidns path. func TestMultiPIDNSPath(t *testing.T) { - for name, conf := range configs(t, false /* noOverlay */) { + for name, conf := range configs(t, true /* noOverlay */) { t.Run(name, func(t *testing.T) { rootDir, cleanup, err := testutil.SetupRootDir() if err != nil { @@ -366,7 +366,7 @@ func TestMultiPIDNSKill(t *testing.T) { t.Fatal("error finding test_app:", err) } - for name, conf := range configs(t, false /* noOverlay */) { + for name, conf := range configs(t, true /* noOverlay */) { t.Run(name, func(t *testing.T) { rootDir, cleanup, err := testutil.SetupRootDir() if err != nil { @@ -451,6 +451,93 @@ func TestMultiPIDNSKill(t *testing.T) { } } +// TestMultiPIDNSRoot checks that the sandbox PID namespace can be used to +// reference the root PID namespace of the sandbox. +func TestMultiPIDNSRoot(t *testing.T) { + for name, conf := range configs(t, true /* noOverlay */) { + t.Run(name, func(t *testing.T) { + rootDir, cleanup, err := testutil.SetupRootDir() + if err != nil { + t.Fatalf("error creating root dir: %v", err) + } + defer cleanup() + conf.RootDir = rootDir + + // Setup the containers. One in the root PID namespace and another in a + // sub-namespace. + sleep := []string{"sleep", "100"} + testSpecs, ids := createSpecs(sleep, sleep, sleep) + testSpecs[1].Linux = &specs.Linux{ + Namespaces: []specs.LinuxNamespace{ + { + Type: "pid", + Path: "/proc/1/ns/pid", + }, + }, + } + + // Start 2 containers first, and use the 3rd to join the sandbox pidns. + delayedSpec, delayedID := testSpecs[2], ids[2] + testSpecs = testSpecs[:2] + ids = ids[:2] + + containers, cleanup, err := startContainers(conf, testSpecs, ids) + if err != nil { + t.Fatalf("error starting containers: %v", err) + } + defer cleanup() + + delayedSpec.Linux = &specs.Linux{ + Namespaces: []specs.LinuxNamespace{ + { + Type: "pid", + Path: fmt.Sprintf("/proc/%d/ns/pid", containers[0].SandboxPid()), + }, + }, + } + delayed, cleanup, err := startContainers(conf, []*specs.Spec{delayedSpec}, []string{delayedID}) + if err != nil { + t.Fatalf("error starting sub-container: %v", err) + } + defer cleanup() + + // Wait for all container processes to be up and running. + expectedPL := []*control.Process{ + newProcessBuilder().PID(1).PPID(0).Cmd("sleep").Process(), + } + if err := waitForProcessList(containers[0], expectedPL); err != nil { + t.Errorf("failed to wait for sleep to start: %v", err) + } + expectedPL = []*control.Process{ + newProcessBuilder().PID(2).PPID(0).Cmd("sleep").Process(), + } + if err := waitForProcessList(containers[1], expectedPL); err != nil { + t.Fatalf("failed to wait for sleep to start: %v", err) + } + expectedPL = []*control.Process{ + newProcessBuilder().PID(3).PPID(0).Cmd("sleep").Process(), + } + if err := waitForProcessList(delayed[0], expectedPL); err != nil { + t.Fatalf("failed to wait for sleep to start: %v", err) + } + + // Check that delayer container is running in the root PID namespace and + // can see all other processes. + expectedPL = []*control.Process{ + newProcessBuilder().PID(1).Cmd("sleep").Process(), + newProcessBuilder().PID(2).Cmd("sleep").Process(), + newProcessBuilder().PID(3).Cmd("sleep").Process(), + newProcessBuilder().Cmd("ps").Process(), + } + if got, err := execPS(conf, delayed[0]); err != nil { + t.Fatal(err) + } else if !procListsEqual(got, expectedPL) { + t.Fatalf("container got process list: %s, want: %s", procListToString(got), procListToString(expectedPL)) + } + }) + } +} + func TestMultiContainerWait(t *testing.T) { rootDir, cleanup, err := testutil.SetupRootDir() if err != nil { diff --git a/runsc/sandbox/sandbox.go b/runsc/sandbox/sandbox.go index 5b6036586..1703e4f7f 100644 --- a/runsc/sandbox/sandbox.go +++ b/runsc/sandbox/sandbox.go @@ -403,6 +403,7 @@ func (s *Sandbox) StartSubcontainer(spec *specs.Spec, conf *config.Config, cid s if err := s.configureStdios(conf, stdios); err != nil { return err } + s.fixPidns(spec) // The payload contains (in this specific order): // * stdin/stdout/stderr (optional: only present when not using TTY) @@ -1518,3 +1519,29 @@ func (s *Sandbox) CgroupsWriteControlFile(file control.CgroupControlFile, value } return out.Results[0].AsError() } + +// fixPidns looks at the PID namespace path. If that path corresponds to the +// sandbox process PID namespace, then change the spec so that the container +// joins the sandbox root namespace. +func (s *Sandbox) fixPidns(spec *specs.Spec) { + pidns, ok := specutils.GetNS(specs.PIDNamespace, spec) + if !ok { + // pidns was not set, nothing to fix. + return + } + if pidns.Path != fmt.Sprintf("/proc/%d/ns/pid", s.Pid.load()) { + // Fix only if the PID namespace corresponds to the sandbox's. + return + } + + for i := range spec.Linux.Namespaces { + if spec.Linux.Namespaces[i].Type == specs.PIDNamespace { + // Removing the namespace makes the container join the sandbox root + // namespace. + log.Infof("Fixing PID namespace in spec from %q to make the container join the sandbox root namespace", pidns.Path) + spec.Linux.Namespaces = append(spec.Linux.Namespaces[:i], spec.Linux.Namespaces[i+1:]...) + return + } + } + panic("unreachable") +}