482bfd
From c140ecdc9b416ab4efd4d21d14acd63b6adbdd42 Mon Sep 17 00:00:00 2001
482bfd
From: Matthew Heon <matthew.heon@pm.me>
482bfd
Date: Mon, 10 Feb 2020 13:37:38 -0500
482bfd
Subject: [PATCH] Do not copy up when volume is not empty
482bfd
482bfd
When Docker performs a copy up, it first verifies that the volume
482bfd
being copied into is empty; thus, for volumes that have been
482bfd
modified elsewhere (e.g. manually copying into then), the copy up
482bfd
will not be performed at all. Duplicate this behavior in Podman
482bfd
by checking if the volume is empty before copying.
482bfd
482bfd
Furthermore, move setting copyup to false further up. This will
482bfd
prevent a potential race where copy up could happen more than
482bfd
once if Podman was killed after some files had been copied but
482bfd
before the DB was updated.
482bfd
482bfd
This resolves CVE-2020-1726.
482bfd
482bfd
Signed-off-by: Matthew Heon <matthew.heon@pm.me>
482bfd
---
482bfd
 libpod/container_internal.go | 28 ++++++++++++++++++++++------
482bfd
 test/e2e/run_volume_test.go  | 24 ++++++++++++++++++++++++
482bfd
 2 files changed, 46 insertions(+), 6 deletions(-)
482bfd
482bfd
diff -up ./libpod-5cc92849f7fc9dd734ca2fd8f3ae8830b9a7eb26/libpod/container_internal.go.1801152 ./libpod-5cc92849f7fc9dd734ca2fd8f3ae8830b9a7eb26/libpod/container_internal.go
482bfd
--- libpod-5cc92849f7fc9dd734ca2fd8f3ae8830b9a7eb26/libpod/container_internal.go.1801152	2020-02-21 17:08:38.015363357 +0100
482bfd
+++ libpod-5cc92849f7fc9dd734ca2fd8f3ae8830b9a7eb26/libpod/container_internal.go	2020-02-21 17:08:38.019363413 +0100
482bfd
@@ -1358,18 +1358,34 @@ func (c *Container) mountNamedVolume(v *
482bfd
 	}
482bfd
 	if vol.state.NeedsCopyUp {
482bfd
 		logrus.Debugf("Copying up contents from container %s to volume %s", c.ID(), vol.Name())
482bfd
+
482bfd
+		// Set NeedsCopyUp to false immediately, so we don't try this
482bfd
+		// again when there are already files copied.
482bfd
+		vol.state.NeedsCopyUp = false
482bfd
+		if err := vol.save(); err != nil {
482bfd
+			return nil, err
482bfd
+		}
482bfd
+
482bfd
+		// If the volume is not empty, we should not copy up.
482bfd
+		volMount := vol.MountPoint()
482bfd
+		contents, err := ioutil.ReadDir(volMount)
482bfd
+		if err != nil {
482bfd
+			return nil, errors.Wrapf(err, "error listing contents of volume %s mountpoint when copying up from container %s", vol.Name(), c.ID())
482bfd
+		}
482bfd
+		if len(contents) > 0 {
482bfd
+			// The volume is not empty. It was likely modified
482bfd
+			// outside of Podman. For safety, let's not copy up into
482bfd
+			// it. Fixes CVE-2020-1726.
482bfd
+			return vol, nil
482bfd
+		}
482bfd
+
482bfd
 		srcDir, err := securejoin.SecureJoin(mountpoint, v.Dest)
482bfd
 		if err != nil {
482bfd
 			return nil, errors.Wrapf(err, "error calculating destination path to copy up container %s volume %s", c.ID(), vol.Name())
482bfd
 		}
482bfd
-		if err := c.copyWithTarFromImage(srcDir, vol.MountPoint()); err != nil && !os.IsNotExist(err) {
482bfd
+		if err := c.copyWithTarFromImage(srcDir, volMount); err != nil && !os.IsNotExist(err) {
482bfd
 			return nil, errors.Wrapf(err, "error copying content from container %s into volume %s", c.ID(), vol.Name())
482bfd
 		}
482bfd
-
482bfd
-		vol.state.NeedsCopyUp = false
482bfd
-		if err := vol.save(); err != nil {
482bfd
-			return nil, err
482bfd
-		}
482bfd
 	}
482bfd
 	return vol, nil
482bfd
 }
482bfd
diff -up ./libpod-5cc92849f7fc9dd734ca2fd8f3ae8830b9a7eb26/test/e2e/run_volume_test.go.1801152 ./libpod-5cc92849f7fc9dd734ca2fd8f3ae8830b9a7eb26/test/e2e/run_volume_test.go
482bfd
--- libpod-5cc92849f7fc9dd734ca2fd8f3ae8830b9a7eb26/test/e2e/run_volume_test.go.1801152	2020-02-21 17:08:38.042363735 +0100
482bfd
+++ libpod-5cc92849f7fc9dd734ca2fd8f3ae8830b9a7eb26/test/e2e/run_volume_test.go	2020-02-21 17:08:38.046363791 +0100
482bfd
@@ -375,4 +375,28 @@ var _ = Describe("Podman run with volume
482bfd
 		volMount.WaitWithDefaultTimeout()
482bfd
 		Expect(volMount.ExitCode()).To(Not(Equal(0)))
482bfd
 	})
482bfd
+
482bfd
+	It("Podman fix for CVE-2020-1726", func() {
482bfd
+		volName := "testVol"
482bfd
+		volCreate := podmanTest.Podman([]string{"volume", "create", volName})
482bfd
+		volCreate.WaitWithDefaultTimeout()
482bfd
+		Expect(volCreate.ExitCode()).To(Equal(0))
482bfd
+
482bfd
+		volPath := podmanTest.Podman([]string{"volume", "inspect", "--format", "{{.Mountpoint}}", volName})
482bfd
+		volPath.WaitWithDefaultTimeout()
482bfd
+		Expect(volPath.ExitCode()).To(Equal(0))
482bfd
+		path := volPath.OutputToString()
482bfd
+
482bfd
+		fileName := "thisIsATestFile"
482bfd
+		file, err := os.Create(filepath.Join(path, fileName))
482bfd
+		Expect(err).To(BeNil())
482bfd
+		defer file.Close()
482bfd
+
482bfd
+		runLs := podmanTest.Podman([]string{"run", "-t", "-i", "--rm", "-v", fmt.Sprintf("%v:/etc/ssl", volName), ALPINE, "ls", "-1", "/etc/ssl"})
482bfd
+		runLs.WaitWithDefaultTimeout()
482bfd
+		Expect(runLs.ExitCode()).To(Equal(0))
482bfd
+		outputArr := runLs.OutputToStringArray()
482bfd
+		Expect(len(outputArr)).To(Equal(1))
482bfd
+		Expect(strings.Contains(outputArr[0], fileName)).To(BeTrue())
482bfd
+	})
482bfd
 })