From bc0e12a047cce2cc717320257545663073383f48 Mon Sep 17 00:00:00 2001 From: Daniel J Walsh Date: Thu, 13 May 2021 08:45:29 -0400 Subject: Fix problem copying files when container is in host pid namespace When attempting to copy files into and out of running containers within the host pidnamespace, the code was attempting to join the host pidns again, and getting an error. This was causing the podman cp command to fail. Since we are already in the host pid namespace, we should not be attempting to join. This PR adds a check to see if the container is in NOT host pid namespace, and only then attempts to join. Fixes: https://github.com/containers/podman/issues/9985 Signed-off-by: Daniel J Walsh --- libpod/container_copy_linux.go | 25 +++++++++++++++++------- libpod/container_inspect.go | 23 ++++++++++++++++++++++ test/e2e/cp_test.go | 44 ++++++++++++++++++++++++++++++++++++++++++ test/system/065-cp.bats | 16 +++++++++++++++ 4 files changed, 101 insertions(+), 7 deletions(-) diff --git a/libpod/container_copy_linux.go b/libpod/container_copy_linux.go index 5c275c641..0ab322829 100644 --- a/libpod/container_copy_linux.go +++ b/libpod/container_copy_linux.go @@ -237,21 +237,32 @@ func (c *Container) joinMountAndExec(ctx context.Context, f func() error) error } defer mountFD.Close() - pidFD, err := getFD(PIDNS) + inHostPidNS, err := c.inHostPidNS() if err != nil { - errChan <- err + errChan <- errors.Wrap(err, "checking inHostPidNS") return } - defer pidFD.Close() - if err := unix.Unshare(unix.CLONE_NEWNS); err != nil { - errChan <- err - return + var pidFD *os.File + if !inHostPidNS { + pidFD, err = getFD(PIDNS) + if err != nil { + errChan <- err + return + } + defer pidFD.Close() } - if err := unix.Setns(int(pidFD.Fd()), unix.CLONE_NEWPID); err != nil { + + if err := unix.Unshare(unix.CLONE_NEWNS); err != nil { errChan <- err return } + if pidFD != nil { + if err := unix.Setns(int(pidFD.Fd()), unix.CLONE_NEWPID); err != nil { + errChan <- err + return + } + } if err := unix.Setns(int(mountFD.Fd()), unix.CLONE_NEWNS); err != nil { errChan <- err return diff --git a/libpod/container_inspect.go b/libpod/container_inspect.go index 4210bc581..638e0b756 100644 --- a/libpod/container_inspect.go +++ b/libpod/container_inspect.go @@ -892,3 +892,26 @@ func (c *Container) generateInspectContainerHostConfig(ctrSpec *spec.Spec, named return hostConfig, nil } + +// Return true if the container is running in the host's PID NS. +func (c *Container) inHostPidNS() (bool, error) { + if c.config.PIDNsCtr != "" { + return false, nil + } + ctrSpec, err := c.specFromState() + if err != nil { + return false, err + } + if ctrSpec.Linux != nil { + // Locate the spec's PID namespace. + // If there is none, it's pid=host. + // If there is one and it has a path, it's "ns:". + // If there is no path, it's default - the empty string. + for _, ns := range ctrSpec.Linux.Namespaces { + if ns.Type == spec.PIDNamespace { + return false, nil + } + } + } + return true, nil +} diff --git a/test/e2e/cp_test.go b/test/e2e/cp_test.go index c0fb3f887..be4901833 100644 --- a/test/e2e/cp_test.go +++ b/test/e2e/cp_test.go @@ -91,6 +91,50 @@ var _ = Describe("Podman cp", func() { Expect(roundtripContent).To(Equal(originalContent)) }) + // Copy a file to the container, then back to the host in --pid=host + It("podman cp --pid=host file", func() { + SkipIfRootlessCgroupsV1("Not supported for rootless + CGroupsV1") + srcFile, err := ioutil.TempFile("", "") + Expect(err).To(BeNil()) + defer srcFile.Close() + defer os.Remove(srcFile.Name()) + + originalContent := []byte("podman cp file test") + err = ioutil.WriteFile(srcFile.Name(), originalContent, 0644) + Expect(err).To(BeNil()) + + // Create a container. NOTE that container mustn't be running for copying. + session := podmanTest.Podman([]string{"create", "--pid=host", ALPINE, "top"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + name := session.OutputToString() + + session = podmanTest.Podman([]string{"start", name}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + // The file will now be created (and written to). + session = podmanTest.Podman([]string{"cp", srcFile.Name(), name + ":foo"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + // Copy FROM the container. + + destFile, err := ioutil.TempFile("", "") + Expect(err).To(BeNil()) + defer destFile.Close() + defer os.Remove(destFile.Name()) + + session = podmanTest.Podman([]string{"cp", name + ":foo", destFile.Name()}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + // Now make sure the content matches. + roundtripContent, err := ioutil.ReadFile(destFile.Name()) + Expect(err).To(BeNil()) + Expect(roundtripContent).To(Equal(originalContent)) + }) + // Create a symlink in the container, use it as a copy destination and // make sure that the link and the resolved path are accessible and // give the right content. diff --git a/test/system/065-cp.bats b/test/system/065-cp.bats index 679cdc209..24ac8118e 100644 --- a/test/system/065-cp.bats +++ b/test/system/065-cp.bats @@ -130,6 +130,22 @@ load helpers } +@test "podman cp file from/to host while --pid=host" { + if is_rootless && ! is_cgroupsv2; then + skip "'podman cp --pid=host' (rootless) only works with cgroups v2" + fi + + srcdir=$PODMAN_TMPDIR/cp-pid-equals-host + mkdir -p $srcdir + touch $srcdir/hostfile + + run_podman run --pid=host -d --name cpcontainer $IMAGE sleep infinity + run_podman cp $srcdir/hostfile cpcontainer:/tmp/hostfile + run_podman cp cpcontainer:/tmp/hostfile $srcdir/hostfile1 + run_podman kill cpcontainer + run_podman rm -f cpcontainer +} + @test "podman cp file from container to host" { srcdir=$PODMAN_TMPDIR/cp-test-file-ctr-to-host mkdir -p $srcdir -- cgit v1.2.3-54-g00ecf