Add chroot for tar packing operations
authorBrian Goff <cpuguy83@gmail.com>
Thu, 30 May 2019 21:55:52 +0000 (14:55 -0700)
committerShengjing Zhu <zhsj@debian.org>
Sat, 22 Jun 2019 17:25:10 +0000 (18:25 +0100)
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

Gbp-Pq: Name cve-2018-15664-02-add-chroot-for-tar-packing-operations.patch

engine/daemon/archive.go
engine/daemon/export.go
engine/pkg/chrootarchive/archive.go
engine/pkg/chrootarchive/archive_unix.go
engine/pkg/chrootarchive/archive_windows.go
engine/pkg/chrootarchive/init_unix.go

index 9f56ca750392dc7b9f875104c48003041aa96693..109376b4b566e25c0eb69c8b2114897fbbca8576 100644 (file)
@@ -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
        }
index 27bc35967d220fecf2ecc38fbfe62c686e903c19..01593f4e8a4f48b612df28fd0011e81e8ecb2301 100644 (file)
@@ -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
index 7ebca3774c3dcbc6a53ceef89d2d3132bc5ae9a3..6ff61e6a767af105d6584b4e5c60f75ed59f747b 100644 (file)
@@ -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)
+}
index 96f07c4bb4d688412e405e944b9127783954b43d..ea2879dc002f2b92d1657c3b03fb71441209c6e7 100644 (file)
@@ -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
+}
index bd5712c5c04cb7c1f5ed28be9eda00c8df0d8dfc..de87113e95448f0875ee4e86be0986a20f56bd74 100644 (file)
@@ -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)
+}
index a15e4bb83c40e6dbd56264e618e30781a771eb2f..c24fea7d9c13add5195b90e8eaf5cb3e75a48780 100644 (file)
@@ -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) {