diff --git a/windows/security_windows.go b/windows/security_windows.go index 6f7d2ac70..97651b5bd 100644 --- a/windows/security_windows.go +++ b/windows/security_windows.go @@ -894,7 +894,7 @@ type ACL struct { aclRevision byte sbz1 byte aclSize uint16 - aceCount uint16 + AceCount uint16 sbz2 uint16 } @@ -1087,6 +1087,27 @@ type EXPLICIT_ACCESS struct { Trustee TRUSTEE } +// https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-ace_header +type ACE_HEADER struct { + AceType uint8 + AceFlags uint8 + AceSize uint16 +} + +// https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-access_allowed_ace +type ACCESS_ALLOWED_ACE struct { + Header ACE_HEADER + Mask ACCESS_MASK + SidStart uint32 +} + +const ( + // Constants for AceType + // https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-ace_header + ACCESS_ALLOWED_ACE_TYPE = 0 + ACCESS_DENIED_ACE_TYPE = 1 +) + // This type is the union inside of TRUSTEE and must be created using one of the TrusteeValueFrom* functions. type TrusteeValue uintptr @@ -1158,6 +1179,7 @@ type OBJECTS_AND_NAME struct { //sys makeSelfRelativeSD(absoluteSD *SECURITY_DESCRIPTOR, selfRelativeSD *SECURITY_DESCRIPTOR, selfRelativeSDSize *uint32) (err error) = advapi32.MakeSelfRelativeSD //sys setEntriesInAcl(countExplicitEntries uint32, explicitEntries *EXPLICIT_ACCESS, oldACL *ACL, newACL **ACL) (ret error) = advapi32.SetEntriesInAclW +//sys GetAce(acl *ACL, aceIndex uint32, pAce **ACCESS_ALLOWED_ACE) (ret error) = advapi32.GetAce // Control returns the security descriptor control bits. func (sd *SECURITY_DESCRIPTOR) Control() (control SECURITY_DESCRIPTOR_CONTROL, revision uint32, err error) { diff --git a/windows/syscall_windows_test.go b/windows/syscall_windows_test.go index 665837907..770815411 100644 --- a/windows/syscall_windows_test.go +++ b/windows/syscall_windows_test.go @@ -359,6 +359,168 @@ func TestBuildSecurityDescriptor(t *testing.T) { } } +// getEntriesFromACL returns a list of explicit access control entries associated with the given ACL. +func getEntriesFromACL(acl *windows.ACL) (aces []*windows.ACCESS_ALLOWED_ACE, err error) { + aces = make([]*windows.ACCESS_ALLOWED_ACE, acl.AceCount) + + for i := uint16(0); i < acl.AceCount; i++ { + err = windows.GetAce(acl, uint32(i), &aces[i]) + if err != nil { + return nil, err + } + } + + return aces, nil +} + +func TestGetACEsFromACL(t *testing.T) { + // Create a temporary file to set ACLs on and test getting the ACEs from the ACL. + f, err := os.CreateTemp("", "foo.lish") + defer os.Remove(f.Name()) + + if err = f.Close(); err != nil { + t.Fatal(err) + } + + // Well-known SID Strings: + // https://support.microsoft.com/en-us/help/243330/well-known-security-identifiers-in-windows-operating-systems + ownerSid, err := windows.StringToSid("S-1-3-2") + if err != nil { + t.Fatal(err) + } + groupSid, err := windows.StringToSid("S-1-3-3") + if err != nil { + t.Fatal(err) + } + worldSid, err := windows.StringToSid("S-1-1-0") + if err != nil { + t.Fatal(err) + } + + ownerPermissions := windows.ACCESS_MASK(windows.GENERIC_ALL) + groupPermissions := windows.ACCESS_MASK(windows.GENERIC_READ | windows.GENERIC_EXECUTE) + worldPermissions := windows.ACCESS_MASK(windows.GENERIC_READ) + + access := []windows.EXPLICIT_ACCESS{ + { + AccessPermissions: ownerPermissions, + AccessMode: windows.GRANT_ACCESS, + Trustee: windows.TRUSTEE{ + TrusteeForm: windows.TRUSTEE_IS_SID, + TrusteeValue: windows.TrusteeValueFromSID(ownerSid), + }, + }, + { + AccessPermissions: groupPermissions, + AccessMode: windows.GRANT_ACCESS, + Trustee: windows.TRUSTEE{ + TrusteeForm: windows.TRUSTEE_IS_SID, + TrusteeType: windows.TRUSTEE_IS_GROUP, + TrusteeValue: windows.TrusteeValueFromSID(groupSid), + }, + }, + { + AccessPermissions: worldPermissions, + AccessMode: windows.GRANT_ACCESS, + Trustee: windows.TRUSTEE{ + TrusteeForm: windows.TRUSTEE_IS_SID, + TrusteeType: windows.TRUSTEE_IS_GROUP, + TrusteeValue: windows.TrusteeValueFromSID(worldSid), + }, + }, + } + + acl, err := windows.ACLFromEntries(access, nil) + if err != nil { + t.Fatal(err) + } + + // Set new ACL. + err = windows.SetNamedSecurityInfo( + f.Name(), + windows.SE_FILE_OBJECT, + windows.DACL_SECURITY_INFORMATION|windows.PROTECTED_DACL_SECURITY_INFORMATION, + nil, + nil, + acl, + nil, + ) + if err != nil { + t.Fatal(err) + } + + descriptor, err := windows.GetNamedSecurityInfo( + f.Name(), + windows.SE_FILE_OBJECT, + windows.DACL_SECURITY_INFORMATION|windows.PROTECTED_DACL_SECURITY_INFORMATION|windows.OWNER_SECURITY_INFORMATION|windows.GROUP_SECURITY_INFORMATION, + ) + if err != nil { + t.Fatal(err) + } + + dacl, _, err := descriptor.DACL() + if err != nil { + t.Fatal(err) + } + + owner, _, err := descriptor.Owner() + if err != nil { + t.Fatal(err) + } + + group, _, err := descriptor.Group() + if err != nil { + t.Fatal(err) + } + + entries, err := getEntriesFromACL(dacl) + if err != nil { + t.Fatal(err) + } + + if len(entries) != 3 { + t.Fatalf("Expected newly set ACL to only have 3 entries.") + } + + // https://docs.microsoft.com/en-us/windows/win32/fileio/file-access-rights-constants + read := uint32(windows.FILE_READ_DATA | windows.FILE_READ_ATTRIBUTES) + write := uint32(windows.FILE_WRITE_DATA | windows.FILE_APPEND_DATA | windows.FILE_WRITE_ATTRIBUTES | windows.FILE_WRITE_EA) + execute := uint32(windows.FILE_READ_DATA | windows.FILE_EXECUTE) + + // Check the set ACEs. We should have the equivalent of 754. + for _, entry := range entries { + mask := uint32(entry.Mask) + actual := 0 + + if mask&read == read { + actual |= 4 + } + if mask&write == write { + actual |= 2 + } + if mask&execute == execute { + actual |= 1 + } + + entrySid := (*windows.SID)(unsafe.Pointer(&entry.SidStart)) + if owner.Equals(entrySid) { + if actual != 7 { + t.Fatalf("Expected owner to have FullAccess permissions.") + } + } else if group.Equals(entrySid) { + if actual != 5 { + t.Fatalf("Expected group to have only Read and Execute permissions.") + } + } else if worldSid.Equals(entrySid) { + if actual != 4 { + t.Fatalf("Expected the World to have only Read permissions.") + } + } else { + t.Fatalf("Unexpected SID in ACEs: %s", entrySid.String()) + } + } +} + func TestGetDiskFreeSpaceEx(t *testing.T) { cwd, err := windows.UTF16PtrFromString(".") if err != nil { diff --git a/windows/zsyscall_windows.go b/windows/zsyscall_windows.go index 9f73df75b..eba761018 100644 --- a/windows/zsyscall_windows.go +++ b/windows/zsyscall_windows.go @@ -91,6 +91,7 @@ var ( procEnumServicesStatusExW = modadvapi32.NewProc("EnumServicesStatusExW") procEqualSid = modadvapi32.NewProc("EqualSid") procFreeSid = modadvapi32.NewProc("FreeSid") + procGetAce = modadvapi32.NewProc("GetAce") procGetLengthSid = modadvapi32.NewProc("GetLengthSid") procGetNamedSecurityInfoW = modadvapi32.NewProc("GetNamedSecurityInfoW") procGetSecurityDescriptorControl = modadvapi32.NewProc("GetSecurityDescriptorControl") @@ -1224,6 +1225,14 @@ func setEntriesInAcl(countExplicitEntries uint32, explicitEntries *EXPLICIT_ACCE return } +func GetAce(acl *ACL, aceIndex uint32, pAce **ACCESS_ALLOWED_ACE) (ret error) { + r0, _, _ := syscall.Syscall(procGetAce.Addr(), 3, uintptr(unsafe.Pointer(acl)), uintptr(aceIndex), uintptr(unsafe.Pointer(pAce))) + if r0 == 0 { + ret = GetLastError() + } + return +} + func SetKernelObjectSecurity(handle Handle, securityInformation SECURITY_INFORMATION, securityDescriptor *SECURITY_DESCRIPTOR) (err error) { r1, _, e1 := syscall.Syscall(procSetKernelObjectSecurity.Addr(), 3, uintptr(handle), uintptr(securityInformation), uintptr(unsafe.Pointer(securityDescriptor))) if r1 == 0 {