ソースを参照

Windows: reexec when importing layers

This improves reliability by doing parsing of potentially untrusted data
in a separate process. It opens the door for further security improvements
if we can lock down the reexec-ed process. It also improves import
performance by only taking the backup and restore privileges once, for the
whole process.

Signed-off-by: John Starks <jostarks@microsoft.com>
John Starks 9 年 前
コミット
b3bc5e0fe4
1 ファイル変更99 行追加42 行削除
  1. 99 42
      daemon/graphdriver/windows/windows.go

+ 99 - 42
daemon/graphdriver/windows/windows.go

@@ -4,6 +4,7 @@ package windows
 
 import (
 	"bufio"
+	"bytes"
 	"crypto/sha512"
 	"encoding/json"
 	"fmt"
@@ -12,6 +13,7 @@ import (
 	"os"
 	"path"
 	"path/filepath"
+	"strconv"
 	"strings"
 	"syscall"
 	"time"
@@ -28,6 +30,7 @@ import (
 	"github.com/docker/docker/pkg/idtools"
 	"github.com/docker/docker/pkg/ioutils"
 	"github.com/docker/docker/pkg/longpath"
+	"github.com/docker/docker/pkg/reexec"
 	"github.com/docker/docker/pkg/system"
 	"github.com/vbatts/tar-split/tar/storage"
 )
@@ -36,6 +39,7 @@ import (
 func init() {
 	graphdriver.Register("windowsfilter", InitFilter)
 	graphdriver.Register("windowsdiff", InitDiff)
+	reexec.Register("docker-windows-write-layer", writeLayer)
 }
 
 const (
@@ -308,18 +312,21 @@ func (d *Driver) Diff(id, parent string) (_ archive.Archive, err error) {
 	if err := hcsshim.UnprepareLayer(d.info, rID); err != nil {
 		return nil, err
 	}
-	defer func() {
+	prepare := func() {
 		if err := hcsshim.PrepareLayer(d.info, rID, layerChain); err != nil {
 			logrus.Warnf("Failed to Deactivate %s: %s", rID, err)
 		}
-	}()
+	}
 
 	arch, err := d.exportLayer(rID, layerChain)
 	if err != nil {
+		prepare()
 		return
 	}
 	return ioutils.NewReadCloserWrapper(arch, func() error {
-		return arch.Close()
+		err := arch.Close()
+		prepare()
+		return err
 	}), nil
 }
 
@@ -346,29 +353,35 @@ func (d *Driver) Changes(id, parent string) ([]archive.Change, error) {
 		}
 	}()
 
-	r, err := hcsshim.NewLayerReader(d.info, id, parentChain)
-	if err != nil {
-		return nil, err
-	}
-	defer r.Close()
-
 	var changes []archive.Change
-	for {
-		name, _, fileInfo, err := r.Next()
-		if err == io.EOF {
-			break
-		}
+	err = winio.RunWithPrivilege(winio.SeBackupPrivilege, func() error {
+		r, err := hcsshim.NewLayerReader(d.info, id, parentChain)
 		if err != nil {
-			return nil, err
+			return err
 		}
-		name = filepath.ToSlash(name)
-		if fileInfo == nil {
-			changes = append(changes, archive.Change{Path: name, Kind: archive.ChangeDelete})
-		} else {
-			// Currently there is no way to tell between an add and a modify.
-			changes = append(changes, archive.Change{Path: name, Kind: archive.ChangeModify})
+		defer r.Close()
+
+		for {
+			name, _, fileInfo, err := r.Next()
+			if err == io.EOF {
+				return nil
+			}
+			if err != nil {
+				return err
+			}
+			name = filepath.ToSlash(name)
+			if fileInfo == nil {
+				changes = append(changes, archive.Change{Path: name, Kind: archive.ChangeDelete})
+			} else {
+				// Currently there is no way to tell between an add and a modify.
+				changes = append(changes, archive.Change{Path: name, Kind: archive.ChangeModify})
+			}
 		}
+	})
+	if err != nil {
+		return nil, err
 	}
+
 	return changes, nil
 }
 
@@ -554,19 +567,21 @@ func writeTarFromLayer(r hcsshim.LayerReader, w io.Writer) error {
 
 // exportLayer generates an archive from a layer based on the given ID.
 func (d *Driver) exportLayer(id string, parentLayerPaths []string) (archive.Archive, error) {
-	var r hcsshim.LayerReader
-	r, err := hcsshim.NewLayerReader(d.info, id, parentLayerPaths)
-	if err != nil {
-		return nil, err
-	}
-
 	archive, w := io.Pipe()
 	go func() {
-		err := writeTarFromLayer(r, w)
-		cerr := r.Close()
-		if err == nil {
-			err = cerr
-		}
+		err := winio.RunWithPrivilege(winio.SeBackupPrivilege, func() error {
+			r, err := hcsshim.NewLayerReader(d.info, id, parentLayerPaths)
+			if err != nil {
+				return err
+			}
+
+			err = writeTarFromLayer(r, w)
+			cerr := r.Close()
+			if err == nil {
+				err = cerr
+			}
+			return err
+		})
 		w.CloseWithError(err)
 	}()
 
@@ -682,21 +697,63 @@ func addAceToSddlDacl(sddl, ace string) (string, bool) {
 
 // importLayer adds a new layer to the tag and graph store based on the given data.
 func (d *Driver) importLayer(id string, layerData archive.Reader, parentLayerPaths []string) (size int64, err error) {
-	var w hcsshim.LayerWriter
-	w, err = hcsshim.NewLayerWriter(d.info, id, parentLayerPaths)
-	if err != nil {
+	cmd := reexec.Command(append([]string{"docker-windows-write-layer", d.info.HomeDir, id}, parentLayerPaths...)...)
+	output := bytes.NewBuffer(nil)
+	cmd.Stdin = layerData
+	cmd.Stdout = output
+	cmd.Stderr = output
+
+	if err = cmd.Start(); err != nil {
 		return
 	}
-	size, err = writeLayerFromTar(layerData, w)
-	if err != nil {
-		w.Close()
-		return
+
+	if err = cmd.Wait(); err != nil {
+		return 0, fmt.Errorf("re-exec error: %v: output: %s", err, output)
 	}
-	err = w.Close()
+
+	return strconv.ParseInt(output.String(), 10, 64)
+}
+
+// writeLayer is the re-exec entry point for writing a layer from a tar file
+func writeLayer() {
+	home := os.Args[1]
+	id := os.Args[2]
+	parentLayerPaths := os.Args[3:]
+
+	err := func() error {
+		err := winio.EnableProcessPrivileges([]string{winio.SeBackupPrivilege, winio.SeRestorePrivilege})
+		if err != nil {
+			return err
+		}
+
+		info := hcsshim.DriverInfo{
+			Flavour: filterDriver,
+			HomeDir: home,
+		}
+
+		w, err := hcsshim.NewLayerWriter(info, id, parentLayerPaths)
+		if err != nil {
+			return err
+		}
+
+		size, err := writeLayerFromTar(os.Stdin, w)
+		if err != nil {
+			return err
+		}
+
+		err = w.Close()
+		if err != nil {
+			return err
+		}
+
+		fmt.Fprint(os.Stdout, size)
+		return nil
+	}()
+
 	if err != nil {
-		return
+		fmt.Fprint(os.Stderr, err)
+		os.Exit(1)
 	}
-	return
 }
 
 // resolveID computes the layerID information based on the given id.