github.com/Serizao/go-winio@v0.0.0-20230906082528-f02f7f4ad6e8/pkg/security/grantvmgroupaccess.go (about)

     1  //go:build windows
     2  // +build windows
     3  
     4  package security
     5  
     6  import (
     7  	"fmt"
     8  	"os"
     9  	"unsafe"
    10  
    11  	"golang.org/x/sys/windows"
    12  )
    13  
    14  type (
    15  	accessMask          uint32
    16  	accessMode          uint32
    17  	desiredAccess       uint32
    18  	inheritMode         uint32
    19  	objectType          uint32
    20  	shareMode           uint32
    21  	securityInformation uint32
    22  	trusteeForm         uint32
    23  	trusteeType         uint32
    24  
    25  	explicitAccess struct {
    26  		accessPermissions accessMask
    27  		accessMode        accessMode
    28  		inheritance       inheritMode
    29  		trustee           trustee
    30  	}
    31  
    32  	trustee struct {
    33  		multipleTrustee          *trustee
    34  		multipleTrusteeOperation int32
    35  		trusteeForm              trusteeForm
    36  		trusteeType              trusteeType
    37  		name                     uintptr
    38  	}
    39  )
    40  
    41  const (
    42  	accessMaskDesiredPermission accessMask = 1 << 31 // GENERIC_READ
    43  
    44  	accessModeGrant accessMode = 1
    45  
    46  	desiredAccessReadControl desiredAccess = 0x20000
    47  	desiredAccessWriteDac    desiredAccess = 0x40000
    48  
    49  	//cspell:disable-next-line
    50  	gvmga = "GrantVmGroupAccess:"
    51  
    52  	inheritModeNoInheritance                  inheritMode = 0x0
    53  	inheritModeSubContainersAndObjectsInherit inheritMode = 0x3
    54  
    55  	objectTypeFileObject objectType = 0x1
    56  
    57  	securityInformationDACL securityInformation = 0x4
    58  
    59  	shareModeRead  shareMode = 0x1
    60  	shareModeWrite shareMode = 0x2
    61  
    62  	sidVMGroup = "S-1-5-83-0"
    63  
    64  	trusteeFormIsSID trusteeForm = 0
    65  
    66  	trusteeTypeWellKnownGroup trusteeType = 5
    67  )
    68  
    69  // GrantVMGroupAccess sets the DACL for a specified file or directory to
    70  // include Grant ACE entries for the VM Group SID. This is a golang re-
    71  // implementation of the same function in vmcompute, just not exported in
    72  // RS5. Which kind of sucks. Sucks a lot :/
    73  //
    74  //revive:disable-next-line:var-naming VM, not Vm
    75  func GrantVmGroupAccess(name string) error {
    76  	// Stat (to determine if `name` is a directory).
    77  	s, err := os.Stat(name)
    78  	if err != nil {
    79  		return fmt.Errorf("%s os.Stat %s: %w", gvmga, name, err)
    80  	}
    81  
    82  	// Get a handle to the file/directory. Must defer Close on success.
    83  	fd, err := createFile(name, s.IsDir())
    84  	if err != nil {
    85  		return err // Already wrapped
    86  	}
    87  	defer windows.CloseHandle(fd) //nolint:errcheck
    88  
    89  	// Get the current DACL and Security Descriptor. Must defer LocalFree on success.
    90  	ot := objectTypeFileObject
    91  	si := securityInformationDACL
    92  	sd := uintptr(0)
    93  	origDACL := uintptr(0)
    94  	if err := getSecurityInfo(fd, uint32(ot), uint32(si), nil, nil, &origDACL, nil, &sd); err != nil {
    95  		return fmt.Errorf("%s GetSecurityInfo %s: %w", gvmga, name, err)
    96  	}
    97  	defer windows.LocalFree(windows.Handle(sd)) //nolint:errcheck
    98  
    99  	// Generate a new DACL which is the current DACL with the required ACEs added.
   100  	// Must defer LocalFree on success.
   101  	newDACL, err := generateDACLWithAcesAdded(name, s.IsDir(), origDACL)
   102  	if err != nil {
   103  		return err // Already wrapped
   104  	}
   105  	defer windows.LocalFree(windows.Handle(newDACL)) //nolint:errcheck
   106  
   107  	// And finally use SetSecurityInfo to apply the updated DACL.
   108  	if err := setSecurityInfo(fd, uint32(ot), uint32(si), uintptr(0), uintptr(0), newDACL, uintptr(0)); err != nil {
   109  		return fmt.Errorf("%s SetSecurityInfo %s: %w", gvmga, name, err)
   110  	}
   111  
   112  	return nil
   113  }
   114  
   115  // createFile is a helper function to call [Nt]CreateFile to get a handle to
   116  // the file or directory.
   117  func createFile(name string, isDir bool) (windows.Handle, error) {
   118  	namep, err := windows.UTF16FromString(name)
   119  	if err != nil {
   120  		return windows.InvalidHandle, fmt.Errorf("could not convernt name to UTF-16: %w", err)
   121  	}
   122  	da := uint32(desiredAccessReadControl | desiredAccessWriteDac)
   123  	sm := uint32(shareModeRead | shareModeWrite)
   124  	fa := uint32(windows.FILE_ATTRIBUTE_NORMAL)
   125  	if isDir {
   126  		fa |= windows.FILE_FLAG_BACKUP_SEMANTICS
   127  	}
   128  	fd, err := windows.CreateFile(&namep[0], da, sm, nil, windows.OPEN_EXISTING, fa, 0)
   129  	if err != nil {
   130  		return windows.InvalidHandle, fmt.Errorf("%s windows.CreateFile %s: %w", gvmga, name, err)
   131  	}
   132  	return fd, nil
   133  }
   134  
   135  // generateDACLWithAcesAdded generates a new DACL with the two needed ACEs added.
   136  // The caller is responsible for LocalFree of the returned DACL on success.
   137  func generateDACLWithAcesAdded(name string, isDir bool, origDACL uintptr) (uintptr, error) {
   138  	// Generate pointers to the SIDs based on the string SIDs
   139  	sid, err := windows.StringToSid(sidVMGroup)
   140  	if err != nil {
   141  		return 0, fmt.Errorf("%s windows.StringToSid %s %s: %w", gvmga, name, sidVMGroup, err)
   142  	}
   143  
   144  	inheritance := inheritModeNoInheritance
   145  	if isDir {
   146  		inheritance = inheritModeSubContainersAndObjectsInherit
   147  	}
   148  
   149  	eaArray := []explicitAccess{
   150  		{
   151  			accessPermissions: accessMaskDesiredPermission,
   152  			accessMode:        accessModeGrant,
   153  			inheritance:       inheritance,
   154  			trustee: trustee{
   155  				trusteeForm: trusteeFormIsSID,
   156  				trusteeType: trusteeTypeWellKnownGroup,
   157  				name:        uintptr(unsafe.Pointer(sid)),
   158  			},
   159  		},
   160  	}
   161  
   162  	modifiedDACL := uintptr(0)
   163  	if err := setEntriesInAcl(uintptr(uint32(1)), uintptr(unsafe.Pointer(&eaArray[0])), origDACL, &modifiedDACL); err != nil {
   164  		return 0, fmt.Errorf("%s SetEntriesInAcl %s: %w", gvmga, name, err)
   165  	}
   166  
   167  	return modifiedDACL, nil
   168  }