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 {