...

Source file src/github.com/Microsoft/hcsshim/internal/security/grantvmgroupaccess_test.go

Documentation: github.com/Microsoft/hcsshim/internal/security

     1  //go:build windows
     2  // +build windows
     3  
     4  package security
     5  
     6  import (
     7  	"fmt"
     8  	"os"
     9  	"os/exec"
    10  	"path/filepath"
    11  	"regexp"
    12  	"testing"
    13  )
    14  
    15  const (
    16  	vmAccountName = `NT VIRTUAL MACHINE\\Virtual Machines`
    17  	vmAccountSID  = "S-1-5-83-0"
    18  )
    19  
    20  // TestGrantVmGroupAccess verifies for the three case of a file, a directory,
    21  // and a file in a directory that the appropriate ACEs are set, including
    22  // inheritance in the second two examples. These are the expected ACES. Is
    23  // verified by running icacls and comparing output.
    24  //
    25  // File:
    26  // S-1-15-3-1024-2268835264-3721307629-241982045-173645152-1490879176-104643441-2915960892-1612460704:(R,W)
    27  // S-1-5-83-1-3166535780-1122986932-343720105-43916321:(R,W)
    28  //
    29  // Directory:
    30  // S-1-15-3-1024-2268835264-3721307629-241982045-173645152-1490879176-104643441-2915960892-1612460704:(OI)(CI)(R,W)
    31  // S-1-5-83-1-3166535780-1122986932-343720105-43916321:(OI)(CI)(R,W)
    32  //
    33  // File in directory (inherited):
    34  // S-1-15-3-1024-2268835264-3721307629-241982045-173645152-1490879176-104643441-2915960892-1612460704:(I)(R,W)
    35  // S-1-5-83-1-3166535780-1122986932-343720105-43916321:(I)(R,W)
    36  
    37  func TestGrantVmGroupAccessDefault(t *testing.T) {
    38  	f1Path := filepath.Join(t.TempDir(), "gvmgafile")
    39  	f, err := os.Create(f1Path)
    40  	if err != nil {
    41  		t.Fatal(err)
    42  	}
    43  	defer func() {
    44  		_ = f.Close()
    45  		_ = os.Remove(f1Path)
    46  	}()
    47  
    48  	dir2 := t.TempDir()
    49  	f2Path := filepath.Join(dir2, "find.txt")
    50  	find, err := os.Create(f2Path)
    51  	if err != nil {
    52  		t.Fatal(err)
    53  	}
    54  	defer func() {
    55  		_ = find.Close()
    56  		_ = os.Remove(f2Path)
    57  	}()
    58  
    59  	if err := GrantVmGroupAccess(f1Path); err != nil {
    60  		t.Fatal(err)
    61  	}
    62  
    63  	if err := GrantVmGroupAccess(dir2); err != nil {
    64  		t.Fatal(err)
    65  	}
    66  
    67  	verifyVMAccountDACLs(t,
    68  		f1Path,
    69  		[]string{`(R)`},
    70  	)
    71  
    72  	// Two items here:
    73  	//  - One explicit read only.
    74  	//  - Other applies to this folder, subfolders and files
    75  	//      (OI): object inherit
    76  	//      (CI): container inherit
    77  	//      (IO): inherit only
    78  	//      (GR): generic read
    79  	//
    80  	// In properties for the directory, advanced security settings, this will
    81  	// show as a single line "Allow/Virtual Machines/Read/Inherited from none/This folder, subfolder and files
    82  	verifyVMAccountDACLs(t,
    83  		dir2,
    84  		[]string{`(R)`, `(OI)(CI)(IO)(GR)`},
    85  	)
    86  
    87  	verifyVMAccountDACLs(t,
    88  		f2Path,
    89  		[]string{`(I)(R)`},
    90  	)
    91  }
    92  
    93  func TestGrantVMGroupAccess_File_DesiredPermissions(t *testing.T) {
    94  	type config struct {
    95  		name                string
    96  		desiredAccess       accessMask
    97  		expectedPermissions []string
    98  	}
    99  
   100  	for _, cfg := range []config{
   101  		{
   102  			name:                "Read",
   103  			desiredAccess:       AccessMaskRead,
   104  			expectedPermissions: []string{`(R)`},
   105  		},
   106  		{
   107  			name:                "Write",
   108  			desiredAccess:       AccessMaskWrite,
   109  			expectedPermissions: []string{`(W,Rc)`},
   110  		},
   111  		{
   112  			name:                "Execute",
   113  			desiredAccess:       AccessMaskExecute,
   114  			expectedPermissions: []string{`(Rc,S,X,RA)`},
   115  		},
   116  		{
   117  			name:                "ReadWrite",
   118  			desiredAccess:       AccessMaskRead | AccessMaskWrite,
   119  			expectedPermissions: []string{`(R,W)`},
   120  		},
   121  		{
   122  			name:                "ReadExecute",
   123  			desiredAccess:       AccessMaskRead | AccessMaskExecute,
   124  			expectedPermissions: []string{`(RX)`},
   125  		},
   126  		{
   127  			name:                "WriteExecute",
   128  			desiredAccess:       AccessMaskWrite | AccessMaskExecute,
   129  			expectedPermissions: []string{`(W,Rc,X,RA)`},
   130  		},
   131  		{
   132  			name:                "ReadWriteExecute",
   133  			desiredAccess:       AccessMaskRead | AccessMaskWrite | AccessMaskExecute,
   134  			expectedPermissions: []string{`(RX,W)`},
   135  		},
   136  		{
   137  			name:                "All",
   138  			desiredAccess:       AccessMaskAll,
   139  			expectedPermissions: []string{`(F)`},
   140  		},
   141  	} {
   142  		t.Run(cfg.name, func(t *testing.T) {
   143  			dir := t.TempDir()
   144  			fd, err := os.Create(filepath.Join(dir, "test.txt"))
   145  			if err != nil {
   146  				t.Fatalf("failed to create temporary file: %s", err)
   147  			}
   148  			defer func() {
   149  				_ = fd.Close()
   150  				_ = os.Remove(fd.Name())
   151  			}()
   152  
   153  			if err := GrantVmGroupAccessWithMask(fd.Name(), cfg.desiredAccess); err != nil {
   154  				t.Fatal(err)
   155  			}
   156  			verifyVMAccountDACLs(t, fd.Name(), cfg.expectedPermissions)
   157  		})
   158  	}
   159  }
   160  
   161  func TestGrantVMGroupAccess_Directory_Permissions(t *testing.T) {
   162  	type config struct {
   163  		name            string
   164  		access          accessMask
   165  		filePermissions []string
   166  		dirPermissions  []string
   167  	}
   168  
   169  	for _, cfg := range []config{
   170  		{
   171  			name:            "Read",
   172  			access:          AccessMaskRead,
   173  			filePermissions: []string{`(I)(R)`},
   174  			dirPermissions:  []string{`(R)`, `(OI)(CI)(IO)(GR)`},
   175  		},
   176  		{
   177  			name:            "Write",
   178  			access:          AccessMaskWrite,
   179  			filePermissions: []string{`(I)(W,Rc)`},
   180  			dirPermissions:  []string{`(W,Rc)`, `(OI)(CI)(IO)(GW)`},
   181  		},
   182  		{
   183  			name:            "Execute",
   184  			access:          AccessMaskExecute,
   185  			filePermissions: []string{`(I)(Rc,S,X,RA)`},
   186  			dirPermissions:  []string{`(Rc,S,X,RA)`, `(OI)(CI)(IO)(GE)`},
   187  		},
   188  		{
   189  			name:            "ReadWrite",
   190  			access:          AccessMaskRead | AccessMaskWrite,
   191  			filePermissions: []string{`(I)(R,W)`},
   192  			dirPermissions:  []string{`(R,W)`, `(OI)(CI)(IO)(GR,GW)`},
   193  		},
   194  		{
   195  			name:            "ReadExecute",
   196  			access:          AccessMaskRead | AccessMaskExecute,
   197  			filePermissions: []string{`(I)(RX)`},
   198  			dirPermissions:  []string{`(RX)`, `(OI)(CI)(IO)(GR,GE)`},
   199  		},
   200  		{
   201  			name:            "WriteExecute",
   202  			access:          AccessMaskWrite | AccessMaskExecute,
   203  			filePermissions: []string{`(I)(W,Rc,X,RA)`},
   204  			dirPermissions:  []string{`(W,Rc,X,RA)`, `(OI)(CI)(IO)(GW,GE)`},
   205  		},
   206  		{
   207  			name:            "ReadWriteExecute",
   208  			access:          AccessMaskRead | AccessMaskWrite | AccessMaskExecute,
   209  			filePermissions: []string{`(I)(RX,W)`},
   210  			dirPermissions:  []string{`(RX,W)`, `(OI)(CI)(IO)(GR,GW,GE)`},
   211  		},
   212  		{
   213  			name:            "All",
   214  			access:          AccessMaskAll,
   215  			filePermissions: []string{`(I)(F)`},
   216  			dirPermissions:  []string{`(F)`, `(OI)(CI)(IO)(F)`},
   217  		}} {
   218  		t.Run(cfg.name, func(t *testing.T) {
   219  			dir := t.TempDir()
   220  			fd, err := os.Create(filepath.Join(dir, "test.txt"))
   221  			if err != nil {
   222  				t.Fatalf("failed to create temporary file: %s", err)
   223  			}
   224  			defer func() {
   225  				_ = fd.Close()
   226  				_ = os.Remove(fd.Name())
   227  			}()
   228  
   229  			if err := GrantVmGroupAccessWithMask(dir, cfg.access); err != nil {
   230  				t.Fatal(err)
   231  			}
   232  			verifyVMAccountDACLs(t, dir, cfg.dirPermissions)
   233  			verifyVMAccountDACLs(t, fd.Name(), cfg.filePermissions)
   234  		})
   235  	}
   236  }
   237  
   238  func TestGrantVmGroupAccess_Invalid_AccessMask(t *testing.T) {
   239  	t.Helper()
   240  	for _, access := range []accessMask{
   241  		0,          // no bits set
   242  		1,          // invalid bit set
   243  		0x02000001, // invalid extra bit set
   244  	} {
   245  		t.Run(fmt.Sprintf("AccessMask_0x%x", access), func(t *testing.T) {
   246  			dir := t.TempDir()
   247  			fd, err := os.Create(filepath.Join(dir, "test.txt"))
   248  			if err != nil {
   249  				t.Fatalf("failed to create temporary file: %s", err)
   250  			}
   251  			defer func() {
   252  				_ = fd.Close()
   253  				_ = os.Remove(fd.Name())
   254  			}()
   255  
   256  			if err := GrantVmGroupAccessWithMask(fd.Name(), access); err == nil {
   257  				t.Fatalf("expected an error for mask: %x", access)
   258  			}
   259  		})
   260  	}
   261  }
   262  
   263  func verifyVMAccountDACLs(t *testing.T, name string, permissions []string) {
   264  	t.Helper()
   265  	cmd := exec.Command("icacls", name)
   266  	outb, err := cmd.CombinedOutput()
   267  	if err != nil {
   268  		t.Fatal(err)
   269  	}
   270  	out := string(outb)
   271  
   272  	for _, p := range permissions {
   273  		// Avoid '(' and ')' being part of match groups
   274  		p = regexp.QuoteMeta(p)
   275  
   276  		nameToCheck := vmAccountName + ":" + p
   277  		sidToCheck := vmAccountSID + ":" + p
   278  
   279  		rxName := regexp.MustCompile(nameToCheck)
   280  		rxSID := regexp.MustCompile(sidToCheck)
   281  
   282  		matchesName := rxName.FindAllStringIndex(out, -1)
   283  		matchesSID := rxSID.FindAllStringIndex(out, -1)
   284  
   285  		if len(matchesName) != 1 && len(matchesSID) != 1 {
   286  			t.Fatalf("expected one match for %s or %s\n%s\n", nameToCheck, sidToCheck, out)
   287  		}
   288  	}
   289  }
   290  

View as plain text