Created
November 27, 2024 18:02
-
-
Save rolandshoemaker/21a04f56111c61da439f5b5a8def3275 to your computer and use it in GitHub Desktop.
acl.cpp
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <windows.h> | |
#include <stdio.h> | |
#include <aclapi.h> | |
#include <tchar.h> | |
#include <sddl.h> | |
#define S_IRWXU 0000700 /* RWX mask for owner */ | |
#define S_IRUSR 0000400 /* R for owner */ | |
#define S_IWUSR 0000200 /* W for owner */ | |
#define S_IXUSR 0000100 /* X for owner */ | |
#define S_IRWXG 0000070 /* RWX mask for group */ | |
#define S_IRGRP 0000040 /* R for group */ | |
#define S_IWGRP 0000020 /* W for group */ | |
#define S_IXGRP 0000010 /* X for group */ | |
#define S_IRWXO 0000007 /* RWX mask for other */ | |
#define S_IROTH 0000004 /* R for other */ | |
#define S_IWOTH 0000002 /* W for other */ | |
#define S_IXOTH 0000001 /* X for other */ | |
int main() | |
{ | |
UINT32 unixMode; | |
DWORD dwRes, dwDisposition; | |
PSID pOwnerSID = NULL, pOwnerGroupSID = NULL, pEveryoneSID = NULL; | |
PACL pACL = NULL; | |
PSECURITY_DESCRIPTOR pSD = NULL; | |
EXPLICIT_ACCESS ea[3]; | |
SID_IDENTIFIER_AUTHORITY SIDAuthOwner = SECURITY_CREATOR_SID_AUTHORITY; | |
SID_IDENTIFIER_AUTHORITY SIDAuthOwnerGroup = SECURITY_CREATOR_SID_AUTHORITY; | |
SID_IDENTIFIER_AUTHORITY SIDAuthWorld = SECURITY_WORLD_SID_AUTHORITY; | |
// Take unix octal and split it into: owner, group, everyone, using the three well known SIDs: | |
// * SECURITY_CREATOR_OWNER_RID (owner) | |
// * SECURITY_CREATOR_GROUP_RID (owner group) | |
// * SECURITY_WORLD_RID (everyone) | |
// This... mostly maps to the unix semantics. | |
ZeroMemory(&ea, 3 * sizeof(EXPLICIT_ACCESS)); | |
// Create a well-known SID for the owner. | |
if(!AllocateAndInitializeSid(&SIDAuthOwner, 1, | |
SECURITY_CREATOR_OWNER_RID, | |
0, 0, 0, 0, 0, 0, 0, | |
&pOwnerSID)) | |
{ | |
_tprintf(_T("AllocateAndInitializeSid Error %u\n"), GetLastError()); | |
goto Cleanup; | |
} | |
ACCESS_MASK ownerMode; | |
if (unixMode&S_IRUSR) { | |
ownerMode |= GENERIC_READ; | |
} | |
if (unixMode&S_IWUSR) { | |
ownerMode |= GENERIC_WRITE; | |
} | |
if (unixMode&S_IXUSR) { | |
ownerMode |= GENERIC_EXECUTE; | |
} | |
ea[0].grfAccessPermissions = ownerMode; | |
ea[0].grfAccessMode = SET_ACCESS; | |
ea[0].grfInheritance= NO_INHERITANCE; | |
ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID; | |
ea[0].Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP; | |
ea[0].Trustee.ptstrName = (LPTSTR) pOwnerSID; | |
// Create a well-known SID for the owner group. | |
if(!AllocateAndInitializeSid(&SIDAuthOwnerGroup, 1, | |
SECURITY_CREATOR_GROUP_RID, | |
0, 0, 0, 0, 0, 0, 0, | |
&pOwnerGroupSID)) | |
{ | |
_tprintf(_T("AllocateAndInitializeSid Error %u\n"), GetLastError()); | |
goto Cleanup; | |
} | |
ACCESS_MASK ownerGroupMode; | |
if (unixMode&S_IRGRP) { | |
ownerGroupMode |= GENERIC_READ; | |
} | |
if (unixMode&S_IWGRP) { | |
ownerGroupMode |= GENERIC_WRITE; | |
} | |
if (unixMode&S_IXGRP) { | |
ownerGroupMode |= GENERIC_EXECUTE; | |
} | |
ea[1].grfAccessPermissions = ownerGroupMode; | |
ea[1].grfAccessMode = SET_ACCESS; | |
ea[1].grfInheritance= NO_INHERITANCE; | |
ea[1].Trustee.TrusteeForm = TRUSTEE_IS_SID; | |
ea[1].Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP; | |
ea[1].Trustee.ptstrName = (LPTSTR) pOwnerGroupSID; | |
// Create a well-known SID for the Everyone group. | |
if(!AllocateAndInitializeSid(&SIDAuthWorld, 1, | |
SECURITY_WORLD_RID, | |
0, 0, 0, 0, 0, 0, 0, | |
&pEveryoneSID)) | |
{ | |
_tprintf(_T("AllocateAndInitializeSid Error %u\n"), GetLastError()); | |
goto Cleanup; | |
} | |
ACCESS_MASK everyoneMode; | |
if (unixMode&S_IROTH) { | |
everyoneMode |= GENERIC_READ; | |
} | |
if (unixMode&S_IWOTH) { | |
everyoneMode |= GENERIC_WRITE; | |
} | |
if (unixMode&S_IXOTH) { | |
everyoneMode |= GENERIC_EXECUTE; | |
} | |
ea[2].grfAccessPermissions = everyoneMode; | |
ea[2].grfAccessMode = SET_ACCESS; | |
ea[2].grfInheritance= NO_INHERITANCE; | |
ea[2].Trustee.TrusteeForm = TRUSTEE_IS_SID; | |
ea[2].Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP; | |
ea[2].Trustee.ptstrName = (LPTSTR) pEveryoneSID; | |
// Create a new ACL that contains the new ACEs. | |
dwRes = SetEntriesInAcl(3, ea, NULL, &pACL); | |
if (ERROR_SUCCESS != dwRes) | |
{ | |
_tprintf(_T("SetEntriesInAcl Error %u\n"), GetLastError()); | |
goto Cleanup; | |
} | |
// Initialize a security descriptor. | |
pSD = (PSECURITY_DESCRIPTOR) LocalAlloc(LPTR, | |
SECURITY_DESCRIPTOR_MIN_LENGTH); | |
if (NULL == pSD) | |
{ | |
_tprintf(_T("LocalAlloc Error %u\n"), GetLastError()); | |
goto Cleanup; | |
} | |
if (!InitializeSecurityDescriptor(pSD, | |
SECURITY_DESCRIPTOR_REVISION)) | |
{ | |
_tprintf(_T("InitializeSecurityDescriptor Error %u\n"), | |
GetLastError()); | |
goto Cleanup; | |
} | |
// Add the ACL to the security descriptor. | |
if (!SetSecurityDescriptorDacl(pSD, | |
TRUE, // bDaclPresent flag | |
pACL, | |
FALSE)) // not a default DACL | |
{ | |
_tprintf(_T("SetSecurityDescriptorDacl Error %u\n"), | |
GetLastError()); | |
goto Cleanup; | |
} | |
LPSTR sdStr; | |
PULONG sdLen; | |
if (!ConvertSecurityDescriptorToStringSecurityDescriptorA( | |
pSD, | |
SECURITY_DESCRIPTOR_REVISION, // SDDL_REVISION_1 ? | |
0, // SecurityInformation | |
&sdStr, | |
sdLen)) { | |
_tprintf(_T("ConvertSecurityDescriptorToStringSecurityDescriptorA Error %u\n"), | |
GetLastError()); | |
goto Cleanup; | |
} | |
_tprintf(_T("%s %d\n"), sdStr, IsValidSecurityDescriptor(pSD)); | |
Cleanup: | |
if (pOwnerSID) | |
FreeSid(pOwnerSID); | |
if (pOwnerGroupSID) | |
FreeSid(pOwnerGroupSID); | |
if (pEveryoneSID) | |
FreeSid(pEveryoneSID); | |
if (pACL) | |
LocalFree(pACL); | |
if (pSD) | |
LocalFree(pSD); | |
return 0; | |
} |
@rolandshoemaker Per your comment here here's an equivalent program in go:
package main
import (
"fmt"
"os"
"strconv"
"golang.org/x/sys/windows"
)
func main() {
var unixMode uint32
if len(os.Args) > 1 {
unixModeStr := os.Args[1]
unixMode64, err := strconv.ParseUint(unixModeStr, 8, 32)
if err != nil || unixMode64 > 0o777 {
fmt.Fprintf(os.Stderr, "Invalid octal mode: %s (valid range: 000-777)\n", unixModeStr)
os.Exit(1)
}
unixMode = uint32(unixMode64)
}
fmt.Printf("unixMode=%04o (0x%x) (%d)\n", unixMode, unixMode, unixMode)
var ea [3]windows.EXPLICIT_ACCESS
ownerSid, err := allocSID(windows.SECURITY_CREATOR_SID_AUTHORITY, windows.SECURITY_CREATOR_OWNER_RID)
checkErr("Allocate owner SID", err)
defer windows.FreeSid(ownerSid) //nolint:errcheck // quiet linter
ownerMask := accessMask(unixMode, 6) //nolint:mnd // quiet linter
setExplicitAccess(&ea[0], ownerSid, ownerMask)
groupSid, err := allocSID(windows.SECURITY_CREATOR_SID_AUTHORITY, windows.SECURITY_CREATOR_GROUP_RID)
checkErr("Allocate group SID", err)
defer windows.FreeSid(groupSid) //nolint:errcheck // quiet linter
groupMask := accessMask(unixMode, 3) //nolint:mnd // quiet linter
setExplicitAccess(&ea[1], groupSid, groupMask)
everyoneSid, err := allocSID(windows.SECURITY_WORLD_SID_AUTHORITY, windows.SECURITY_WORLD_RID)
checkErr("Allocate everyone SID", err)
defer windows.FreeSid(everyoneSid) //nolint:errcheck // quiet linter
everyoneMask := accessMask(unixMode, 0)
setExplicitAccess(&ea[2], everyoneSid, everyoneMask)
acl, err := windows.ACLFromEntries(ea[:], nil)
checkErr("ACLFromEntries (setEntriesInAcl)", err)
sd, err := windows.NewSecurityDescriptor()
checkErr("NewSecurityDescriptor (InitializeSecurityDescriptor)", err)
err = sd.SetDACL(acl, true, false)
checkErr("SetDACL (SetSecurityDescriptorDacl)", err)
sddl := sd.String()
fmt.Printf("IsValidSecurityDescriptor('%s') = %v\n", sddl, sd.IsValid())
}
func accessMask(mode uint32, shift int) uint32 {
var mask uint32
if mode&(0o4<<shift) != 0 { //nolint:mnd // quiet linter
mask |= windows.GENERIC_READ
}
if mode&(0o2<<shift) != 0 { //nolint:mnd // quiet linter
mask |= windows.GENERIC_WRITE | windows.DELETE
}
if mode&(0o1<<shift) != 0 { //nolint:mnd // quiet linter
mask |= windows.GENERIC_EXECUTE
}
return mask
}
func setExplicitAccess(ea *windows.EXPLICIT_ACCESS, sid *windows.SID, mask uint32) {
ea.AccessPermissions = windows.ACCESS_MASK(mask)
ea.AccessMode = windows.SET_ACCESS
ea.Inheritance = windows.NO_INHERITANCE
ea.Trustee.TrusteeForm = windows.TRUSTEE_IS_SID
ea.Trustee.TrusteeType = windows.TRUSTEE_IS_WELL_KNOWN_GROUP
ea.Trustee.TrusteeValue = windows.TrusteeValueFromSID(sid)
}
func allocSID(authority windows.SidIdentifierAuthority, rid uint32) (*windows.SID, error) {
var sid *windows.SID
err := windows.AllocateAndInitializeSid(&authority, 1, rid, 0, 0, 0, 0, 0, 0, 0, &sid)
if err != nil {
return nil, err
}
return sid, nil
}
func checkErr(context string, err error) {
if err != nil {
fmt.Fprintf(os.Stderr, "%s failed: %v\n", context, err)
os.Exit(1)
}
}
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@rolandshoemaker This script works, but
sdStr
is empty (on my system at least), so the program outputs simply:To fix this, I changed line 149 to
Now the program outputs:
Also, per
https://learn.microsoft.com/en-us/windows/win32/fileio/file-security-and-access-rights
GENERIC_WRITE
doesn't includeDELETE
.So, setting
unixMode
to777
in the above program produces:Replacing
GENERIC_WRITE
withGENERIC_WRITE | DELETE
produces:If the goal is to map Unix rights to Windows ACLs,
DELETE
needs to be added, so users can delete (and rename) files they have write permissions for.