The GetExplicitEntriesFromAcl
Win32 API function allows to retrieve the explicit entries of a file ACL. But when I change some entries, convert the result into a new ACL using SetEntriesInAcl
and finally apply the ACL back to the file with SetSecurityInfo
all inherited entries seem to be lost and only the (changed) explicit entries are left.
Is there a counterpart function "SetExplicitEntriesInAcl" that only replaces the explicit entries within an ACL structure and keeps the inherited entries intact?
Edit1: Code Sample
I'm using code similar to the following lines for ACL update:
int RemoveAclAccessRights( HANDLE hFile, PSID SidPtr,
DWORD AccessRights, ACCESS_MODE AccessMode )
{
PACL OldAcl = NULL, NewAcl = NULL;
PSECURITY_DESCRIPTOR SecDesc = NULL;
PEXPLICIT_ACCESS EntryList = NULL, EntryItem;
ULONG EntryCount, EntryIndex;
int r;
// Get a pointer to the existing DACL
r = GetSecurityInfo(hFile, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION,
NULL, NULL, &OldAcl, NULL, &SecDesc);
if ( r != ERROR_SUCCESS )
goto _CleanUp;
r = GetExplicitEntriesFromAcl(OldAcl, &EntryCount, &EntryItem);
if ( r != ERROR_SUCCESS )
goto _CleanUp;
EntryList = EntryItem;
EntryIndex = 0;
while ( EntryIndex < EntryCount ) {
// ... update access entry ...
EntryIndex++;
EntryItem++;
}
// Create a new ACL from the explicit entries of the existing DACL
r = SetEntriesInAcl(EntryCount, EntryList, NULL, &NewAcl);
if ( r != ERROR_SUCCESS )
goto _CleanUp;
// Attach the new ACL as the object's DACL
r = SetSecurityInfo(hFile, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION,
NULL, NULL, NewAcl, NULL);
_CleanUp:
LocalFree(NewAcl);
LocalFree(EntryList);
LocalFree(SecDesc);
return r;
}
Edit2: ACLs of the file and parent directory
Output of icacls
on the file:
> icacls TestAcl01.txt
TestAcl01.txt VORDEFINIERT\Gäste:(R)
VORDEFINIERT\Administratoren:(I)(F)
NT-AUTORITÄT\SYSTEM:(I)(F)
NT-AUTORITÄT\Authentifizierte Benutzer:(I)(M)
VORDEFINIERT\Benutzer:(I)(RX)
Output of icacls
on the parent directory:
> icacls .
. VORDEFINIERT\Administratoren:(I)(F)
VORDEFINIERT\Administratoren:(I)(OI)(CI)(IO)(F)
NT-AUTORITÄT\SYSTEM:(I)(F)
NT-AUTORITÄT\SYSTEM:(I)(OI)(CI)(IO)(F)
NT-AUTORITÄT\Authentifizierte Benutzer:(I)(M)
NT-AUTORITÄT\Authentifizierte Benutzer:(I)(OI)(CI)(IO)(M)
VORDEFINIERT\Benutzer:(I)(RX)
VORDEFINIERT\Benutzer:(I)(OI)(CI)(IO)(GR,GE)
The file has one explicit entry which is "VORDEFINIERT\Gäste:(R)" (SID "S-1-5-32-546"). The other entries are inherited from the parent directory.
In the while loop above I am trying to delete the explicit entry if it matches the SID using code like
if ( (EntryItem->Trustee.TrusteeForm == TRUSTEE_IS_SID) && EqualSid(EntryItem->Trustee.ptstrName, SidPtr) ) {
if ( EntryIndex < (EntryCount-1) )
MoveMemory(&EntryList[EntryIndex], &EntryList[EntryIndex+1], (EntryCount-EntryIndex-1)*sizeof(EntryList[0]));
EntryCount--;
continue;
}
Given the information in the latest edit, I can now replicate your problem. It only occurs in the case when you are removing all of the explicit entries from the DACL.
It turns out there's a nasty (and undocumented, so far as I can see) catch in SetEntriesInAcl: if you pass it a zero-length array, it silently returns NULL
as the new ACL rather than returning an empty ACL as you might reasonably expect.
The documentation for SetSecurityInfo explains what happens in this case:
If the value of the SecurityInfo parameter includes the DACL_SECURITY_INFORMATION flag and the value of this parameter is set to NULL, full access to the object is granted to everyone.
That implicitly removes the inherited permissions (which are redundant anyway).
One way to fix the problem:
ACL empty_acl;
if (!InitializeAcl(&empty_acl, sizeof(empty_acl), ACL_REVISION))
goto _CleanUp;
// Create a new ACL from the explicit entries of the existing DACL
r = SetEntriesInAcl(EntryCount, EntryList, &empty_acl, &NewAcl);
if ( r != ERROR_SUCCESS )
goto _CleanUp;