diff --git a/daemon/graphdriver/windows/windows.go b/daemon/graphdriver/windows/windows.go index c7581f77df..fc0fe5c500 100644 --- a/daemon/graphdriver/windows/windows.go +++ b/daemon/graphdriver/windows/windows.go @@ -15,6 +15,7 @@ import ( "strings" "syscall" "time" + "unsafe" "github.com/Microsoft/go-winio" "github.com/Microsoft/go-winio/archive/tar" @@ -27,6 +28,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/system" "github.com/vbatts/tar-split/tar/storage" ) @@ -51,6 +53,10 @@ type Driver struct { var _ graphdriver.DiffGetterDriver = &Driver{} +func isTP5OrOlder() bool { + return system.GetOSVersion().Build <= 14300 +} + // InitFilter returns a new Windows storage filter driver. func InitFilter(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (graphdriver.Driver, error) { logrus.Debugf("WindowsGraphDriver InitFilter at %s", home) @@ -158,6 +164,30 @@ func (d *Driver) create(id, parent, mountLabel string, readOnly bool, storageOpt if len(layerChain) != 0 { parentPath = layerChain[0] } + + if isTP5OrOlder() { + // Pre-create the layer directory, providing an ACL to give the Hyper-V Virtual Machines + // group access. This is necessary to ensure that Hyper-V containers can access the + // virtual machine data. This is not necessary post-TP5. + path, err := syscall.UTF16FromString(filepath.Join(d.info.HomeDir, id)) + if err != nil { + return err + } + // Give system and administrators full control, and VMs read, write, and execute. + // Mark these ACEs as inherited. + sd, err := winio.SddlToSecurityDescriptor("D:(A;OICI;FA;;;SY)(A;OICI;FA;;;BA)(A;OICI;FRFWFX;;;S-1-5-83-0)") + if err != nil { + return err + } + err = syscall.CreateDirectory(&path[0], &syscall.SecurityAttributes{ + Length: uint32(unsafe.Sizeof(syscall.SecurityAttributes{})), + SecurityDescriptor: uintptr(unsafe.Pointer(&sd[0])), + }) + if err != nil { + return err + } + } + if err := hcsshim.CreateSandboxLayer(d.info, id, parentPath, layerChain); err != nil { return err } @@ -578,6 +608,24 @@ func writeLayerFromTar(r archive.Reader, w hcsshim.LayerWriter) (int64, error) { return 0, err } buf.Reset(w) + + // Add the Hyper-V Virutal Machine group ACE to the security descriptor + // for TP5 so that Xenons can access all files. This is not necessary + // for post-TP5 builds. + if isTP5OrOlder() { + if sddl, ok := hdr.Winheaders["sd"]; ok { + var ace string + if hdr.Typeflag == tar.TypeDir { + ace = "(A;OICI;0x1200a9;;;S-1-5-83-0)" + } else { + ace = "(A;;0x1200a9;;;S-1-5-83-0)" + } + if hdr.Winheaders["sd"], ok = addAceToSddlDacl(sddl, ace); !ok { + logrus.Debugf("failed to add VM ACE to %s", sddl) + } + } + } + hdr, err = backuptar.WriteBackupStreamFromTarFile(buf, t, hdr) ferr := buf.Flush() if ferr != nil { @@ -592,6 +640,46 @@ func writeLayerFromTar(r archive.Reader, w hcsshim.LayerWriter) (int64, error) { return totalSize, nil } +func addAceToSddlDacl(sddl, ace string) (string, bool) { + daclStart := strings.Index(sddl, "D:") + if daclStart < 0 { + return sddl, false + } + + dacl := sddl[daclStart:] + daclEnd := strings.Index(dacl, "S:") + if daclEnd < 0 { + daclEnd = len(dacl) + } + dacl = dacl[:daclEnd] + + if strings.Contains(dacl, ace) { + return sddl, true + } + + i := 2 + for i+1 < len(dacl) { + if dacl[i] != '(' { + return sddl, false + } + + if dacl[i+1] == 'A' { + break + } + + i += 2 + for p := 1; i < len(dacl) && p > 0; i++ { + if dacl[i] == '(' { + p++ + } else if dacl[i] == ')' { + p-- + } + } + } + + return sddl[:daclStart+i] + ace + sddl[daclStart+i:], true +} + // 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 diff --git a/daemon/graphdriver/windows/windows_windows_test.go b/daemon/graphdriver/windows/windows_windows_test.go new file mode 100644 index 0000000000..911a36251f --- /dev/null +++ b/daemon/graphdriver/windows/windows_windows_test.go @@ -0,0 +1,18 @@ +package windows + +import "testing" + +func TestAddAceToSddlDacl(t *testing.T) { + cases := [][3]string{ + {"D:", "(A;;;)", "D:(A;;;)"}, + {"D:(A;;;)", "(A;;;)", "D:(A;;;)"}, + {"O:D:(A;;;stuff)", "(A;;;new)", "O:D:(A;;;new)(A;;;stuff)"}, + {"O:D:(D;;;no)(A;;;stuff)", "(A;;;new)", "O:D:(D;;;no)(A;;;new)(A;;;stuff)"}, + } + + for _, c := range cases { + if newSddl, worked := addAceToSddlDacl(c[0], c[1]); !worked || newSddl != c[2] { + t.Errorf("%s + %s == %s, expected %s (%v)", c[0], c[1], newSddl, c[2], worked) + } + } +}