Skip to content

Commit

Permalink
adds Windows GetAce syscall wrapper
Browse files Browse the repository at this point in the history
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
  • Loading branch information
claudiubelu committed Apr 16, 2024
1 parent 27dc90b commit b911428
Show file tree
Hide file tree
Showing 3 changed files with 202 additions and 0 deletions.
43 changes: 43 additions & 0 deletions windows/security_windows.go
Expand Up @@ -1086,6 +1086,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
Sid SID
}

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

Expand Down Expand Up @@ -1157,6 +1178,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 getAceFromAcl(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) {
Expand Down Expand Up @@ -1433,3 +1455,24 @@ func ACLFromEntries(explicitEntries []EXPLICIT_ACCESS, mergedACL *ACL) (acl *ACL
copy(aclBytes, (*[(1 << 31) - 1]byte)(unsafe.Pointer(winHeapACL))[:len(aclBytes):len(aclBytes)])
return (*ACL)(unsafe.Pointer(&aclBytes[0])), nil
}

// GetEntriesFromACL returns a list of explicit access control entries associated with the given ACL.
// https://learn.microsoft.com/en-us/windows/win32/api/securitybaseapi/nf-securitybaseapi-getace
func GetEntriesFromACL(acl *ACL) (aces []*ACCESS_ALLOWED_ACE, err error) {
aces = make([]*ACCESS_ALLOWED_ACE, acl.aceCount)
var ace *ACCESS_ALLOWED_ACE

for i := uint16(0); i < acl.aceCount; i++ {
// aceCount is a word, but aceIndex is a double word.
err = getAceFromAcl(acl, uint32(i), &ace)
if err != nil {
return []*ACCESS_ALLOWED_ACE{}, err
}

aceBytes := make([]byte, ace.Header.AceSize)
copy(aceBytes, (*[(1 << 31) - 1]byte)(unsafe.Pointer(ace))[:len(aceBytes):len(aceBytes)])
aces[i] = (*ACCESS_ALLOWED_ACE)(unsafe.Pointer(&aceBytes[0]))
}

return aces, nil
}
150 changes: 150 additions & 0 deletions windows/syscall_windows_test.go
Expand Up @@ -359,6 +359,156 @@ func TestBuildSecurityDescriptor(t *testing.T) {
}
}

func TestGetEntriesFromACL(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())

f.Close()

// 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 := windows.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 = read data | read attributes
read := 0x0001 | 0x0080

// write = write data | append data | write attributes | write EA
write := 0x0002 | 0x0004 | 0x0100 | 0x0010

// execute = read data | file execute
execute := 0x0001 | 0x0020

// Check the set ACEs. We should have the equivalent of 754.
for _, entry := range entries {
mask := int(entry.Mask)
actual := 0

if mask&read == read {
actual |= 4
}
if mask&write == write {
actual |= 2
}
if mask&execute == execute {
actual |= 1
}

if owner.Equals(&entry.Sid) {
if actual != 7 {
t.Fatalf("Expected owner to have FullAccess permissions.")
}
} else if group.Equals(&entry.Sid) {
if actual != 5 {
t.Fatalf("Expected group to have only Read and Execute permissions.")
}
} else if worldSid.Equals(&entry.Sid) {
if actual != 4 {
t.Fatalf("Expected the World to have only Read permissions.")
}
} else {
t.Fatalf("Unexpected SID in ACEs: %s", (&entry.Sid).String())
}
}
}

func TestGetDiskFreeSpaceEx(t *testing.T) {
cwd, err := windows.UTF16PtrFromString(".")
if err != nil {
Expand Down
9 changes: 9 additions & 0 deletions windows/zsyscall_windows.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit b911428

Please sign in to comment.