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>
This commit is contained in:
John Starks 2016-05-12 21:11:18 -07:00
parent 6d40104f11
commit b3bc5e0fe4
1 changed files with 100 additions and 43 deletions

View File

@ -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)
var changes []archive.Change
err = winio.RunWithPrivilege(winio.SeBackupPrivilege, func() error {
r, err := hcsshim.NewLayerReader(d.info, id, parentChain)
if err != nil {
return err
}
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
}
defer r.Close()
var changes []archive.Change
for {
name, _, fileInfo, err := r.Next()
if err == io.EOF {
break
}
if err != nil {
return nil, 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})
}
}
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.