Skip to content

Commit c973250

Browse files
committed
windows: Adds GetAce syscall wrapper
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. Reference: 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
1 parent 27dc90b commit c973250

File tree

3 files changed

+202
-0
lines changed

3 files changed

+202
-0
lines changed

windows/security_windows.go

+43
Original file line numberDiff line numberDiff line change
@@ -1086,6 +1086,27 @@ type EXPLICIT_ACCESS struct {
10861086
Trustee TRUSTEE
10871087
}
10881088

1089+
// https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-ace_header
1090+
type ACE_HEADER struct {
1091+
AceType uint8
1092+
AceFlags uint8
1093+
AceSize uint16
1094+
}
1095+
1096+
// https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-access_allowed_ace
1097+
type ACCESS_ALLOWED_ACE struct {
1098+
Header ACE_HEADER
1099+
Mask ACCESS_MASK
1100+
Sid SID
1101+
}
1102+
1103+
const (
1104+
// Constants for AceType
1105+
// https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-ace_header
1106+
ACCESS_ALLOWED_ACE_TYPE = 0
1107+
ACCESS_DENIED_ACE_TYPE = 1
1108+
)
1109+
10891110
// This type is the union inside of TRUSTEE and must be created using one of the TrusteeValueFrom* functions.
10901111
type TrusteeValue uintptr
10911112

