Browse code

Windows: Fix Hyper-V container ACLs for TP5 (#21974)

In TP5, Hyper-V containers need all image files ACLed so that the virtual
machine process can access them. This was fixed post-TP5 in Windows, but
for TP5 we need to explicitly add these ACLs.

Signed-off-by: John Starks <jostarks@microsoft.com>

John Starks authored on 2016/04/14 02:15:38
Showing 2 changed files
... ...
@@ -15,6 +15,7 @@ import (
15 15
 	"strings"
16 16
 	"syscall"
17 17
 	"time"
18
+	"unsafe"
18 19
 
19 20
 	"github.com/Microsoft/go-winio"
20 21
 	"github.com/Microsoft/go-winio/archive/tar"
... ...
@@ -27,6 +28,7 @@ import (
27 27
 	"github.com/docker/docker/pkg/idtools"
28 28
 	"github.com/docker/docker/pkg/ioutils"
29 29
 	"github.com/docker/docker/pkg/longpath"
30
+	"github.com/docker/docker/pkg/system"
30 31
 	"github.com/vbatts/tar-split/tar/storage"
31 32
 )
32 33
 
... ...
@@ -51,6 +53,10 @@ type Driver struct {
51 51
 
52 52
 var _ graphdriver.DiffGetterDriver = &Driver{}
53 53
 
54
+func isTP5OrOlder() bool {
55
+	return system.GetOSVersion().Build <= 14300
56
+}
57
+
54 58
 // InitFilter returns a new Windows storage filter driver.
55 59
 func InitFilter(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (graphdriver.Driver, error) {
56 60
 	logrus.Debugf("WindowsGraphDriver InitFilter at %s", home)
... ...
@@ -158,6 +164,30 @@ func (d *Driver) create(id, parent, mountLabel string, readOnly bool, storageOpt
158 158
 		if len(layerChain) != 0 {
159 159
 			parentPath = layerChain[0]
160 160
 		}
161
+
162
+		if isTP5OrOlder() {
163
+			// Pre-create the layer directory, providing an ACL to give the Hyper-V Virtual Machines
164
+			// group access. This is necessary to ensure that Hyper-V containers can access the
165
+			// virtual machine data. This is not necessary post-TP5.
166
+			path, err := syscall.UTF16FromString(filepath.Join(d.info.HomeDir, id))
167
+			if err != nil {
168
+				return err
169
+			}
170
+			// Give system and administrators full control, and VMs read, write, and execute.
171
+			// Mark these ACEs as inherited.
172
+			sd, err := winio.SddlToSecurityDescriptor("D:(A;OICI;FA;;;SY)(A;OICI;FA;;;BA)(A;OICI;FRFWFX;;;S-1-5-83-0)")
173
+			if err != nil {
174
+				return err
175
+			}
176
+			err = syscall.CreateDirectory(&path[0], &syscall.SecurityAttributes{
177
+				Length:             uint32(unsafe.Sizeof(syscall.SecurityAttributes{})),
178
+				SecurityDescriptor: uintptr(unsafe.Pointer(&sd[0])),
179
+			})
180
+			if err != nil {
181
+				return err
182
+			}
183
+		}
184
+
161 185
 		if err := hcsshim.CreateSandboxLayer(d.info, id, parentPath, layerChain); err != nil {
162 186
 			return err
163 187
 		}
... ...
@@ -578,6 +608,24 @@ func writeLayerFromTar(r archive.Reader, w hcsshim.LayerWriter) (int64, error) {
578 578
 				return 0, err
579 579
 			}
580 580
 			buf.Reset(w)
581
+
582
+			// Add the Hyper-V Virutal Machine group ACE to the security descriptor
583
+			// for TP5 so that Xenons can access all files. This is not necessary
584
+			// for post-TP5 builds.
585
+			if isTP5OrOlder() {
586
+				if sddl, ok := hdr.Winheaders["sd"]; ok {
587
+					var ace string
588
+					if hdr.Typeflag == tar.TypeDir {
589
+						ace = "(A;OICI;0x1200a9;;;S-1-5-83-0)"
590
+					} else {
591
+						ace = "(A;;0x1200a9;;;S-1-5-83-0)"
592
+					}
593
+					if hdr.Winheaders["sd"], ok = addAceToSddlDacl(sddl, ace); !ok {
594
+						logrus.Debugf("failed to add VM ACE to %s", sddl)
595
+					}
596
+				}
597
+			}
598
+
581 599
 			hdr, err = backuptar.WriteBackupStreamFromTarFile(buf, t, hdr)
582 600
 			ferr := buf.Flush()
583 601
 			if ferr != nil {
... ...
@@ -592,6 +640,46 @@ func writeLayerFromTar(r archive.Reader, w hcsshim.LayerWriter) (int64, error) {
592 592
 	return totalSize, nil
593 593
 }
594 594
 
595
+func addAceToSddlDacl(sddl, ace string) (string, bool) {
596
+	daclStart := strings.Index(sddl, "D:")
597
+	if daclStart < 0 {
598
+		return sddl, false
599
+	}
600
+
601
+	dacl := sddl[daclStart:]
602
+	daclEnd := strings.Index(dacl, "S:")
603
+	if daclEnd < 0 {
604
+		daclEnd = len(dacl)
605
+	}
606
+	dacl = dacl[:daclEnd]
607
+
608
+	if strings.Contains(dacl, ace) {
609
+		return sddl, true
610
+	}
611
+
612
+	i := 2
613
+	for i+1 < len(dacl) {
614
+		if dacl[i] != '(' {
615
+			return sddl, false
616
+		}
617
+
618
+		if dacl[i+1] == 'A' {
619
+			break
620
+		}
621
+
622
+		i += 2
623
+		for p := 1; i < len(dacl) && p > 0; i++ {
624
+			if dacl[i] == '(' {
625
+				p++
626
+			} else if dacl[i] == ')' {
627
+				p--
628
+			}
629
+		}
630
+	}
631
+
632
+	return sddl[:daclStart+i] + ace + sddl[daclStart+i:], true
633
+}
634
+
595 635
 // importLayer adds a new layer to the tag and graph store based on the given data.
596 636
 func (d *Driver) importLayer(id string, layerData archive.Reader, parentLayerPaths []string) (size int64, err error) {
597 637
 	var w hcsshim.LayerWriter
598 638
new file mode 100644
... ...
@@ -0,0 +1,18 @@
0
+package windows
1
+
2
+import "testing"
3
+
4
+func TestAddAceToSddlDacl(t *testing.T) {
5
+	cases := [][3]string{
6
+		{"D:", "(A;;;)", "D:(A;;;)"},
7
+		{"D:(A;;;)", "(A;;;)", "D:(A;;;)"},
8
+		{"O:D:(A;;;stuff)", "(A;;;new)", "O:D:(A;;;new)(A;;;stuff)"},
9
+		{"O:D:(D;;;no)(A;;;stuff)", "(A;;;new)", "O:D:(D;;;no)(A;;;new)(A;;;stuff)"},
10
+	}
11
+
12
+	for _, c := range cases {
13
+		if newSddl, worked := addAceToSddlDacl(c[0], c[1]); !worked || newSddl != c[2] {
14
+			t.Errorf("%s + %s == %s, expected %s (%v)", c[0], c[1], newSddl, c[2], worked)
15
+		}
16
+	}
17
+}