Skip to content

Commit

Permalink
windows: add GetAce Windows API
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

Change-Id: I98306ff7e947e586a58d563d364169a2555492f4
GitHub-Last-Rev: d14ca7f
GitHub-Pull-Request: #191
Reviewed-on: https://go-review.googlesource.com/c/sys/+/578976
LUCI-TryBot-Result: Go LUCI <[email protected]>
Reviewed-by: Damien Neil <[email protected]>
Reviewed-by: David Chase <[email protected]>
Reviewed-by: Alex Brainman <[email protected]>
  • Loading branch information
claudiubelu authored and neild committed Jun 24, 2024
1 parent 348425a commit 7670087
Show file tree
Hide file tree
Showing 3 changed files with 194 additions and 1 deletion.
24 changes: 23 additions & 1 deletion windows/security_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -894,7 +894,7 @@ type ACL struct {
aclRevision byte
sbz1 byte
aclSize uint16
aceCount uint16
AceCount uint16
sbz2 uint16
}

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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) {
Expand Down
162 changes: 162 additions & 0 deletions windows/syscall_windows_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
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 7670087

Please sign in to comment.