@@ -1157,6 +1178,7 @@ type OBJECTS_AND_NAME struct {
11571178
//sys makeSelfRelativeSD(absoluteSD *SECURITY_DESCRIPTOR, selfRelativeSD *SECURITY_DESCRIPTOR, selfRelativeSDSize *uint32) (err error) = advapi32.MakeSelfRelativeSD
11581179

11591180
//sys setEntriesInAcl(countExplicitEntries uint32, explicitEntries *EXPLICIT_ACCESS, oldACL *ACL, newACL **ACL) (ret error) = advapi32.SetEntriesInAclW
1181+
//sys getAceFromAcl(acl *ACL, aceIndex uint32, pAce **ACCESS_ALLOWED_ACE) (ret error) = advapi32.GetAce
11601182

11611183
// Control returns the security descriptor control bits.
11621184
func (sd *SECURITY_DESCRIPTOR) Control() (control SECURITY_DESCRIPTOR_CONTROL, revision uint32, err error) {
@@ -1433,3 +1455,24 @@ func ACLFromEntries(explicitEntries []EXPLICIT_ACCESS, mergedACL *ACL) (acl *ACL
14331455
copy(aclBytes, (*[(1 << 31) - 1]byte)(unsafe.Pointer(winHeapACL))[:len(aclBytes):len(aclBytes)])
14341456
return (*ACL)(unsafe.Pointer(&aclBytes[0])), nil
14351457
}
1458+
1459+
// GetEntriesFromACL returns a list of explicit access control entries associated with the given ACL.
1460+
// https://learn.microsoft.com/en-us/windows/win32/api/securitybaseapi/nf-securitybaseapi-getace
1461+
func GetEntriesFromACL(acl *ACL) (aces []*ACCESS_ALLOWED_ACE, err error) {
1462+
aces = make([]*ACCESS_ALLOWED_ACE, acl.aceCount)
1463+
var ace *ACCESS_ALLOWED_ACE
1464+
1465+
for i := uint16(0); i < acl.aceCount; i++ {
1466+
// aceCount is a word, but aceIndex is a double word.
1467+
err = getAceFromAcl(acl, uint32(i), &ace)
1468+
if err != nil {
1469+
return []*ACCESS_ALLOWED_ACE{}, err
1470+
}
1471+
1472+
aceBytes := make([]byte, ace.Header.AceSize)
1473+
copy(aceBytes, (*[(1 << 31) - 1]byte)(unsafe.Pointer(ace))[:len(aceBytes):len(aceBytes)])
1474+
aces[i] = (*ACCESS_ALLOWED_ACE)(unsafe.Pointer(&aceBytes[0]))
1475+
}
1476+
1477+
return aces, nil
1478+
}

windows/syscall_windows_test.go

+150
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,156 @@ func TestBuildSecurityDescriptor(t *testing.T) {
359359
}
360360
}
361361

362+
func TestGetEntriesFromACL(t *testing.T) {
363+
// Create a temporary file to set ACLs on and test getting the ACEs from the ACL.
364+
f, err := os.CreateTemp("", "foo.lish")
365+
defer os.Remove(f.Name())
366+
367+
f.Close()
368+
369+
// Well-known SID Strings:
370+
// https://support.microsoft.com/en-us/help/243330/well-known-security-identifiers-in-windows-operating-systems
371+
ownerSid, err := windows.StringToSid("S-1-3-2")
372+
if err != nil {
373+
t.Fatal(err)
374+
}
375+
groupSid, err := windows.StringToSid("S-1-3-3")
376+
if err != nil {
377+
t.Fatal(err)
378+
}
379+
worldSid, err := windows.StringToSid("S-1-1-0")
380+
if err != nil {
381+
t.Fatal(err)
382+
}
383+
384+
ownerPermissions := windows.ACCESS_MASK(windows.GENERIC_ALL)
385+
groupPermissions := windows.ACCESS_MASK(windows.GENERIC_READ | windows.GENERIC_EXECUTE)
386+
worldPermissions := windows.ACCESS_MASK(windows.GENERIC_READ)
387+
388+
access := []windows.EXPLICIT_ACCESS{
389+
{
390+
AccessPermissions: ownerPermissions,
391+
AccessMode: windows.GRANT_ACCESS,
392+
Trustee: windows.TRUSTEE{
393+
TrusteeForm: windows.TRUSTEE_IS_SID,
394+
TrusteeValue: windows.TrusteeValueFromSID(ownerSid),
395+
},
396+
},
397+
{
398+
AccessPermissions: groupPermissions,
399+
AccessMode: windows.GRANT_ACCESS,
400+
Trustee: windows.TRUSTEE{
401+
TrusteeForm: windows.TRUSTEE_IS_SID,
402+
TrusteeType: windows.TRUSTEE_IS_GROUP,
403+
TrusteeValue: windows.TrusteeValueFromSID(groupSid),
404+
},
405+
},
406+
{
407+
AccessPermissions: worldPermissions,
408+
AccessMode: windows.GRANT_ACCESS,
409+
Trustee: windows.TRUSTEE{
410+
TrusteeForm: windows.TRUSTEE_IS_SID,
411+
TrusteeType: windows.TRUSTEE_IS_GROUP,
412+
TrusteeValue: windows.TrusteeValueFromSID(worldSid),
413+
},
414+
},
415+
}
416+
417+
acl, err := windows.ACLFromEntries(access, nil)
418+
if err != nil {
419+
t.Fatal(err)
420+
}
421+
422+
// Set new ACL.
423+
err = windows.SetNamedSecurityInfo(
424+
f.Name(),
425+
windows.SE_FILE_OBJECT,
426+
windows.DACL_SECURITY_INFORMATION|windows.PROTECTED_DACL_SECURITY_INFORMATION,
427+
nil,
428+
nil,
429+
acl,
430+
nil,
431+
)
432+
if err != nil {
433+
t.Fatal(err)
434+
}
435+
436+
descriptor, err := windows.GetNamedSecurityInfo(
437+
f.Name(),
438+
windows.SE_FILE_OBJECT,
439+
windows.DACL_SECURITY_INFORMATION|windows.PROTECTED_DACL_SECURITY_INFORMATION|windows.OWNER_SECURITY_INFORMATION|windows.GROUP_SECURITY_INFORMATION,
440+
)
441+
if err != nil {
442+
t.Fatal(err)
443+
}
444+
445+
dacl, _, err := descriptor.DACL()
446+
if err != nil {
447+
t.Fatal(err)
448+
}
449+
450+
owner, _, err := descriptor.Owner()
451+
if err != nil {
452+
t.Fatal(err)
453+
}
454+
455+
group, _, err := descriptor.Group()
456+
if err != nil {
457+
t.Fatal(err)
458+
}
459+
460+
entries, err := windows.GetEntriesFromACL(dacl)
461+
if err != nil {
462+
t.Fatal(err)
463+
}
464+
465+
if len(entries) != 3 {
466+
t.Fatalf("Expected newly set ACL to only have 3 entries.")
467+
}
468+
469+
// https://docs.microsoft.com/en-us/windows/win32/fileio/file-access-rights-constants
470+
// read = read data | read attributes
471+
read := 0x0001 | 0x0080
472+
473+
// write = write data | append data | write attributes | write EA
474+
write := 0x0002 | 0x0004 | 0x0100 | 0x0010
475+
476+
// execute = read data | file execute
477+
execute := 0x0001 | 0x0020
478+
479+
// Check the set ACEs. We should have the equivalent of 754.
480+
for _, entry := range entries {
481+
mask := int(entry.Mask)
482+
actual := 0
483+
484+
if mask&read == read {
485+
actual |= 4
486+
}
487+
if mask&write == write {
488+
actual |= 2
489+
}
490+
if mask&execute == execute {
491+
actual |= 1
492+
}
493+
494+
if owner.Equals(&entry.Sid) {
495+
if actual != 7 {
496+
t.Fatalf("Expected owner to have FullAccess permissions.")
497+
}
498+
} else if group.Equals(&entry.Sid) {
499+
if actual != 5 {
500+
t.Fatalf("Expected group to have only Read and Execute permissions.")
501+
}
502+
} else if worldSid.Equals(&entry.Sid) {
503+
if actual != 4 {
504+
t.Fatalf("Expected the World to have only Read permissions.")
505+
}
506+
} else {
507+
t.Fatalf("Unexpected SID in ACEs: %s", (&entry.Sid).String())
508+
}
509+
}
510+
}
511+
362512
func TestGetDiskFreeSpaceEx(t *testing.T) {
363513
cwd, err := windows.UTF16PtrFromString(".")
364514
if err != nil {

windows/zsyscall_windows.go

+9
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)