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