diff --git a/SOURCES/0001-rootfs-add-mount-destination-validation.patch b/SOURCES/0001-rootfs-add-mount-destination-validation.patch deleted file mode 100644 index 7d84489..0000000 --- a/SOURCES/0001-rootfs-add-mount-destination-validation.patch +++ /dev/null @@ -1,411 +0,0 @@ -From 9e0940c49a1b65f0fa656905a6b6a14ed2f70dfe Mon Sep 17 00:00:00 2001 -From: Kir Kolyshkin -Date: Mon, 17 May 2021 14:11:35 -0700 -Subject: [PATCH] rootfs: add mount destination validation - -This is a manual backport of the CVE-2021-30465 fix. Original -description follows. - ---- - -Because the target of a mount is inside a container (which may be a -volume that is shared with another container), there exists a race -condition where the target of the mount may change to a path containing -a symlink after we have sanitised the path -- resulting in us -inadvertently mounting the path outside of the container. - -This is not immediately useful because we are in a mount namespace with -MS_SLAVE mount propagation applied to "/", so we cannot mount on top of -host paths in the host namespace. However, if any subsequent mountpoints -in the configuration use a subdirectory of that host path as a source, -those subsequent mounts will use an attacker-controlled source path -(resolved within the host rootfs) -- allowing the bind-mounting of "/" -into the container. - -While arguably configuration issues like this are not entirely within -runc's threat model, within the context of Kubernetes (and possibly -other container managers that provide semi-arbitrary container creation -privileges to untrusted users) this is a legitimate issue. Since we -cannot block mounting from the host into the container, we need to block -the first stage of this attack (mounting onto a path outside the -container). - -The long-term plan to solve this would be to migrate to libpathrs, but -as a stop-gap we implement libpathrs-like path verification through -readlink(/proc/self/fd/$n) and then do mount operations through the -procfd once it's been verified to be inside the container. The target -could move after we've checked it, but if it is inside the container -then we can assume that it is safe for the same reason that libpathrs -operations would be safe. - -A slight wrinkle is the "copyup" functionality we provide for tmpfs, -which is the only case where we want to do a mount on the host -filesystem. To facilitate this, I split out the copy-up functionality -entirely so that the logic isn't interspersed with the regular tmpfs -logic. In addition, all dependencies on m.Destination being overwritten -have been removed since that pattern was just begging to be a source of -more mount-target bugs (we do still have to modify m.Destination for -tmpfs-copyup but we only do it temporarily). - -Fixes: CVE-2021-30465 -Reported-by: Etienne Champetier -Co-authored-by: Noah Meyerhans -Reviewed-by: Samuel Karp -Reviewed-by: Akihiro Suda -Signed-off-by: Aleksa Sarai - -Signed-off-by: Kir Kolyshkin ---- - libcontainer/rootfs_linux.go | 158 ++++++++++++++++++----------------- - libcontainer/utils/utils.go | 56 +++++++++++++ - 2 files changed, 139 insertions(+), 75 deletions(-) - -diff --git a/libcontainer/rootfs_linux.go b/libcontainer/rootfs_linux.go -index 42b66141..56bfb39e 100644 ---- a/runc-66aedde759f33c190954815fb765eedc1d782dd9/libcontainer/rootfs_linux.go -+++ b/runc-66aedde759f33c190954815fb765eedc1d782dd9/libcontainer/rootfs_linux.go -@@ -14,6 +14,7 @@ import ( - "syscall" - "time" - -+ "github.com/Sirupsen/logrus" - securejoin "github.com/cyphar/filepath-securejoin" - "github.com/docker/docker/pkg/mount" - "github.com/mrunalp/fileutils" -@@ -145,8 +146,6 @@ func prepareBindMount(m *configs.Mount, rootfs string) error { - if err := checkProcMount(rootfs, dest, m.Source); err != nil { - return err - } -- // update the mount with the correct dest after symlinks are resolved. -- m.Destination = dest - if err := createIfNotExists(dest, stat.IsDir()); err != nil { - return err - } -@@ -154,12 +153,50 @@ func prepareBindMount(m *configs.Mount, rootfs string) error { - return nil - } - -+func doTmpfsCopyUp(m *configs.Mount, rootfs, mountLabel string) (Err error) { -+ // Set up a scratch dir for the tmpfs on the host. -+ tmpDir, err := ioutil.TempDir("/tmp", "runctmpdir") -+ if err != nil { -+ return newSystemErrorWithCause(err, "tmpcopyup: failed to create tmpdir") -+ } -+ defer os.RemoveAll(tmpDir) -+ -+ // Configure the *host* tmpdir as if it's the container mount. We change -+ // m.Destination since we are going to mount *on the host*. -+ oldDest := m.Destination -+ m.Destination = tmpDir -+ err = mountPropagate(m, "/", mountLabel) -+ m.Destination = oldDest -+ if err != nil { -+ return err -+ } -+ defer func() { -+ if Err != nil { -+ if err := syscall.Unmount(tmpDir, syscall.MNT_DETACH); err != nil { -+ logrus.Warnf("tmpcopyup: failed to unmount tmpdir on error: %v", err) -+ } -+ } -+ }() -+ -+ return libcontainerUtils.WithProcfd(rootfs, m.Destination, func(procfd string) (Err error) { -+ // Copy the container data to the host tmpdir. We append "/" to force -+ // CopyDirectory to resolve the symlink rather than trying to copy the -+ // symlink itself. -+ if err := fileutils.CopyDirectory(procfd+"/", tmpDir); err != nil { -+ return fmt.Errorf("tmpcopyup: failed to copy %s to %s (%s): %v", m.Destination, procfd, tmpDir, err) -+ } -+ // Now move the mount into the container. -+ if err := syscall.Mount(tmpDir, procfd, "", syscall.MS_MOVE, ""); err != nil { -+ return fmt.Errorf("tmpcopyup: failed to move mount %s to %s (%s): %v", tmpDir, procfd, m.Destination, err) -+ } -+ return nil -+ }) -+} -+ - func mountToRootfs(m *configs.Mount, rootfs, mountLabel string) error { -- var ( -- dest = m.Destination -- ) -- if !strings.HasPrefix(dest, rootfs) { -- dest = filepath.Join(rootfs, dest) -+ dest, err := securejoin.SecureJoin(rootfs, m.Destination) -+ if err != nil { -+ return err - } - - switch m.Device { -@@ -182,41 +219,20 @@ func mountToRootfs(m *configs.Mount, rootfs, mountLabel string) error { - } - return nil - case "tmpfs": -- copyUp := m.Extensions&configs.EXT_COPYUP == configs.EXT_COPYUP -- tmpDir := "" - stat, err := os.Stat(dest) - if err != nil { - if err := os.MkdirAll(dest, 0755); err != nil { - return err - } - } -- if copyUp { -- tmpDir, err = ioutil.TempDir("/tmp", "runctmpdir") -- if err != nil { -- return newSystemErrorWithCause(err, "tmpcopyup: failed to create tmpdir") -- } -- defer os.RemoveAll(tmpDir) -- m.Destination = tmpDir -+ if m.Extensions&configs.EXT_COPYUP == configs.EXT_COPYUP { -+ err = doTmpfsCopyUp(m, rootfs, mountLabel) -+ } else { -+ err = mountPropagate(m, rootfs, mountLabel) - } -- if err := mountPropagate(m, rootfs, mountLabel); err != nil { -+ if err != nil { - return err - } -- if copyUp { -- if err := fileutils.CopyDirectory(dest, tmpDir); err != nil { -- errMsg := fmt.Errorf("tmpcopyup: failed to copy %s to %s: %v", dest, tmpDir, err) -- if err1 := syscall.Unmount(tmpDir, syscall.MNT_DETACH); err1 != nil { -- return newSystemErrorWithCausef(err1, "tmpcopyup: %v: failed to unmount", errMsg) -- } -- return errMsg -- } -- if err := syscall.Mount(tmpDir, dest, "", syscall.MS_MOVE, ""); err != nil { -- errMsg := fmt.Errorf("tmpcopyup: failed to move mount %s to %s: %v", tmpDir, dest, err) -- if err1 := syscall.Unmount(tmpDir, syscall.MNT_DETACH); err1 != nil { -- return newSystemErrorWithCausef(err1, "tmpcopyup: %v: failed to unmount", errMsg) -- } -- return errMsg -- } -- } - if stat != nil { - if err = os.Chmod(dest, stat.Mode()); err != nil { - return err -@@ -299,19 +315,9 @@ func mountToRootfs(m *configs.Mount, rootfs, mountLabel string) error { - } - } - default: -- // ensure that the destination of the mount is resolved of symlinks at mount time because -- // any previous mounts can invalidate the next mount's destination. -- // this can happen when a user specifies mounts within other mounts to cause breakouts or other -- // evil stuff to try to escape the container's rootfs. -- var err error -- if dest, err = securejoin.SecureJoin(rootfs, m.Destination); err != nil { -- return err -- } - if err := checkProcMount(rootfs, dest, m.Source); err != nil { - return err - } -- // update the mount with the correct dest after symlinks are resolved. -- m.Destination = dest - if err := os.MkdirAll(dest, 0755); err != nil { - return err - } -@@ -485,7 +491,7 @@ func createDevices(config *configs.Config) error { - return nil - } - --func bindMountDeviceNode(dest string, node *configs.Device) error { -+func bindMountDeviceNode(rootfs, dest string, node *configs.Device) error { - f, err := os.Create(dest) - if err != nil && !os.IsExist(err) { - return err -@@ -493,24 +499,29 @@ func bindMountDeviceNode(dest string, node *configs.Device) error { - if f != nil { - f.Close() - } -- return syscall.Mount(node.Path, dest, "bind", syscall.MS_BIND, "") -+ return libcontainerUtils.WithProcfd(rootfs, dest, func(procfd string) error { -+ return syscall.Mount(node.Path, procfd, "bind", syscall.MS_BIND, "") -+ }) - } - - // Creates the device node in the rootfs of the container. - func createDeviceNode(rootfs string, node *configs.Device, bind bool) error { -- dest := filepath.Join(rootfs, node.Path) -+ dest, err := securejoin.SecureJoin(rootfs, node.Path) -+ if err != nil { -+ return err -+ } - if err := os.MkdirAll(filepath.Dir(dest), 0755); err != nil { - return err - } - - if bind { -- return bindMountDeviceNode(dest, node) -+ return bindMountDeviceNode(rootfs, dest, node) - } - if err := mknodDevice(dest, node); err != nil { - if os.IsExist(err) { - return nil - } else if os.IsPermission(err) { -- return bindMountDeviceNode(dest, node) -+ return bindMountDeviceNode(rootfs, dest, node) - } - return err - } -@@ -681,10 +692,6 @@ func pivotRoot(rootfs string) error { - - // Make oldroot rprivate to make sure our unmounts don't propagate to the - // host (and thus bork the machine). -- if err := syscall.Mount("", ".", "", syscall.MS_PRIVATE|syscall.MS_REC, ""); err != nil { -- return err -- } -- // Preform the unmount. MNT_DETACH allows us to unmount /proc/self/cwd. - if err := syscall.Unmount(".", syscall.MNT_DETACH); err != nil { - return err - } -@@ -807,43 +814,44 @@ func writeSystemProperty(key, value string) error { - } - - func remount(m *configs.Mount, rootfs string) error { -- var ( -- dest = m.Destination -- ) -- if !strings.HasPrefix(dest, rootfs) { -- dest = filepath.Join(rootfs, dest) -- } -- if err := syscall.Mount(m.Source, dest, m.Device, uintptr(m.Flags|syscall.MS_REMOUNT), ""); err != nil { -- return err -- } -- return nil -+ return libcontainerUtils.WithProcfd(rootfs, m.Destination, func(procfd string) error { -+ return syscall.Mount(m.Source, procfd, m.Device, uintptr(m.Flags|syscall.MS_REMOUNT), "") -+ }) - } - - // Do the mount operation followed by additional mounts required to take care --// of propagation flags. -+// of propagation flags. This will always be scoped inside the container rootfs. - func mountPropagate(m *configs.Mount, rootfs string, mountLabel string) error { - var ( -- dest = m.Destination - data = label.FormatMountLabel(m.Data, mountLabel) - flags = m.Flags - ) -- if libcontainerUtils.CleanPath(dest) == "/dev" { -+ if libcontainerUtils.CleanPath(m.Destination) == "/dev" { - flags &= ^syscall.MS_RDONLY - } - -- copyUp := m.Extensions&configs.EXT_COPYUP == configs.EXT_COPYUP -- if !(copyUp || strings.HasPrefix(dest, rootfs)) { -- dest = filepath.Join(rootfs, dest) -+ // Because the destination is inside a container path which might be -+ // mutating underneath us, we verify that we are actually going to mount -+ // inside the container with WithProcfd() -- mounting through a procfd -+ // mounts on the target. -+ if err := libcontainerUtils.WithProcfd(rootfs, m.Destination, func(procfd string) error { -+ return syscall.Mount(m.Source, procfd, m.Device, uintptr(flags), data) -+ }); err != nil { -+ return fmt.Errorf("mount through procfd: %v", err) - } - -- if err := syscall.Mount(m.Source, dest, m.Device, uintptr(flags), data); err != nil { -- return err -- } -- -- for _, pflag := range m.PropagationFlags { -- if err := syscall.Mount("", dest, "", uintptr(pflag), ""); err != nil { -- return err -+ // We have to apply mount propagation flags in a separate WithProcfd() call -+ // because the previous call invalidates the passed procfd -- the mount -+ // target needs to be re-opened. -+ if err := libcontainerUtils.WithProcfd(rootfs, m.Destination, func(procfd string) error { -+ for _, pflag := range m.PropagationFlags { -+ if err := syscall.Mount("", procfd, "", uintptr(pflag), ""); err != nil { -+ return err -+ } - } -+ return nil -+ }); err != nil { -+ return fmt.Errorf("change mount propagation through procfd: %v", err) - } - return nil - } -diff --git a/libcontainer/utils/utils.go b/libcontainer/utils/utils.go -index 2b35b9a7..a89fc6a2 100644 ---- a/runc-66aedde759f33c190954815fb765eedc1d782dd9/libcontainer/utils/utils.go -+++ b/runc-66aedde759f33c190954815fb765eedc1d782dd9/libcontainer/utils/utils.go -@@ -4,12 +4,17 @@ import ( - "crypto/rand" - "encoding/hex" - "encoding/json" -+ "fmt" - "io" - "os" - "path/filepath" -+ "strconv" - "strings" - "syscall" - "unsafe" -+ -+ securejoin "github.com/cyphar/filepath-securejoin" -+ "golang.org/x/sys/unix" - ) - - const ( -@@ -87,6 +92,57 @@ func CleanPath(path string) string { - return filepath.Clean(path) - } - -+// stripRoot returns the passed path, stripping the root path if it was -+// (lexicially) inside it. Note that both passed paths will always be treated -+// as absolute, and the returned path will also always be absolute. In -+// addition, the paths are cleaned before stripping the root. -+func stripRoot(root, path string) string { -+ // Make the paths clean and absolute. -+ root, path = CleanPath("/"+root), CleanPath("/"+path) -+ switch { -+ case path == root: -+ path = "/" -+ case root == "/": -+ // do nothing -+ case strings.HasPrefix(path, root+"/"): -+ path = strings.TrimPrefix(path, root+"/") -+ } -+ return CleanPath("/" + path) -+} -+ -+// WithProcfd runs the passed closure with a procfd path (/proc/self/fd/...) -+// corresponding to the unsafePath resolved within the root. Before passing the -+// fd, this path is verified to have been inside the root -- so operating on it -+// through the passed fdpath should be safe. Do not access this path through -+// the original path strings, and do not attempt to use the pathname outside of -+// the passed closure (the file handle will be freed once the closure returns). -+func WithProcfd(root, unsafePath string, fn func(procfd string) error) error { -+ // Remove the root then forcefully resolve inside the root. -+ unsafePath = stripRoot(root, unsafePath) -+ path, err := securejoin.SecureJoin(root, unsafePath) -+ if err != nil { -+ return fmt.Errorf("resolving path inside rootfs failed: %v", err) -+ } -+ -+ // Open the target path. -+ fh, err := os.OpenFile(path, unix.O_PATH|unix.O_CLOEXEC, 0) -+ if err != nil { -+ return fmt.Errorf("open o_path procfd: %v", err) -+ } -+ defer fh.Close() -+ -+ // Double-check the path is the one we expected. -+ procfd := "/proc/self/fd/" + strconv.Itoa(int(fh.Fd())) -+ if realpath, err := os.Readlink(procfd); err != nil { -+ return fmt.Errorf("procfd verification failed: %v", err) -+ } else if realpath != path { -+ return fmt.Errorf("possibly malicious path detected -- refusing to operate on %s", realpath) -+ } -+ -+ // Run the closure. -+ return fn(procfd) -+} -+ - // SearchLabels searches a list of key-value pairs for the provided key and - // returns the corresponding value. The pairs must be separated with '='. - func SearchLabels(labels []string, query string) string { --- -2.31.1 - diff --git a/SOURCES/1.patch b/SOURCES/1.patch new file mode 100644 index 0000000..c4f8e4c --- /dev/null +++ b/SOURCES/1.patch @@ -0,0 +1,69 @@ +From 40fd67a5303214be8a6aeb30e4f30735dcaf3094 Mon Sep 17 00:00:00 2001 +From: y00316549 +Date: Thu, 11 Jan 2018 20:16:18 +0800 +Subject: [PATCH] Security: fix mem leak in containerd + +Change-Id: I79df63093835a28ff23074ebc0f75fffac592e66 +Signed-off-by: Shukui Yang +(cherry picked from commit 64456eccb7443ab68b1b5cf0c33be51fdfe5e346) +Signed-off-by: Kir Kolyshkin +--- + supervisor/delete.go | 7 +++++-- + supervisor/exit.go | 1 + + supervisor/supervisor.go | 8 ++++++-- + 3 files changed, 12 insertions(+), 4 deletions(-) + +diff --git a/supervisor/delete.go b/supervisor/delete.go +index 26cf1bb..9cf517f 100644 +--- a/containerd-9c53e35c39f214b128beed3dfb670ccf751c4173/supervisor/delete.go ++++ b/containerd-9c53e35c39f214b128beed3dfb670ccf751c4173/supervisor/delete.go +@@ -27,11 +27,14 @@ func (s *Supervisor) delete(t *DeleteTask) error { + t.Process.Wait() + } + if !t.NoEvent { +- execMap := s.getExecSyncMap(t.ID) + go func() { + // Wait for all exec processe events to be sent (we seem + // to sometimes receive them after the init event) +- for _, ch := range execMap { ++ for { ++ ch := s.getExecSyncOneChannel(t.ID) ++ if ch == nil { ++ break ++ } + <-ch + } + s.deleteExecSyncMap(t.ID) +diff --git a/supervisor/exit.go b/supervisor/exit.go +index 2bce31e..537927b 100644 +--- a/containerd-9c53e35c39f214b128beed3dfb670ccf751c4173/supervisor/exit.go ++++ b/containerd-9c53e35c39f214b128beed3dfb670ccf751c4173/supervisor/exit.go +@@ -89,6 +89,7 @@ func (s *Supervisor) execExit(t *ExecExitTask) error { + PID: t.PID, + Status: t.Status, + }) ++ s.deleteExecSyncChannel(t.ID, t.PID) + close(synCh) + }() + return nil +diff --git a/supervisor/supervisor.go b/supervisor/supervisor.go +index e21ae7b..bbb001c 100644 +--- a/containerd-9c53e35c39f214b128beed3dfb670ccf751c4173/supervisor/supervisor.go ++++ b/containerd-9c53e35c39f214b128beed3dfb670ccf751c4173/supervisor/supervisor.go +@@ -479,10 +479,14 @@ func (s *Supervisor) getExecSyncChannel(containerID, pid string) chan struct{} { + return ch + } + +-func (s *Supervisor) getExecSyncMap(containerID string) map[string]chan struct{} { ++func (s *Supervisor) getExecSyncOneChannel(containerID string) chan struct{} { + s.containerExecSyncLock.Lock() + defer s.containerExecSyncLock.Unlock() +- return s.containerExecSync[containerID] ++ ++ for _, ch := range s.containerExecSync[containerID] { ++ return ch ++ } ++ return nil + } + + func (s *Supervisor) deleteExecSyncMap(containerID string) { diff --git a/SOURCES/54.patch b/SOURCES/54.patch new file mode 100644 index 0000000..0af3708 --- /dev/null +++ b/SOURCES/54.patch @@ -0,0 +1,397 @@ +From 2a572d2d825735493a981b03b48877940ea2c17a Mon Sep 17 00:00:00 2001 +From: Kir Kolyshkin +Date: Mon, 17 May 2021 14:11:35 -0700 +Subject: [PATCH] rootfs: add mount destination validation + +This is a manual backport of the CVE-2021-30465 fix. Original +description follows. + +--- + +Because the target of a mount is inside a container (which may be a +volume that is shared with another container), there exists a race +condition where the target of the mount may change to a path containing +a symlink after we have sanitised the path -- resulting in us +inadvertently mounting the path outside of the container. + +This is not immediately useful because we are in a mount namespace with +MS_SLAVE mount propagation applied to "/", so we cannot mount on top of +host paths in the host namespace. However, if any subsequent mountpoints +in the configuration use a subdirectory of that host path as a source, +those subsequent mounts will use an attacker-controlled source path +(resolved within the host rootfs) -- allowing the bind-mounting of "/" +into the container. + +While arguably configuration issues like this are not entirely within +runc's threat model, within the context of Kubernetes (and possibly +other container managers that provide semi-arbitrary container creation +privileges to untrusted users) this is a legitimate issue. Since we +cannot block mounting from the host into the container, we need to block +the first stage of this attack (mounting onto a path outside the +container). + +The long-term plan to solve this would be to migrate to libpathrs, but +as a stop-gap we implement libpathrs-like path verification through +readlink(/proc/self/fd/$n) and then do mount operations through the +procfd once it's been verified to be inside the container. The target +could move after we've checked it, but if it is inside the container +then we can assume that it is safe for the same reason that libpathrs +operations would be safe. + +A slight wrinkle is the "copyup" functionality we provide for tmpfs, +which is the only case where we want to do a mount on the host +filesystem. To facilitate this, I split out the copy-up functionality +entirely so that the logic isn't interspersed with the regular tmpfs +logic. In addition, all dependencies on m.Destination being overwritten +have been removed since that pattern was just begging to be a source of +more mount-target bugs (we do still have to modify m.Destination for +tmpfs-copyup but we only do it temporarily). + +Fixes: CVE-2021-30465 +Reported-by: Etienne Champetier +Co-authored-by: Noah Meyerhans +Reviewed-by: Samuel Karp +Reviewed-by: Akihiro Suda +Signed-off-by: Aleksa Sarai + +Signed-off-by: Kir Kolyshkin +--- + libcontainer/rootfs_linux.go | 154 +++++++++++++++++++---------------- + libcontainer/utils/utils.go | 56 +++++++++++++ + 2 files changed, 139 insertions(+), 71 deletions(-) + +diff --git a/libcontainer/rootfs_linux.go b/libcontainer/rootfs_linux.go +index 42b66141..984fe289 100644 +--- a/runc-66aedde759f33c190954815fb765eedc1d782dd9/libcontainer/rootfs_linux.go ++++ b/runc-66aedde759f33c190954815fb765eedc1d782dd9/libcontainer/rootfs_linux.go +@@ -14,6 +14,7 @@ import ( + "syscall" + "time" + ++ "github.com/Sirupsen/logrus" + securejoin "github.com/cyphar/filepath-securejoin" + "github.com/docker/docker/pkg/mount" + "github.com/mrunalp/fileutils" +@@ -145,8 +146,6 @@ func prepareBindMount(m *configs.Mount, rootfs string) error { + if err := checkProcMount(rootfs, dest, m.Source); err != nil { + return err + } +- // update the mount with the correct dest after symlinks are resolved. +- m.Destination = dest + if err := createIfNotExists(dest, stat.IsDir()); err != nil { + return err + } +@@ -154,12 +153,50 @@ func prepareBindMount(m *configs.Mount, rootfs string) error { + return nil + } + ++func doTmpfsCopyUp(m *configs.Mount, rootfs, mountLabel string) (Err error) { ++ // Set up a scratch dir for the tmpfs on the host. ++ tmpDir, err := ioutil.TempDir("/tmp", "runctmpdir") ++ if err != nil { ++ return newSystemErrorWithCause(err, "tmpcopyup: failed to create tmpdir") ++ } ++ defer os.RemoveAll(tmpDir) ++ ++ // Configure the *host* tmpdir as if it's the container mount. We change ++ // m.Destination since we are going to mount *on the host*. ++ oldDest := m.Destination ++ m.Destination = tmpDir ++ err = mountPropagate(m, "/", mountLabel) ++ m.Destination = oldDest ++ if err != nil { ++ return err ++ } ++ defer func() { ++ if Err != nil { ++ if err := syscall.Unmount(tmpDir, syscall.MNT_DETACH); err != nil { ++ logrus.Warnf("tmpcopyup: failed to unmount tmpdir on error: %v", err) ++ } ++ } ++ }() ++ ++ return libcontainerUtils.WithProcfd(rootfs, m.Destination, func(procfd string) (Err error) { ++ // Copy the container data to the host tmpdir. We append "/" to force ++ // CopyDirectory to resolve the symlink rather than trying to copy the ++ // symlink itself. ++ if err := fileutils.CopyDirectory(procfd+"/", tmpDir); err != nil { ++ return fmt.Errorf("tmpcopyup: failed to copy %s to %s (%s): %v", m.Destination, procfd, tmpDir, err) ++ } ++ // Now move the mount into the container. ++ if err := syscall.Mount(tmpDir, procfd, "", syscall.MS_MOVE, ""); err != nil { ++ return fmt.Errorf("tmpcopyup: failed to move mount %s to %s (%s): %v", tmpDir, procfd, m.Destination, err) ++ } ++ return nil ++ }) ++} ++ + func mountToRootfs(m *configs.Mount, rootfs, mountLabel string) error { +- var ( +- dest = m.Destination +- ) +- if !strings.HasPrefix(dest, rootfs) { +- dest = filepath.Join(rootfs, dest) ++ dest, err := securejoin.SecureJoin(rootfs, m.Destination) ++ if err != nil { ++ return err + } + + switch m.Device { +@@ -182,41 +219,20 @@ func mountToRootfs(m *configs.Mount, rootfs, mountLabel string) error { + } + return nil + case "tmpfs": +- copyUp := m.Extensions&configs.EXT_COPYUP == configs.EXT_COPYUP +- tmpDir := "" + stat, err := os.Stat(dest) + if err != nil { + if err := os.MkdirAll(dest, 0755); err != nil { + return err + } + } +- if copyUp { +- tmpDir, err = ioutil.TempDir("/tmp", "runctmpdir") +- if err != nil { +- return newSystemErrorWithCause(err, "tmpcopyup: failed to create tmpdir") +- } +- defer os.RemoveAll(tmpDir) +- m.Destination = tmpDir ++ if m.Extensions&configs.EXT_COPYUP == configs.EXT_COPYUP { ++ err = doTmpfsCopyUp(m, rootfs, mountLabel) ++ } else { ++ err = mountPropagate(m, rootfs, mountLabel) + } +- if err := mountPropagate(m, rootfs, mountLabel); err != nil { ++ if err != nil { + return err + } +- if copyUp { +- if err := fileutils.CopyDirectory(dest, tmpDir); err != nil { +- errMsg := fmt.Errorf("tmpcopyup: failed to copy %s to %s: %v", dest, tmpDir, err) +- if err1 := syscall.Unmount(tmpDir, syscall.MNT_DETACH); err1 != nil { +- return newSystemErrorWithCausef(err1, "tmpcopyup: %v: failed to unmount", errMsg) +- } +- return errMsg +- } +- if err := syscall.Mount(tmpDir, dest, "", syscall.MS_MOVE, ""); err != nil { +- errMsg := fmt.Errorf("tmpcopyup: failed to move mount %s to %s: %v", tmpDir, dest, err) +- if err1 := syscall.Unmount(tmpDir, syscall.MNT_DETACH); err1 != nil { +- return newSystemErrorWithCausef(err1, "tmpcopyup: %v: failed to unmount", errMsg) +- } +- return errMsg +- } +- } + if stat != nil { + if err = os.Chmod(dest, stat.Mode()); err != nil { + return err +@@ -299,19 +315,9 @@ func mountToRootfs(m *configs.Mount, rootfs, mountLabel string) error { + } + } + default: +- // ensure that the destination of the mount is resolved of symlinks at mount time because +- // any previous mounts can invalidate the next mount's destination. +- // this can happen when a user specifies mounts within other mounts to cause breakouts or other +- // evil stuff to try to escape the container's rootfs. +- var err error +- if dest, err = securejoin.SecureJoin(rootfs, m.Destination); err != nil { +- return err +- } + if err := checkProcMount(rootfs, dest, m.Source); err != nil { + return err + } +- // update the mount with the correct dest after symlinks are resolved. +- m.Destination = dest + if err := os.MkdirAll(dest, 0755); err != nil { + return err + } +@@ -485,7 +491,7 @@ func createDevices(config *configs.Config) error { + return nil + } + +-func bindMountDeviceNode(dest string, node *configs.Device) error { ++func bindMountDeviceNode(rootfs, dest string, node *configs.Device) error { + f, err := os.Create(dest) + if err != nil && !os.IsExist(err) { + return err +@@ -493,24 +499,29 @@ func bindMountDeviceNode(dest string, node *configs.Device) error { + if f != nil { + f.Close() + } +- return syscall.Mount(node.Path, dest, "bind", syscall.MS_BIND, "") ++ return libcontainerUtils.WithProcfd(rootfs, dest, func(procfd string) error { ++ return syscall.Mount(node.Path, procfd, "bind", syscall.MS_BIND, "") ++ }) + } + + // Creates the device node in the rootfs of the container. + func createDeviceNode(rootfs string, node *configs.Device, bind bool) error { +- dest := filepath.Join(rootfs, node.Path) ++ dest, err := securejoin.SecureJoin(rootfs, node.Path) ++ if err != nil { ++ return err ++ } + if err := os.MkdirAll(filepath.Dir(dest), 0755); err != nil { + return err + } + + if bind { +- return bindMountDeviceNode(dest, node) ++ return bindMountDeviceNode(rootfs, dest, node) + } + if err := mknodDevice(dest, node); err != nil { + if os.IsExist(err) { + return nil + } else if os.IsPermission(err) { +- return bindMountDeviceNode(dest, node) ++ return bindMountDeviceNode(rootfs, dest, node) + } + return err + } +@@ -807,43 +818,44 @@ func writeSystemProperty(key, value string) error { + } + + func remount(m *configs.Mount, rootfs string) error { +- var ( +- dest = m.Destination +- ) +- if !strings.HasPrefix(dest, rootfs) { +- dest = filepath.Join(rootfs, dest) +- } +- if err := syscall.Mount(m.Source, dest, m.Device, uintptr(m.Flags|syscall.MS_REMOUNT), ""); err != nil { +- return err +- } +- return nil ++ return libcontainerUtils.WithProcfd(rootfs, m.Destination, func(procfd string) error { ++ return syscall.Mount(m.Source, procfd, m.Device, uintptr(m.Flags|syscall.MS_REMOUNT), "") ++ }) + } + + // Do the mount operation followed by additional mounts required to take care +-// of propagation flags. ++// of propagation flags. This will always be scoped inside the container rootfs. + func mountPropagate(m *configs.Mount, rootfs string, mountLabel string) error { + var ( +- dest = m.Destination + data = label.FormatMountLabel(m.Data, mountLabel) + flags = m.Flags + ) +- if libcontainerUtils.CleanPath(dest) == "/dev" { ++ if libcontainerUtils.CleanPath(m.Destination) == "/dev" { + flags &= ^syscall.MS_RDONLY + } + +- copyUp := m.Extensions&configs.EXT_COPYUP == configs.EXT_COPYUP +- if !(copyUp || strings.HasPrefix(dest, rootfs)) { +- dest = filepath.Join(rootfs, dest) ++ // Because the destination is inside a container path which might be ++ // mutating underneath us, we verify that we are actually going to mount ++ // inside the container with WithProcfd() -- mounting through a procfd ++ // mounts on the target. ++ if err := libcontainerUtils.WithProcfd(rootfs, m.Destination, func(procfd string) error { ++ return syscall.Mount(m.Source, procfd, m.Device, uintptr(flags), data) ++ }); err != nil { ++ return fmt.Errorf("mount through procfd: %v", err) + } + +- if err := syscall.Mount(m.Source, dest, m.Device, uintptr(flags), data); err != nil { +- return err +- } +- +- for _, pflag := range m.PropagationFlags { +- if err := syscall.Mount("", dest, "", uintptr(pflag), ""); err != nil { +- return err ++ // We have to apply mount propagation flags in a separate WithProcfd() call ++ // because the previous call invalidates the passed procfd -- the mount ++ // target needs to be re-opened. ++ if err := libcontainerUtils.WithProcfd(rootfs, m.Destination, func(procfd string) error { ++ for _, pflag := range m.PropagationFlags { ++ if err := syscall.Mount("", procfd, "", uintptr(pflag), ""); err != nil { ++ return err ++ } + } ++ return nil ++ }); err != nil { ++ return fmt.Errorf("change mount propagation through procfd: %v", err) + } + return nil + } +diff --git a/libcontainer/utils/utils.go b/libcontainer/utils/utils.go +index 2b35b9a7..a89fc6a2 100644 +--- a/runc-66aedde759f33c190954815fb765eedc1d782dd9/libcontainer/utils/utils.go ++++ b/runc-66aedde759f33c190954815fb765eedc1d782dd9/libcontainer/utils/utils.go +@@ -4,12 +4,17 @@ import ( + "crypto/rand" + "encoding/hex" + "encoding/json" ++ "fmt" + "io" + "os" + "path/filepath" ++ "strconv" + "strings" + "syscall" + "unsafe" ++ ++ securejoin "github.com/cyphar/filepath-securejoin" ++ "golang.org/x/sys/unix" + ) + + const ( +@@ -87,6 +92,57 @@ func CleanPath(path string) string { + return filepath.Clean(path) + } + ++// stripRoot returns the passed path, stripping the root path if it was ++// (lexicially) inside it. Note that both passed paths will always be treated ++// as absolute, and the returned path will also always be absolute. In ++// addition, the paths are cleaned before stripping the root. ++func stripRoot(root, path string) string { ++ // Make the paths clean and absolute. ++ root, path = CleanPath("/"+root), CleanPath("/"+path) ++ switch { ++ case path == root: ++ path = "/" ++ case root == "/": ++ // do nothing ++ case strings.HasPrefix(path, root+"/"): ++ path = strings.TrimPrefix(path, root+"/") ++ } ++ return CleanPath("/" + path) ++} ++ ++// WithProcfd runs the passed closure with a procfd path (/proc/self/fd/...) ++// corresponding to the unsafePath resolved within the root. Before passing the ++// fd, this path is verified to have been inside the root -- so operating on it ++// through the passed fdpath should be safe. Do not access this path through ++// the original path strings, and do not attempt to use the pathname outside of ++// the passed closure (the file handle will be freed once the closure returns). ++func WithProcfd(root, unsafePath string, fn func(procfd string) error) error { ++ // Remove the root then forcefully resolve inside the root. ++ unsafePath = stripRoot(root, unsafePath) ++ path, err := securejoin.SecureJoin(root, unsafePath) ++ if err != nil { ++ return fmt.Errorf("resolving path inside rootfs failed: %v", err) ++ } ++ ++ // Open the target path. ++ fh, err := os.OpenFile(path, unix.O_PATH|unix.O_CLOEXEC, 0) ++ if err != nil { ++ return fmt.Errorf("open o_path procfd: %v", err) ++ } ++ defer fh.Close() ++ ++ // Double-check the path is the one we expected. ++ procfd := "/proc/self/fd/" + strconv.Itoa(int(fh.Fd())) ++ if realpath, err := os.Readlink(procfd); err != nil { ++ return fmt.Errorf("procfd verification failed: %v", err) ++ } else if realpath != path { ++ return fmt.Errorf("possibly malicious path detected -- refusing to operate on %s", realpath) ++ } ++ ++ // Run the closure. ++ return fn(procfd) ++} ++ + // SearchLabels searches a list of key-value pairs for the provided key and + // returns the corresponding value. The pairs must be separated with '='. + func SearchLabels(labels []string, query string) string { diff --git a/SOURCES/docker.service b/SOURCES/docker.service index e67f0d2..d6c28fb 100644 --- a/SOURCES/docker.service +++ b/SOURCES/docker.service @@ -1,8 +1,9 @@ [Unit] Description=Docker Application Container Engine Documentation=http://docs.docker.com -After=network.target +After=network.target rhel-push-plugin.service registries.service Wants=docker-storage-setup.service +Requires=rhel-push-plugin.service registries.service Requires=docker-cleanup.timer [Service] @@ -18,6 +19,7 @@ Environment=PATH=/usr/libexec/docker:/usr/bin:/usr/sbin ExecStart=/usr/bin/dockerd-current \ --add-runtime docker-runc=/usr/libexec/docker/docker-runc-current \ --default-runtime=docker-runc \ + --authorization-plugin=rhel-push-plugin \ --exec-opt native.cgroupdriver=systemd \ --userland-proxy-path=/usr/libexec/docker/docker-proxy-current \ --init-path=/usr/libexec/docker/docker-init-current \ diff --git a/SPECS/docker.spec b/SPECS/docker.spec index 42b5351..3d17c74 100644 --- a/SPECS/docker.spec +++ b/SPECS/docker.spec @@ -45,9 +45,9 @@ %global shortcommit_novolume %(c=%{commit_novolume}; echo ${c:0:7}) # rhel-push-plugin -#%global git_rhel_push https://github.com/projectatomic/rhel-push-plugin -#%global commit_rhel_push af9107b2aedb235338e32a3c19507cad3f218b0d -#%global shortcommit_rhel_push %(c=%{commit_rhel_push}; echo ${c:0:7}) +%global git_rhel_push https://github.com/projectatomic/rhel-push-plugin +%global commit_rhel_push af9107b2aedb235338e32a3c19507cad3f218b0d +%global shortcommit_rhel_push %(c=%{commit_rhel_push}; echo ${c:0:7}) # docker-lvm-plugin %global git_lvm https://github.com/projectatomic/%{repo}-lvm-plugin @@ -77,15 +77,15 @@ Name: %{repo} Epoch: 2 Version: 1.13.1 -Release: 206.git%{shortcommit_docker}%{?dist} +Release: 208.git%{shortcommit_docker}%{?dist} Summary: Automates deployment of containerized applications License: ASL 2.0 URL: https://%{import_path} -ExclusiveArch: aarch64 %{arm} ppc64le s390x x86_64 %{ix86} +ExclusiveArch: aarch64 %{arm} ppc64le s390x x86_64 Source0: %{git_docker}/archive/%{commit_docker}.tar.gz Source2: %{git_dss}/archive/%{commit_dss}/container-storage-setup-%{shortcommit_dss}.tar.gz Source4: %{git_novolume}/archive/%{commit_novolume}/%{repo}-novolume-plugin-%{shortcommit_novolume}.tar.gz -#Source5: %{git_rhel_push}/archive/%{commit_rhel_push}/rhel-push-plugin-%{shortcommit_rhel_push}.tar.gz +Source5: %{git_rhel_push}/archive/%{commit_rhel_push}/rhel-push-plugin-%{shortcommit_rhel_push}.tar.gz Source6: %{git_lvm}/archive/%{commit_lvm}/%{repo}-lvm-plugin-%{shortcommit_lvm}.tar.gz Source8: %{name}.service Source9: %{name}.sysconfig @@ -127,12 +127,13 @@ Patch7: docker-CVE-2020-8945.patch # patch: https://github.com/projectatomic/runc/pull/33.patch Patch8: docker-1879425.patch Patch9: docker-1787148.patch -Patch10: 0001-rootfs-add-mount-destination-validation.patch +Patch10: https://patch-diff.githubusercontent.com/raw/projectatomic/runc/pull/54.patch +Patch11: https://patch-diff.githubusercontent.com/raw/kolyshkin/containerd-1/pull/1.patch BuildRequires: cmake BuildRequires: sed BuildRequires: git BuildRequires: glibc-static -%if 0%{?fedora} +%if 0%{?fedora} || 0%{?centos} BuildRequires: %{?go_compiler:compiler(go-compiler)}%{!?go_compiler:golang} %else BuildRequires: go-toolset-1.10 @@ -206,7 +207,7 @@ Requires: device-mapper-libs >= 7:1.02.97 Requires: oci-umount >= 2:2.3.3-3 Requires: oci-register-machine >= 1:0-5.13 Requires: oci-systemd-hook >= 1:0.1.4-9 -#Requires: %{name}-rhel-push-plugin = %{epoch}:%{version}-%{release} +Requires: %{name}-rhel-push-plugin = %{epoch}:%{version}-%{release} Requires: xz Requires: atomic-registries Requires: container-selinux >= 2:2.51-1 @@ -258,16 +259,16 @@ local volumes defined. In particular, the plugin will block `docker run` with: The only thing allowed will be just bind mounts. -#%package rhel-push-plugin -#License: GPLv2 -#Summary: Avoids pushing a RHEL-based image to docker.io registry +%package rhel-push-plugin +License: GPLv2 +Summary: Avoids pushing a RHEL-based image to docker.io registry -#%description rhel-push-plugin -#In order to use this plugin you must be running at least Docker 1.10 which -#has support for authorization plugins. +%description rhel-push-plugin +In order to use this plugin you must be running at least Docker 1.10 which +has support for authorization plugins. -#This plugin avoids any RHEL based image to be pushed to the default docker.io -#registry preventing users to violate the RH subscription agreement. +This plugin avoids any RHEL based image to be pushed to the default docker.io +registry preventing users to violate the RH subscription agreement. %package lvm-plugin License: LGPLv3 @@ -292,7 +293,7 @@ tar zxf %{SOURCE2} tar zxf %{SOURCE4} # untar rhel-push-plugin -#tar zxf %{SOURCE5} +tar zxf %{SOURCE5} # untar lvm-plugin tar zxf %{SOURCE6} @@ -346,6 +347,7 @@ cd - %patch8 -p1 %patch9 -p1 %patch10 -p1 +%patch11 -p1 %build # compile docker-proxy first - otherwise deps in gopath conflict with the others below and this fails. Remove libnetwork libs then. @@ -367,7 +369,7 @@ pushd _build mkdir -p src/%{provider}.%{provider_tld}/{%{name},projectatomic} ln -s $(dirs +1 -l) src/%{import_path} ln -s $(dirs +1 -l)/%{repo}-novolume-plugin-%{commit_novolume} src/%{provider}.%{provider_tld}/projectatomic/%{repo}-novolume-plugin -# ln -s $(dirs +1 -l)/rhel-push-plugin-%{commit_rhel_push} src/%{provider}.%{provider_tld}/projectatomic/rhel-push-plugin + ln -s $(dirs +1 -l)/rhel-push-plugin-%{commit_rhel_push} src/%{provider}.%{provider_tld}/projectatomic/rhel-push-plugin ln -s $(dirs +1 -l)/%{repo}-lvm-plugin-%{commit_lvm} src/%{provider}.%{provider_tld}/projectatomic/%{repo}-lvm-plugin popd @@ -376,10 +378,10 @@ pushd $(pwd)/_build/src %gobuild %{provider}.%{provider_tld}/projectatomic/%{repo}-novolume-plugin popd -#export GOPATH=$(pwd)/rhel-push-plugin-%{commit_rhel_push}/Godeps/_workspace:$(pwd)/_build -#pushd $(pwd)/_build/src -#%gobuild %{provider}.%{provider_tld}/projectatomic/rhel-push-plugin -#popd +export GOPATH=$(pwd)/rhel-push-plugin-%{commit_rhel_push}/Godeps/_workspace:$(pwd)/_build +pushd $(pwd)/_build/src +%gobuild %{provider}.%{provider_tld}/projectatomic/rhel-push-plugin +popd export GOPATH=$(pwd)/%{repo}-lvm-plugin-%{commit_lvm}/Godeps/_workspace:$(pwd)/_build pushd $(pwd)/_build/src @@ -402,7 +404,7 @@ export GOPATH=$(pwd)/_build:$(pwd)/vendor # build %%{name} manpages man/md2man-all.sh go-md2man -in %{repo}-novolume-plugin-%{commit_novolume}/man/%{repo}-novolume-plugin.8.md -out %{repo}-novolume-plugin.8 -#go-md2man -in rhel-push-plugin-%{commit_rhel_push}/man/rhel-push-plugin.8.md -out rhel-push-plugin.8 +go-md2man -in rhel-push-plugin-%{commit_rhel_push}/man/rhel-push-plugin.8.md -out rhel-push-plugin.8 go-md2man -in %{repo}-lvm-plugin-%{commit_lvm}/man/%{repo}-lvm-plugin.8.md -out %{repo}-lvm-plugin.8 # build %%{name} binary @@ -567,12 +569,12 @@ install -d %{buildroot}%{_mandir}/man8 install -p -m 644 %{repo}-novolume-plugin.8 %{buildroot}%{_mandir}/man8 # install rhel-push-plugin executable, unitfile, socket and man -#install -d %{buildroot}%{_libexecdir}/%{repo} -#install -p -m 755 _build/src/rhel-push-plugin %{buildroot}%{_libexecdir}/%{repo}/rhel-push-plugin -#install -p -m 644 rhel-push-plugin-%{commit_rhel_push}/systemd/rhel-push-plugin.service %{buildroot}%{_unitdir}/rhel-push-plugin.service -#install -p -m 644 rhel-push-plugin-%{commit_rhel_push}/systemd/rhel-push-plugin.socket %{buildroot}%{_unitdir}/rhel-push-plugin.socket -#install -d %{buildroot}%{_mandir}/man8 -#install -p -m 644 rhel-push-plugin.8 %{buildroot}%{_mandir}/man8 +install -d %{buildroot}%{_libexecdir}/%{repo} +install -p -m 755 _build/src/rhel-push-plugin %{buildroot}%{_libexecdir}/%{repo}/rhel-push-plugin +install -p -m 644 rhel-push-plugin-%{commit_rhel_push}/systemd/rhel-push-plugin.service %{buildroot}%{_unitdir}/rhel-push-plugin.service +install -p -m 644 rhel-push-plugin-%{commit_rhel_push}/systemd/rhel-push-plugin.socket %{buildroot}%{_unitdir}/rhel-push-plugin.socket +install -d %{buildroot}%{_mandir}/man8 +install -p -m 644 rhel-push-plugin.8 %{buildroot}%{_mandir}/man8 # install %%{repo}-lvm-plugin executable, unitfile, socket and man install -d %{buildroot}/%{_libexecdir}/%{repo} @@ -652,14 +654,14 @@ exit 0 %postun novolume-plugin %systemd_postun_with_restart %{name}-novolume-plugin.service -#%post rhel-push-plugin -#%systemd_post rhel-push-plugin.service +%post rhel-push-plugin +%systemd_post rhel-push-plugin.service -#%preun rhel-push-plugin -#%systemd_preun rhel-push-plugin.service +%preun rhel-push-plugin +%systemd_preun rhel-push-plugin.service -#%postun rhel-push-plugin -#%systemd_postun_with_restart rhel-push-plugin.service +%postun rhel-push-plugin +%systemd_postun_with_restart rhel-push-plugin.service %posttrans # Install a default docker-storage-setup based on kernel version. @@ -755,12 +757,12 @@ fi %{_libexecdir}/%{repo}/%{repo}-novolume-plugin %{_unitdir}/%{repo}-novolume-plugin.* -#%files rhel-push-plugin -#%license rhel-push-plugin-%{commit_rhel_push}/LICENSE -#%doc rhel-push-plugin-%{commit_rhel_push}/README.md -#%{_mandir}/man8/rhel-push-plugin.8.gz -#%{_libexecdir}/%{repo}/rhel-push-plugin -#%{_unitdir}/rhel-push-plugin.* +%files rhel-push-plugin +%license rhel-push-plugin-%{commit_rhel_push}/LICENSE +%doc rhel-push-plugin-%{commit_rhel_push}/README.md +%{_mandir}/man8/rhel-push-plugin.8.gz +%{_libexecdir}/%{repo}/rhel-push-plugin +%{_unitdir}/rhel-push-plugin.* %files lvm-plugin %license %{repo}-lvm-plugin-%{commit_lvm}/LICENSE @@ -776,6 +778,14 @@ fi %{_bindir}/%{name}-v1.10-migrator-* %changelog +* Fri Jun 04 2021 Jindrich Novy - 2:1.13.1-208.git7d71120 +- propagate mounts to the host - fix regression introduced by fix of CVE-2021-30465 +- Resolves: #1966968 + +* Wed May 26 2021 Jindrich Novy - 2:1.13.1-207.git7d71120 +- apply memory leak fix from Kir Kolyshkin +- Resolves: #1951896 + * Tue May 18 2021 Jindrich Novy - 2:1.13.1-206.git7d71120 - fix CVE-2021-30465 - Related: #1959475