windows: add GetAce Windows API

GetAce obtains a pointer to an access control entry (ACE) in an
discretionary access control list (DACL), which controls access to
an object.

Adds the ACE_HEADER and ACCESS_ALLOWED_ACE structs.
Adds GetEntriesFromACL function which returns an array of ACEs from the
given ACL if no errors have been encountered.

References:

- https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-ace_header
- https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-access_allowed_ace
- https://learn.microsoft.com/en-us/windows/win32/api/securitybaseapi/nf-securitybaseapi-getace

Fixes golang/go#66850

Change-Id: I98306ff7e947e586a58d563d364169a2555492f4
GitHub-Last-Rev: d14ca7fb0b8103294fd7c09ee3469ff6f9556508
GitHub-Pull-Request: golang/sys#191
Reviewed-on: https://go-review.googlesource.com/c/sys/+/578976
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Damien Neil <dneil@google.com>
Reviewed-by: David Chase <drchase@google.com>
Reviewed-by: Alex Brainman <alex.brainman@gmail.com>
diff --git a/windows/security_windows.go b/windows/security_windows.go
index 6f7d2ac..97651b5 100644
--- a/windows/security_windows.go
+++ b/windows/security_windows.go
@@ -894,7 +894,7 @@
 	aclRevision byte
 	sbz1        byte
 	aclSize     uint16
-	aceCount    uint16
+	AceCount    uint16
 	sbz2        uint16
 }
 
@@ -1087,6 +1087,27 @@
 	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 @@
 //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 6658379..7708154 100644
--- a/windows/syscall_windows_test.go
+++ b/windows/syscall_windows_test.go
@@ -359,6 +359,168 @@
 	}
 }
 
+// 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 9f73df7..eba7610 100644
--- a/windows/zsyscall_windows.go
+++ b/windows/zsyscall_windows.go
@@ -91,6 +91,7 @@
 	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 @@
 	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 {