| //go:build windows | |
| // +build windows | |
| package security | |
| import ( | |
| "fmt" | |
| "os" | |
| "syscall" | |
| "unsafe" | |
| ) | |
| type ( | |
| accessMask uint32 | |
| accessMode uint32 | |
| desiredAccess uint32 | |
| inheritMode uint32 | |
| objectType uint32 | |
| shareMode uint32 | |
| securityInformation uint32 | |
| trusteeForm uint32 | |
| trusteeType uint32 | |
| ) | |
| type explicitAccess struct { | |
| accessPermissions accessMask | |
| accessMode accessMode | |
| inheritance inheritMode | |
| trustee trustee | |
| } | |
| type trustee struct { | |
| multipleTrustee *trustee | |
| multipleTrusteeOperation int32 | |
| trusteeForm trusteeForm | |
| trusteeType trusteeType | |
| name uintptr | |
| } | |
| const ( | |
| AccessMaskNone accessMask = 0 | |
| AccessMaskRead accessMask = 1 << 31 // GENERIC_READ | |
| AccessMaskWrite accessMask = 1 << 30 // GENERIC_WRITE | |
| AccessMaskExecute accessMask = 1 << 29 // GENERIC_EXECUTE | |
| AccessMaskAll accessMask = 1 << 28 // GENERIC_ALL | |
| accessMaskDesiredPermission = AccessMaskRead | |
| accessModeGrant accessMode = 1 | |
| desiredAccessReadControl desiredAccess = 0x20000 | |
| desiredAccessWriteDac desiredAccess = 0x40000 | |
| gvmga = "GrantVmGroupAccess:" | |
| inheritModeNoInheritance inheritMode = 0x0 | |
| inheritModeSubContainersAndObjectsInherit inheritMode = 0x3 | |
| objectTypeFileObject objectType = 0x1 | |
| securityInformationDACL securityInformation = 0x4 | |
| shareModeRead shareMode = 0x1 | |
| shareModeWrite shareMode = 0x2 | |
| sidVMGroup = "S-1-5-83-0" | |
| trusteeFormIsSid trusteeForm = 0 | |
| trusteeTypeWellKnownGroup trusteeType = 5 | |
| ) | |
| // GrantVmGroupAccess sets the DACL for a specified file or directory to | |
| // include Grant ACE entries for the VM Group SID. This is a golang re- | |
| // implementation of the same function in vmcompute, just not exported in | |
| // RS5. Which kind of sucks. Sucks a lot :/ | |
| func GrantVmGroupAccess(name string) error { //nolint:staticcheck // ST1003: ALL_CAPS | |
| return GrantVmGroupAccessWithMask(name, accessMaskDesiredPermission) | |
| } | |
| // GrantVmGroupAccessWithMask sets the desired DACL for a specified file or | |
| // directory. | |
| func GrantVmGroupAccessWithMask(name string, access accessMask) error { //nolint:staticcheck // ST1003: ALL_CAPS | |
| if access == 0 || access<<4 != 0 { | |
| return fmt.Errorf("invalid access mask: 0x%08x", access) | |
| } | |
| // Stat (to determine if `name` is a directory). | |
| s, err := os.Stat(name) | |
| if err != nil { | |
| return fmt.Errorf("%s os.Stat %s: %w", gvmga, name, err) | |
| } | |
| // Get a handle to the file/directory. Must defer Close on success. | |
| fd, err := createFile(name, s.IsDir()) | |
| if err != nil { | |
| return err // Already wrapped | |
| } | |
| defer func() { | |
| _ = syscall.CloseHandle(fd) | |
| }() | |
| // Get the current DACL and Security Descriptor. Must defer LocalFree on success. | |
| ot := objectTypeFileObject | |
| si := securityInformationDACL | |
| sd := uintptr(0) | |
| origDACL := uintptr(0) | |
| if err := getSecurityInfo(fd, uint32(ot), uint32(si), nil, nil, &origDACL, nil, &sd); err != nil { | |
| return fmt.Errorf("%s GetSecurityInfo %s: %w", gvmga, name, err) | |
| } | |
| defer func() { | |
| _, _ = syscall.LocalFree((syscall.Handle)(unsafe.Pointer(sd))) | |
| }() | |
| // Generate a new DACL which is the current DACL with the required ACEs added. | |
| // Must defer LocalFree on success. | |
| newDACL, err := generateDACLWithAcesAdded(name, s.IsDir(), access, origDACL) | |
| if err != nil { | |
| return err // Already wrapped | |
| } | |
| defer func() { | |
| _, _ = syscall.LocalFree((syscall.Handle)(unsafe.Pointer(newDACL))) | |
| }() | |
| // And finally use SetSecurityInfo to apply the updated DACL. | |
| if err := setSecurityInfo(fd, uint32(ot), uint32(si), uintptr(0), uintptr(0), newDACL, uintptr(0)); err != nil { | |
| return fmt.Errorf("%s SetSecurityInfo %s: %w", gvmga, name, err) | |
| } | |
| return nil | |
| } | |
| // createFile is a helper function to call [Nt]CreateFile to get a handle to | |
| // the file or directory. | |
| func createFile(name string, isDir bool) (syscall.Handle, error) { | |
| namep, err := syscall.UTF16FromString(name) | |
| if err != nil { | |
| return 0, fmt.Errorf("syscall.UTF16FromString %s: %w", name, err) | |
| } | |
| da := uint32(desiredAccessReadControl | desiredAccessWriteDac) | |
| sm := uint32(shareModeRead | shareModeWrite) | |
| fa := uint32(syscall.FILE_ATTRIBUTE_NORMAL) | |
| if isDir { | |
| fa = uint32(fa | syscall.FILE_FLAG_BACKUP_SEMANTICS) | |
| } | |
| fd, err := syscall.CreateFile(&namep[0], da, sm, nil, syscall.OPEN_EXISTING, fa, 0) | |
| if err != nil { | |
| return 0, fmt.Errorf("%s syscall.CreateFile %s: %w", gvmga, name, err) | |
| } | |
| return fd, nil | |
| } | |
| // generateDACLWithAcesAdded generates a new DACL with the two needed ACEs added. | |
| // The caller is responsible for LocalFree of the returned DACL on success. | |
| func generateDACLWithAcesAdded(name string, isDir bool, desiredAccess accessMask, origDACL uintptr) (uintptr, error) { | |
| // Generate pointers to the SIDs based on the string SIDs | |
| sid, err := syscall.StringToSid(sidVMGroup) | |
| if err != nil { | |
| return 0, fmt.Errorf("%s syscall.StringToSid %s %s: %w", gvmga, name, sidVMGroup, err) | |
| } | |
| inheritance := inheritModeNoInheritance | |
| if isDir { | |
| inheritance = inheritModeSubContainersAndObjectsInherit | |
| } | |
| eaArray := []explicitAccess{ | |
| { | |
| accessPermissions: desiredAccess, | |
| accessMode: accessModeGrant, | |
| inheritance: inheritance, | |
| trustee: trustee{ | |
| trusteeForm: trusteeFormIsSid, | |
| trusteeType: trusteeTypeWellKnownGroup, | |
| name: uintptr(unsafe.Pointer(sid)), | |
| }, | |
| }, | |
| } | |
| modifiedDACL := uintptr(0) | |
| if err := setEntriesInAcl(uintptr(uint32(1)), uintptr(unsafe.Pointer(&eaArray[0])), origDACL, &modifiedDACL); err != nil { | |
| return 0, fmt.Errorf("%s SetEntriesInAcl %s: %w", gvmga, name, err) | |
| } | |
| return modifiedDACL, nil | |
| } |