[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Bug#930293: unblock: docker.io/18.09.1+dfsg1-7



Hi,

On Tue, Jun 18, 2019 at 10:18:47PM +0200, Paul Gevers wrote:
> I don't like to rush you, but be aware that the time slot to fix this is
> closing. The package needs to be ready to migrate at 2019-06-25 13:00
> UTC [1]. If the package isn't ready, we'll remove it from buster (fixing
> some headaches for the security team, but a shame nevertheless).
> 

Hope it's still in time...

Please unblock docker.io/18.09.1+dfsg1-7.1

The debdiff is:

diff -Nru docker.io-18.09.1+dfsg1/debian/changelog docker.io-18.09.1+dfsg1/debian/changelog
--- docker.io-18.09.1+dfsg1/debian/changelog	2019-05-13 10:34:45.000000000 +0800
+++ docker.io-18.09.1+dfsg1/debian/changelog	2019-06-23 01:25:10.000000000 +0800
@@ -1,3 +1,15 @@
+docker.io (18.09.1+dfsg1-7.1) unstable; urgency=medium
+
+  * Non-maintainer upload.
+
+  [ Hideki Yamane ]
+  * upstream site moved to mobyproject.org
+
+  [ Arnaud Rebillout ]
+  * Add patch for CVE-2018-15664 (Closes: #929662).
+
+ -- Shengjing Zhu <zhsj@debian.org>  Sun, 23 Jun 2019 01:25:10 +0800
+
 docker.io (18.09.1+dfsg1-7) unstable; urgency=medium
 
   * Add patch to revert using iptables-legacy (Closes: #921600).
diff -Nru docker.io-18.09.1+dfsg1/debian/control docker.io-18.09.1+dfsg1/debian/control
--- docker.io-18.09.1+dfsg1/debian/control	2019-05-13 10:34:45.000000000 +0800
+++ docker.io-18.09.1+dfsg1/debian/control	2019-06-23 01:25:10.000000000 +0800
@@ -139,7 +139,7 @@
     ,pkg-config
     ,procps
     ,tzdata
-Homepage: https://dockerproject.org
+Homepage: https://mobyproject.org
 Vcs-Browser: https://salsa.debian.org/docker-team/docker
 Vcs-Git: https://salsa.debian.org/docker-team/docker.git
 XS-Go-Import-Path: github.com/docker/docker
diff -Nru docker.io-18.09.1+dfsg1/debian/patches/cve-2018-15664-01-pass-root-to-chroot-to-for-chroot-untar.patch docker.io-18.09.1+dfsg1/debian/patches/cve-2018-15664-01-pass-root-to-chroot-to-for-chroot-untar.patch
--- docker.io-18.09.1+dfsg1/debian/patches/cve-2018-15664-01-pass-root-to-chroot-to-for-chroot-untar.patch	1970-01-01 08:00:00.000000000 +0800
+++ docker.io-18.09.1+dfsg1/debian/patches/cve-2018-15664-01-pass-root-to-chroot-to-for-chroot-untar.patch	2019-06-23 01:25:10.000000000 +0800
@@ -0,0 +1,186 @@
+From: Brian Goff <cpuguy83@gmail.com>
+Date: Thu, 30 May 2019 11:15:09 -0700
+Subject: [PATCH] Pass root to chroot to for chroot Untar
+
+This is useful for preventing CVE-2018-15664 where a malicious container
+process can take advantage of a race on symlink resolution/sanitization.
+
+Before this change chrootarchive would chroot to the destination
+directory which is attacker controlled. With this patch we always chroot
+to the container's root which is not attacker controlled.
+
+Signed-off-by: Brian Goff <cpuguy83@gmail.com>
+Origin: upstream, https://github.com/moby/moby/pull/39292
+---
+ daemon/archive.go                      |  7 ++-
+ pkg/chrootarchive/archive.go           | 24 ++++++--
+ pkg/chrootarchive/archive_unix.go      | 34 ++++++++++--
+ pkg/chrootarchive/archive_windows.go   |  2 +-
+ 5 files changed, 55 insertions(+), 12 deletions(-)
+
+diff --git a/engine/daemon/archive.go b/engine/daemon/archive.go
+index 9c7971b56ea3..9f56ca750392 100644
+--- a/engine/daemon/archive.go
++++ b/engine/daemon/archive.go
+@@ -31,11 +31,12 @@ type archiver interface {
+ }
+ 
+ // helper functions to extract or archive
+-func extractArchive(i interface{}, src io.Reader, dst string, opts *archive.TarOptions) error {
++func extractArchive(i interface{}, src io.Reader, dst string, opts *archive.TarOptions, root string) error {
+ 	if ea, ok := i.(extractor); ok {
+ 		return ea.ExtractArchive(src, dst, opts)
+ 	}
+-	return chrootarchive.Untar(src, dst, opts)
++
++	return chrootarchive.UntarWithRoot(src, dst, opts, root)
+ }
+ 
+ func archivePath(i interface{}, src string, opts *archive.TarOptions) (io.ReadCloser, error) {
+@@ -367,7 +368,7 @@ func (daemon *Daemon) containerExtractToDir(container *container.Container, path
+ 		}
+ 	}
+ 
+-	if err := extractArchive(driver, content, resolvedPath, options); err != nil {
++	if err := extractArchive(driver, content, resolvedPath, options, container.BaseFS.Path()); err != nil {
+ 		return err
+ 	}
+ 
+diff --git a/engine/pkg/chrootarchive/archive.go b/engine/pkg/chrootarchive/archive.go
+index 2d9d662830b7..7ebca3774c3d 100644
+--- a/engine/pkg/chrootarchive/archive.go
++++ b/engine/pkg/chrootarchive/archive.go
+@@ -27,18 +27,34 @@ func NewArchiver(idMapping *idtools.IdentityMapping) *archive.Archiver {
+ // The archive may be compressed with one of the following algorithms:
+ //  identity (uncompressed), gzip, bzip2, xz.
+ func Untar(tarArchive io.Reader, dest string, options *archive.TarOptions) error {
+-	return untarHandler(tarArchive, dest, options, true)
++	return untarHandler(tarArchive, dest, options, true, dest)
++}
++
++// UntarWithRoot is the same as `Untar`, but allows you to pass in a root directory
++// The root directory is the directory that will be chrooted to.
++// `dest` must be a path within `root`, if it is not an error will be returned.
++//
++// `root` should set to a directory which is not controlled by any potentially
++// malicious process.
++//
++// This should be used to prevent a potential attacker from manipulating `dest`
++// such that it would provide access to files outside of `dest` through things
++// like symlinks. Normally `ResolveSymlinksInScope` would handle this, however
++// sanitizing symlinks in this manner is inherrently racey:
++// ref: CVE-2018-15664
++func UntarWithRoot(tarArchive io.Reader, dest string, options *archive.TarOptions, root string) error {
++	return untarHandler(tarArchive, dest, options, true, root)
+ }
+ 
+ // UntarUncompressed reads a stream of bytes from `archive`, parses it as a tar archive,
+ // and unpacks it into the directory at `dest`.
+ // The archive must be an uncompressed stream.
+ func UntarUncompressed(tarArchive io.Reader, dest string, options *archive.TarOptions) error {
+-	return untarHandler(tarArchive, dest, options, false)
++	return untarHandler(tarArchive, dest, options, false, dest)
+ }
+ 
+ // Handler for teasing out the automatic decompression
+-func untarHandler(tarArchive io.Reader, dest string, options *archive.TarOptions, decompress bool) error {
++func untarHandler(tarArchive io.Reader, dest string, options *archive.TarOptions, decompress bool, root string) error {
+ 	if tarArchive == nil {
+ 		return fmt.Errorf("Empty archive")
+ 	}
+@@ -69,5 +85,5 @@ func untarHandler(tarArchive io.Reader, dest string, options *archive.TarOptions
+ 		r = decompressedArchive
+ 	}
+ 
+-	return invokeUnpack(r, dest, options)
++	return invokeUnpack(r, dest, options, root)
+ }
+diff --git a/engine/pkg/chrootarchive/archive_unix.go b/engine/pkg/chrootarchive/archive_unix.go
+index 5df8afd66205..96f07c4bb4d6 100644
+--- a/engine/pkg/chrootarchive/archive_unix.go
++++ b/engine/pkg/chrootarchive/archive_unix.go
+@@ -10,6 +10,7 @@ import (
+ 	"io"
+ 	"io/ioutil"
+ 	"os"
++	"path/filepath"
+ 	"runtime"
+ 
+ 	"github.com/docker/docker/pkg/archive"
+@@ -30,11 +31,21 @@ func untar() {
+ 		fatal(err)
+ 	}
+ 
+-	if err := chroot(flag.Arg(0)); err != nil {
++	dst := flag.Arg(0)
++	var root string
++	if len(flag.Args()) > 1 {
++		root = flag.Arg(1)
++	}
++
++	if root == "" {
++		root = dst
++	}
++
++	if err := chroot(root); err != nil {
+ 		fatal(err)
+ 	}
+ 
+-	if err := archive.Unpack(os.Stdin, "/", options); err != nil {
++	if err := archive.Unpack(os.Stdin, dst, options); err != nil {
+ 		fatal(err)
+ 	}
+ 	// fully consume stdin in case it is zero padded
+@@ -45,7 +56,7 @@ func untar() {
+ 	os.Exit(0)
+ }
+ 
+-func invokeUnpack(decompressedArchive io.Reader, dest string, options *archive.TarOptions) error {
++func invokeUnpack(decompressedArchive io.Reader, dest string, options *archive.TarOptions, root string) error {
+ 
+ 	// We can't pass a potentially large exclude list directly via cmd line
+ 	// because we easily overrun the kernel's max argument/environment size
+@@ -57,7 +68,21 @@ func invokeUnpack(decompressedArchive io.Reader, dest string, options *archive.T
+ 		return fmt.Errorf("Untar pipe failure: %v", err)
+ 	}
+ 
+-	cmd := reexec.Command("docker-untar", dest)
++	if root != "" {
++		relDest, err := filepath.Rel(root, dest)
++		if err != nil {
++			return err
++		}
++		if relDest == "." {
++			relDest = "/"
++		}
++		if relDest[0] != '/' {
++			relDest = "/" + relDest
++		}
++		dest = relDest
++	}
++
++	cmd := reexec.Command("docker-untar", dest, root)
+ 	cmd.Stdin = decompressedArchive
+ 
+ 	cmd.ExtraFiles = append(cmd.ExtraFiles, r)
+@@ -69,6 +94,7 @@ func invokeUnpack(decompressedArchive io.Reader, dest string, options *archive.T
+ 		w.Close()
+ 		return fmt.Errorf("Untar error on re-exec cmd: %v", err)
+ 	}
++
+ 	//write the options to the pipe for the untar exec to read
+ 	if err := json.NewEncoder(w).Encode(options); err != nil {
+ 		w.Close()
+diff --git a/engine/pkg/chrootarchive/archive_windows.go b/engine/pkg/chrootarchive/archive_windows.go
+index f2973132a391..bd5712c5c04c 100644
+--- a/engine/pkg/chrootarchive/archive_windows.go
++++ b/engine/pkg/chrootarchive/archive_windows.go
+@@ -14,7 +14,7 @@ func chroot(path string) error {
+ 
+ func invokeUnpack(decompressedArchive io.ReadCloser,
+ 	dest string,
+-	options *archive.TarOptions) error {
++	options *archive.TarOptions, root string) error {
+ 	// Windows is different to Linux here because Windows does not support
+ 	// chroot. Hence there is no point sandboxing a chrooted process to
+ 	// do the unpack. We call inline instead within the daemon process.
diff -Nru docker.io-18.09.1+dfsg1/debian/patches/cve-2018-15664-02-add-chroot-for-tar-packing-operations.patch docker.io-18.09.1+dfsg1/debian/patches/cve-2018-15664-02-add-chroot-for-tar-packing-operations.patch
--- docker.io-18.09.1+dfsg1/debian/patches/cve-2018-15664-02-add-chroot-for-tar-packing-operations.patch	1970-01-01 08:00:00.000000000 +0800
+++ docker.io-18.09.1+dfsg1/debian/patches/cve-2018-15664-02-add-chroot-for-tar-packing-operations.patch	2019-06-23 01:25:10.000000000 +0800
@@ -0,0 +1,248 @@
+From: Brian Goff <cpuguy83@gmail.com>
+Date: Thu, 30 May 2019 14:55:52 -0700
+Subject: [PATCH] Add chroot for tar packing operations
+
+Previously only unpack operations were supported with chroot.
+This adds chroot support for packing operations.
+This prevents potential breakouts when copying data from a container.
+
+Signed-off-by: Brian Goff <cpuguy83@gmail.com>
+Origin: upstream, https://github.com/moby/moby/pull/39292
+---
+ daemon/archive.go                      |  8 +--
+ daemon/export.go                       |  2 +-
+ pkg/chrootarchive/archive.go           |  8 +++
+ pkg/chrootarchive/archive_unix.go      | 98 +++++++++++++++++++++++++-
+ pkg/chrootarchive/archive_windows.go   |  7 ++
+ pkg/chrootarchive/init_unix.go         |  1 +
+ 6 files changed, 117 insertions(+), 7 deletions(-)
+
+diff --git a/engine/daemon/archive.go b/engine/daemon/archive.go
+index 9f56ca750392..109376b4b566 100644
+--- a/engine/daemon/archive.go
++++ b/engine/daemon/archive.go
+@@ -39,11 +39,11 @@ func extractArchive(i interface{}, src io.Reader, dst string, opts *archive.TarO
+ 	return chrootarchive.UntarWithRoot(src, dst, opts, root)
+ }
+ 
+-func archivePath(i interface{}, src string, opts *archive.TarOptions) (io.ReadCloser, error) {
++func archivePath(i interface{}, src string, opts *archive.TarOptions, root string) (io.ReadCloser, error) {
+ 	if ap, ok := i.(archiver); ok {
+ 		return ap.ArchivePath(src, opts)
+ 	}
+-	return archive.TarWithOptions(src, opts)
++	return chrootarchive.Tar(src, opts, root)
+ }
+ 
+ // ContainerCopy performs a deprecated operation of archiving the resource at
+@@ -239,7 +239,7 @@ func (daemon *Daemon) containerArchivePath(container *container.Container, path
+ 	sourceDir, sourceBase := driver.Dir(resolvedPath), driver.Base(resolvedPath)
+ 	opts := archive.TarResourceRebaseOpts(sourceBase, driver.Base(absPath))
+ 
+-	data, err := archivePath(driver, sourceDir, opts)
++	data, err := archivePath(driver, sourceDir, opts, container.BaseFS.Path())
+ 	if err != nil {
+ 		return nil, nil, err
+ 	}
+@@ -433,7 +433,7 @@ func (daemon *Daemon) containerCopy(container *container.Container, resource str
+ 	archive, err := archivePath(driver, basePath, &archive.TarOptions{
+ 		Compression:  archive.Uncompressed,
+ 		IncludeFiles: filter,
+-	})
++	}, container.BaseFS.Path())
+ 	if err != nil {
+ 		return nil, err
+ 	}
+diff --git a/engine/daemon/export.go b/engine/daemon/export.go
+index 27bc35967d22..01593f4e8a4f 100644
+--- a/engine/daemon/export.go
++++ b/engine/daemon/export.go
+@@ -70,7 +70,7 @@ func (daemon *Daemon) containerExport(container *container.Container) (arch io.R
+ 		Compression: archive.Uncompressed,
+ 		UIDMaps:     daemon.idMapping.UIDs(),
+ 		GIDMaps:     daemon.idMapping.GIDs(),
+-	})
++	}, basefs.Path())
+ 	if err != nil {
+ 		rwlayer.Unmount()
+ 		return nil, err
+diff --git a/engine/pkg/chrootarchive/archive.go b/engine/pkg/chrootarchive/archive.go
+index 7ebca3774c3d..6ff61e6a767a 100644
+--- a/engine/pkg/chrootarchive/archive.go
++++ b/engine/pkg/chrootarchive/archive.go
+@@ -87,3 +87,11 @@ func untarHandler(tarArchive io.Reader, dest string, options *archive.TarOptions
+ 
+ 	return invokeUnpack(r, dest, options, root)
+ }
++
++// Tar tars the requested path while chrooted to the specified root.
++func Tar(srcPath string, options *archive.TarOptions, root string) (io.ReadCloser, error) {
++	if options == nil {
++		options = &archive.TarOptions{}
++	}
++	return invokePack(srcPath, options, root)
++}
+diff --git a/engine/pkg/chrootarchive/archive_unix.go b/engine/pkg/chrootarchive/archive_unix.go
+index 96f07c4bb4d6..ea2879dc002f 100644
+--- a/engine/pkg/chrootarchive/archive_unix.go
++++ b/engine/pkg/chrootarchive/archive_unix.go
+@@ -12,9 +12,11 @@ import (
+ 	"os"
+ 	"path/filepath"
+ 	"runtime"
++	"strings"
+ 
+ 	"github.com/docker/docker/pkg/archive"
+ 	"github.com/docker/docker/pkg/reexec"
++	"github.com/pkg/errors"
+ )
+ 
+ // untar is the entry-point for docker-untar on re-exec. This is not used on
+@@ -24,7 +26,7 @@ func untar() {
+ 	runtime.LockOSThread()
+ 	flag.Parse()
+ 
+-	var options *archive.TarOptions
++	var options archive.TarOptions
+ 
+ 	//read the options from the pipe "ExtraFiles"
+ 	if err := json.NewDecoder(os.NewFile(3, "options")).Decode(&options); err != nil {
+@@ -45,7 +47,7 @@ func untar() {
+ 		fatal(err)
+ 	}
+ 
+-	if err := archive.Unpack(os.Stdin, dst, options); err != nil {
++	if err := archive.Unpack(os.Stdin, dst, &options); err != nil {
+ 		fatal(err)
+ 	}
+ 	// fully consume stdin in case it is zero padded
+@@ -57,6 +59,9 @@ func untar() {
+ }
+ 
+ func invokeUnpack(decompressedArchive io.Reader, dest string, options *archive.TarOptions, root string) error {
++	if root == "" {
++		return errors.New("must specify a root to chroot to")
++	}
+ 
+ 	// We can't pass a potentially large exclude list directly via cmd line
+ 	// because we easily overrun the kernel's max argument/environment size
+@@ -112,3 +117,92 @@ func invokeUnpack(decompressedArchive io.Reader, dest string, options *archive.T
+ 	}
+ 	return nil
+ }
++
++func tar() {
++	runtime.LockOSThread()
++	flag.Parse()
++
++	src := flag.Arg(0)
++	var root string
++	if len(flag.Args()) > 1 {
++		root = flag.Arg(1)
++	}
++
++	if root == "" {
++		root = src
++	}
++
++	if err := realChroot(root); err != nil {
++		fatal(err)
++	}
++
++	var options archive.TarOptions
++	if err := json.NewDecoder(os.Stdin).Decode(&options); err != nil {
++		fatal(err)
++	}
++
++	rdr, err := archive.TarWithOptions(src, &options)
++	if err != nil {
++		fatal(err)
++	}
++	defer rdr.Close()
++
++	if _, err := io.Copy(os.Stdout, rdr); err != nil {
++		fatal(err)
++	}
++
++	os.Exit(0)
++}
++
++func invokePack(srcPath string, options *archive.TarOptions, root string) (io.ReadCloser, error) {
++	if root == "" {
++		return nil, errors.New("root path must not be empty")
++	}
++
++	relSrc, err := filepath.Rel(root, srcPath)
++	if err != nil {
++		return nil, err
++	}
++	if relSrc == "." {
++		relSrc = "/"
++	}
++	if relSrc[0] != '/' {
++		relSrc = "/" + relSrc
++	}
++
++	// make sure we didn't trim a trailing slash with the call to `Rel`
++	if strings.HasSuffix(srcPath, "/") && !strings.HasSuffix(relSrc, "/") {
++		relSrc += "/"
++	}
++
++	cmd := reexec.Command("docker-tar", relSrc, root)
++
++	errBuff := bytes.NewBuffer(nil)
++	cmd.Stderr = errBuff
++
++	tarR, tarW := io.Pipe()
++	cmd.Stdout = tarW
++
++	stdin, err := cmd.StdinPipe()
++	if err != nil {
++		return nil, errors.Wrap(err, "error getting options pipe for tar process")
++	}
++
++	if err := cmd.Start(); err != nil {
++		return nil, errors.Wrap(err, "tar error on re-exec cmd")
++	}
++
++	go func() {
++		err := cmd.Wait()
++		err = errors.Wrapf(err, "error processing tar file: %s", errBuff)
++		tarW.CloseWithError(err)
++	}()
++
++	if err := json.NewEncoder(stdin).Encode(options); err != nil {
++		stdin.Close()
++		return nil, errors.Wrap(err, "tar json encode to pipe failed")
++	}
++	stdin.Close()
++
++	return tarR, nil
++}
+diff --git a/engine/pkg/chrootarchive/archive_windows.go b/engine/pkg/chrootarchive/archive_windows.go
+index bd5712c5c04c..de87113e9544 100644
+--- a/engine/pkg/chrootarchive/archive_windows.go
++++ b/engine/pkg/chrootarchive/archive_windows.go
+@@ -20,3 +20,10 @@ func invokeUnpack(decompressedArchive io.ReadCloser,
+ 	// do the unpack. We call inline instead within the daemon process.
+ 	return archive.Unpack(decompressedArchive, longpath.AddPrefix(dest), options)
+ }
++
++func invokePack(srcPath string, options *archive.TarOptions, root string) (io.ReadCloser, error) {
++	// Windows is different to Linux here because Windows does not support
++	// chroot. Hence there is no point sandboxing a chrooted process to
++	// do the pack. We call inline instead within the daemon process.
++	return archive.TarWithOptions(srcPath, options)
++}
+diff --git a/engine/pkg/chrootarchive/init_unix.go b/engine/pkg/chrootarchive/init_unix.go
+index a15e4bb83c40..c24fea7d9c13 100644
+--- a/engine/pkg/chrootarchive/init_unix.go
++++ b/engine/pkg/chrootarchive/init_unix.go
+@@ -14,6 +14,7 @@ import (
+ func init() {
+ 	reexec.Register("docker-applyLayer", applyLayer)
+ 	reexec.Register("docker-untar", untar)
++	reexec.Register("docker-tar", tar)
+ }
+ 
+ func fatal(err error) {
diff -Nru docker.io-18.09.1+dfsg1/debian/patches/series docker.io-18.09.1+dfsg1/debian/patches/series
--- docker.io-18.09.1+dfsg1/debian/patches/series	2019-05-13 10:34:45.000000000 +0800
+++ docker.io-18.09.1+dfsg1/debian/patches/series	2019-06-23 01:25:10.000000000 +0800
@@ -12,6 +12,9 @@
 cli-fix-manpages-build-script.patch
 cli-fix-registry-debug-message-go-1.11.patch
 
+cve-2018-15664-01-pass-root-to-chroot-to-for-chroot-untar.patch
+cve-2018-15664-02-add-chroot-for-tar-packing-operations.patch
+
 engine-contrib-debootstrap-curl-follow-location.patch
 engine-test-noinstall.patch
 

Attachment: signature.asc
Description: PGP signature


Reply to: