Skip to content

Instantly share code, notes, and snippets.

@rolandshoemaker
Created November 27, 2024 18:02
Show Gist options
  • Save rolandshoemaker/21a04f56111c61da439f5b5a8def3275 to your computer and use it in GitHub Desktop.
Save rolandshoemaker/21a04f56111c61da439f5b5a8def3275 to your computer and use it in GitHub Desktop.
acl.cpp
#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;
}
@rasa
Copy link

rasa commented May 11, 2025

@rolandshoemaker This script works, but sdStr is empty (on my system at least), so the program outputs simply:

 1

To fix this, I changed line 149 to

OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION, // SecurityInformation

Now the program outputs:

D:(A;;;;;CO)(A;;;;;CG)(A;;;;;WD) 1

Also, per
https://learn.microsoft.com/en-us/windows/win32/fileio/file-security-and-access-rights
GENERIC_WRITE doesn't include DELETE.

So, setting unixMode to 777 in the above program produces:

D:(A;;GXGWGR;;;CO)(A;;GXGWGR;;;CG)(A;;GXGWGR;;;WD) 1

Replacing GENERIC_WRITE with GENERIC_WRITE | DELETE produces:

D:(A;;SDGXGWGR;;;CO)(A;;SDGXGWGR;;;CG)(A;;SDGXGWGR;;;WD) 1

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.

@rasa
Copy link

rasa commented May 11, 2025

@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