diff --git a/daemon/graphdriver/windows/windows.go b/daemon/graphdriver/windows/windows.go index fc0fe5c500..31f58ab640 100644 --- a/daemon/graphdriver/windows/windows.go +++ b/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) + 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. diff --git a/hack/vendor.sh b/hack/vendor.sh index a72662ea03..0fe174c105 100755 --- a/hack/vendor.sh +++ b/hack/vendor.sh @@ -8,7 +8,7 @@ source 'hack/.vendor-helpers.sh' # the following lines are in sorted order, FYI clone git github.com/Azure/go-ansiterm 388960b655244e76e24c75f48631564eaefade62 clone git github.com/Microsoft/hcsshim v0.2.2 -clone git github.com/Microsoft/go-winio v0.3.0 +clone git github.com/Microsoft/go-winio v0.3.4 clone git github.com/Sirupsen/logrus v0.9.0 # logrus is a common dependency among multiple deps clone git github.com/docker/libtrust 9cbd2a1374f46905c68a4eb3694a130610adc62a clone git github.com/go-check/check 03a4d9dcf2f92eae8e90ed42aa2656f63fdd0b14 https://github.com/cpuguy83/check.git diff --git a/vendor/src/github.com/Microsoft/go-winio/.gitignore b/vendor/src/github.com/Microsoft/go-winio/.gitignore new file mode 100644 index 0000000000..b883f1fdc6 --- /dev/null +++ b/vendor/src/github.com/Microsoft/go-winio/.gitignore @@ -0,0 +1 @@ +*.exe diff --git a/vendor/src/github.com/Microsoft/go-winio/backuptar/tar.go b/vendor/src/github.com/Microsoft/go-winio/backuptar/tar.go index 96069b0daa..c454c4c054 100644 --- a/vendor/src/github.com/Microsoft/go-winio/backuptar/tar.go +++ b/vendor/src/github.com/Microsoft/go-winio/backuptar/tar.go @@ -1,6 +1,7 @@ package backuptar import ( + "encoding/base64" "errors" "fmt" "io" @@ -29,9 +30,10 @@ const ( ) const ( - hdrFileAttributes = "fileattr" - hdrSecurityDescriptor = "sd" - hdrMountPoint = "mountpoint" + hdrFileAttributes = "fileattr" + hdrSecurityDescriptor = "sd" + hdrRawSecurityDescriptor = "rawsd" + hdrMountPoint = "mountpoint" ) func writeZeroes(w io.Writer, count int64) error { @@ -108,7 +110,7 @@ func BasicInfoHeader(name string, size int64, fileInfo *winio.FileBasicInfo) *ta // // MSWINDOWS.fileattr: The Win32 file attributes, as a decimal value // -// MSWINDOWS.sd: The Win32 security descriptor, in SDDL (string) format +// MSWINDOWS.rawsd: The Win32 security descriptor, in raw binary format // // MSWINDOWS.mountpoint: If present, this is a mount point and not a symlink, even though the type is '2' (symlink) func WriteTarFileFromBackupStream(t *tar.Writer, r io.Reader, name string, size int64, fileInfo *winio.FileBasicInfo) error { @@ -133,11 +135,7 @@ func WriteTarFileFromBackupStream(t *tar.Writer, r io.Reader, name string, size if err != nil { return err } - sddl, err := winio.SecurityDescriptorToSddl(sd) - if err != nil { - return err - } - hdr.Winheaders[hdrSecurityDescriptor] = sddl + hdr.Winheaders[hdrRawSecurityDescriptor] = base64.StdEncoding.EncodeToString(sd) case winio.BackupReparseData: hdr.Mode |= c_ISLNK @@ -263,16 +261,28 @@ func FileInfoFromHeader(hdr *tar.Header) (name string, size int64, fileInfo *win // tar file that was not processed, or io.EOF is there are no more. func WriteBackupStreamFromTarFile(w io.Writer, t *tar.Reader, hdr *tar.Header) (*tar.Header, error) { bw := winio.NewBackupStreamWriter(w) + var sd []byte + var err error + // Maintaining old SDDL-based behavior for backward compatibility. All new tar headers written + // by this library will have raw binary for the security descriptor. if sddl, ok := hdr.Winheaders[hdrSecurityDescriptor]; ok { - sd, err := winio.SddlToSecurityDescriptor(sddl) + sd, err = winio.SddlToSecurityDescriptor(sddl) if err != nil { return nil, err } + } + if sdraw, ok := hdr.Winheaders[hdrRawSecurityDescriptor]; ok { + sd, err = base64.StdEncoding.DecodeString(sdraw) + if err != nil { + return nil, err + } + } + if len(sd) != 0 { bhdr := winio.BackupHeader{ Id: winio.BackupSecurity, Size: int64(len(sd)), } - err = bw.WriteHeader(&bhdr) + err := bw.WriteHeader(&bhdr) if err != nil { return nil, err } @@ -284,7 +294,7 @@ func WriteBackupStreamFromTarFile(w io.Writer, t *tar.Reader, hdr *tar.Header) ( if hdr.Typeflag == tar.TypeSymlink { _, isMountPoint := hdr.Winheaders[hdrMountPoint] rp := winio.ReparsePoint{ - Target: hdr.Linkname, + Target: filepath.FromSlash(hdr.Linkname), IsMountPoint: isMountPoint, } reparse := winio.EncodeReparsePoint(&rp) diff --git a/vendor/src/github.com/Microsoft/go-winio/privilege.go b/vendor/src/github.com/Microsoft/go-winio/privilege.go index 81f9af7b70..3d59412c76 100644 --- a/vendor/src/github.com/Microsoft/go-winio/privilege.go +++ b/vendor/src/github.com/Microsoft/go-winio/privilege.go @@ -5,14 +5,17 @@ import ( "encoding/binary" "fmt" "runtime" + "sync" "syscall" "unicode/utf16" + + "golang.org/x/sys/windows" ) -//sys adjustTokenPrivileges(token syscall.Handle, releaseAll bool, input *byte, outputSize uint32, output *byte, requiredSize *uint32) (success bool, err error) [true] = advapi32.AdjustTokenPrivileges +//sys adjustTokenPrivileges(token windows.Token, releaseAll bool, input *byte, outputSize uint32, output *byte, requiredSize *uint32) (success bool, err error) [true] = advapi32.AdjustTokenPrivileges //sys impersonateSelf(level uint32) (err error) = advapi32.ImpersonateSelf //sys revertToSelf() (err error) = advapi32.RevertToSelf -//sys openThreadToken(thread syscall.Handle, accessMask uint32, openAsSelf bool, token *syscall.Handle) (err error) = advapi32.OpenThreadToken +//sys openThreadToken(thread syscall.Handle, accessMask uint32, openAsSelf bool, token *windows.Token) (err error) = advapi32.OpenThreadToken //sys getCurrentThread() (h syscall.Handle) = GetCurrentThread //sys lookupPrivilegeValue(systemName string, name string, luid *uint64) (err error) = advapi32.LookupPrivilegeValueW //sys lookupPrivilegeName(systemName string, luid *uint64, buffer *uint16, size *uint32) (err error) = advapi32.LookupPrivilegeNameW @@ -34,6 +37,12 @@ const ( securityDelegation ) +var ( + privNames = make(map[string]uint64) + privNameMutex sync.Mutex +) + +// PrivilegeError represents an error enabling privileges. type PrivilegeError struct { privileges []uint64 } @@ -56,19 +65,16 @@ func (e *PrivilegeError) Error() string { return s } +// RunWithPrivilege enables a single privilege for a function call. func RunWithPrivilege(name string, fn func() error) error { return RunWithPrivileges([]string{name}, fn) } +// RunWithPrivileges enables privileges for a function call. func RunWithPrivileges(names []string, fn func() error) error { - var privileges []uint64 - for _, name := range names { - p := uint64(0) - err := lookupPrivilegeValue("", name, &p) - if err != nil { - return err - } - privileges = append(privileges, p) + privileges, err := mapPrivileges(names) + if err != nil { + return err } runtime.LockOSThread() defer runtime.UnlockOSThread() @@ -84,7 +90,43 @@ func RunWithPrivileges(names []string, fn func() error) error { return fn() } -func adjustPrivileges(token syscall.Handle, privileges []uint64) error { +func mapPrivileges(names []string) ([]uint64, error) { + var privileges []uint64 + privNameMutex.Lock() + defer privNameMutex.Unlock() + for _, name := range names { + p, ok := privNames[name] + if !ok { + err := lookupPrivilegeValue("", name, &p) + if err != nil { + return nil, err + } + privNames[name] = p + } + privileges = append(privileges, p) + } + return privileges, nil +} + +// EnableProcessPrivileges enables privileges globally for the process. +func EnableProcessPrivileges(names []string) error { + privileges, err := mapPrivileges(names) + if err != nil { + return err + } + + p, _ := windows.GetCurrentProcess() + var token windows.Token + err = windows.OpenProcessToken(p, windows.TOKEN_ADJUST_PRIVILEGES|windows.TOKEN_QUERY, &token) + if err != nil { + return err + } + + defer token.Close() + return adjustPrivileges(token, privileges) +} + +func adjustPrivileges(token windows.Token, privileges []uint64) error { var b bytes.Buffer binary.Write(&b, binary.LittleEndian, uint32(len(privileges))) for _, p := range privileges { @@ -113,23 +155,22 @@ func getPrivilegeName(luid uint64) string { var displayNameBuffer [256]uint16 displayBufSize := uint32(len(displayNameBuffer)) - var langId uint32 - err = lookupPrivilegeDisplayName("", &nameBuffer[0], &displayNameBuffer[0], &displayBufSize, &langId) + var langID uint32 + err = lookupPrivilegeDisplayName("", &nameBuffer[0], &displayNameBuffer[0], &displayBufSize, &langID) if err != nil { - return fmt.Sprintf("", utf16.Decode(nameBuffer[:bufSize])) + return fmt.Sprintf("", string(utf16.Decode(nameBuffer[:bufSize]))) } return string(utf16.Decode(displayNameBuffer[:displayBufSize])) } -func newThreadToken() (syscall.Handle, error) { +func newThreadToken() (windows.Token, error) { err := impersonateSelf(securityImpersonation) if err != nil { - panic(err) return 0, err } - var token syscall.Handle + var token windows.Token err = openThreadToken(getCurrentThread(), syscall.TOKEN_ADJUST_PRIVILEGES|syscall.TOKEN_QUERY, false, &token) if err != nil { rerr := revertToSelf() @@ -141,10 +182,10 @@ func newThreadToken() (syscall.Handle, error) { return token, nil } -func releaseThreadToken(h syscall.Handle) { +func releaseThreadToken(h windows.Token) { err := revertToSelf() if err != nil { panic(err) } - syscall.Close(h) + h.Close() } diff --git a/vendor/src/github.com/Microsoft/go-winio/reparse.go b/vendor/src/github.com/Microsoft/go-winio/reparse.go index 9425711ed4..fc1ee4d3a3 100644 --- a/vendor/src/github.com/Microsoft/go-winio/reparse.go +++ b/vendor/src/github.com/Microsoft/go-winio/reparse.go @@ -80,7 +80,7 @@ func EncodeReparsePoint(rp *ReparsePoint) []byte { var ntTarget string relative := false if strings.HasPrefix(rp.Target, `\\?\`) { - ntTarget = rp.Target + ntTarget = `\??\` + rp.Target[4:] } else if strings.HasPrefix(rp.Target, `\\`) { ntTarget = `\??\UNC\` + rp.Target[2:] } else if len(rp.Target) >= 2 && isDriveLetter(rp.Target[0]) && rp.Target[1] == ':' { diff --git a/vendor/src/github.com/Microsoft/go-winio/zsyscall.go b/vendor/src/github.com/Microsoft/go-winio/zsyscall.go index 74b6e97a66..6d047d3690 100644 --- a/vendor/src/github.com/Microsoft/go-winio/zsyscall.go +++ b/vendor/src/github.com/Microsoft/go-winio/zsyscall.go @@ -2,8 +2,12 @@ package winio -import "unsafe" -import "syscall" +import ( + "syscall" + "unsafe" + + "golang.org/x/sys/windows" +) var _ unsafe.Pointer @@ -300,7 +304,7 @@ func setFileInformationByHandle(h syscall.Handle, class uint32, buffer *byte, si return } -func adjustTokenPrivileges(token syscall.Handle, releaseAll bool, input *byte, outputSize uint32, output *byte, requiredSize *uint32) (success bool, err error) { +func adjustTokenPrivileges(token windows.Token, releaseAll bool, input *byte, outputSize uint32, output *byte, requiredSize *uint32) (success bool, err error) { var _p0 uint32 if releaseAll { _p0 = 1 @@ -343,7 +347,7 @@ func revertToSelf() (err error) { return } -func openThreadToken(thread syscall.Handle, accessMask uint32, openAsSelf bool, token *syscall.Handle) (err error) { +func openThreadToken(thread syscall.Handle, accessMask uint32, openAsSelf bool, token *windows.Token) (err error) { var _p0 uint32 if openAsSelf { _p0 = 1