Inheritable ACE doesn't inherit (code included)

C

clintp

I've got a function that I'm using to create an inheritable ACE. I
want to grant permission on a directory, and have it be inherited by
its children.

The function was largely stolen from MSDN, and it almost works. If I
go into Explorer and pull up the directory, everyhing looks fine -- all
of the correct switches are set, etc... If I look at the children --
no permissions are inherited.

Doing it manually using Explorer's Security tab works fine.

And, in fact, if I take the "broken" ACL on the directory and modify it
in any way using the Security tab and apply the changes... voila, it
gets inherited by its children.

The function follows.

PS: in the if(){} block below, I'm sure that AddAccessAllowedAceEx is
being called. I've dumped the ACE using another tool and the flag bits
look the same in the header whether this function adjusts the
permissions, or the Security tab does it. I'm completely lost.

BOOL AddAccessRights(TCHAR *lpszFileName, TCHAR *lpszAccountName, DWORD
dwAccessMask)
{
// SID variables.
LPVOID pUserSID = NULL;

// File SD variables.
PSECURITY_DESCRIPTOR pFileSD = NULL;
DWORD cbFileSD = 0;

// New SD variables.
SECURITY_DESCRIPTOR newSD;

// ACL variables.
PACL pACL = NULL;
BOOL fDaclPresent;
BOOL fDaclDefaulted;
ACL_SIZE_INFORMATION AclInfo;

// New ACL variables.
PACL pNewACL = NULL;
DWORD cbNewACL = 0;

// Temporary ACE.
LPVOID pTempAce = NULL;
UINT CurrentAceIndex = 0;

UINT newAceIndex = 0;

// Assume function will fail.
BOOL fResult = FALSE;

SetSecurityDescriptorControlFnPtr _SetSecurityDescriptorControl;
AddAccessAllowedAceExFnPtr _AddAccessAllowedAceEx;

SECURITY_INFORMATION secInfo = DACL_SECURITY_INFORMATION;

//
// STEP 1: Get SID of the account name specified.
//
if (! (pUserSID = GetUserSid(lpszAccountName)))
{
fResult = 1;
goto EXITFUNC;
}

//
// STEP 2: Get security descriptor (SD) of the file specified.
//
if (! GetFileSecurity(lpszFileName, secInfo, pFileSD, 0, &cbFileSD))
{
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
{
err_msg();
fResult = 2;
goto EXITFUNC;
}
}

if (! (pFileSD = malloc(cbFileSD)))
{
fResult = -1;
goto EXITFUNC;
}

if (! GetFileSecurity(lpszFileName, secInfo, pFileSD, cbFileSD,
&cbFileSD))
{
err_msg();
fResult = 2;
goto EXITFUNC;
}

//
// STEP 3: Initialize new SD.
//
if (!InitializeSecurityDescriptor(&newSD,
SECURITY_DESCRIPTOR_REVISION))
{
err_msg();
fResult = 3;
goto EXITFUNC;
}

//
// STEP 4: Get DACL from the old SD.
//
if (!GetSecurityDescriptorDacl(pFileSD, &fDaclPresent, &pACL,
&fDaclDefaulted))
{
err_msg();
fResult = 4;
goto EXITFUNC;
}

//
// STEP 5: Get size information for DACL.
//
AclInfo.AceCount = 0; // Assume NULL DACL.
AclInfo.AclBytesFree = 0;
AclInfo.AclBytesInUse = sizeof(ACL);

if (pACL == NULL)
fDaclPresent = FALSE;

// If not NULL DACL, gather size information from DACL.
if (fDaclPresent)
{
if (!GetAclInformation(pACL, &AclInfo, sizeof(ACL_SIZE_INFORMATION),
AclSizeInformation))
{
err_msg();
fResult = 5;
goto EXITFUNC;
}
}

//
// STEP 6: Compute size needed for the new ACL.
//
cbNewACL = AclInfo.AclBytesInUse + sizeof(ACCESS_ALLOWED_ACE) +
GetLengthSid(pUserSID) - sizeof(DWORD);

//
// STEP 7: Allocate memory for new ACL.
//
if (! (pNewACL = (PACL) malloc(cbNewACL)))
{
fResult = -1;
goto EXITFUNC;
}

//
// STEP 8: Initialize the new ACL.
//
if (!InitializeAcl(pNewACL, cbNewACL, ACL_REVISION2))
{
err_msg();
fResult = 6;
goto EXITFUNC;
}

//
// STEP 9 If DACL is present, copy all the ACEs from the old DACL
// to the new DACL.
//
// The following code assumes that the old DACL is
// already in Windows 2000 preferred order. To conform
// to the new Windows 2000 preferred order, first we will
// copy all non-inherited ACEs from the old DACL to the
// new DACL, irrespective of the ACE type.
//
newAceIndex = 0;

if (fDaclPresent && AclInfo.AceCount)
{
for (CurrentAceIndex = 0; CurrentAceIndex < AclInfo.AceCount;
CurrentAceIndex++)
{
//
// STEP 10: Get an ACE.
//
if (!GetAce(pACL, CurrentAceIndex, &pTempAce))
{
err_msg();
fResult = 7;
goto EXITFUNC;
}

//
// STEP 11: Check if it is a non-inherited ACE.
// If it is an inherited ACE, break from the loop so
// that the new access allowed non-inherited ACE can
// be added in the correct position, immediately after
// all non-inherited ACEs.
//
if (((ACCESS_ALLOWED_ACE *)pTempAce)->Header.AceFlags &
INHERITED_ACE)
break;

//
// STEP 12: Skip adding the ACE, if the SID matches
// with the account specified, as we are going to
// add an access allowed ACE with a different access
// mask.
//
if (EqualSid(pUserSID, &(((ACCESS_ALLOWED_ACE
*)pTempAce)->SidStart)))
continue;

//
// STEP 13: Add the ACE to the new ACL.
//
if (!AddAce(pNewACL, ACL_REVISION, MAXDWORD, pTempAce,
((PACE_HEADER) pTempAce)->AceSize))
{
err_msg();
fResult = 7;
goto EXITFUNC;
}

newAceIndex++;
}
}

//
// STEP 14: Add the access-allowed ACE to the new DACL.
// The new ACE added here will be in the correct position,
// immediately after all existing non-inherited ACEs.
//
_AddAccessAllowedAceEx =
(AddAccessAllowedAceExFnPtr)GetProcAddress(GetModuleHandle(TEXT("advapi32.dll")),"AddAccessAllowedAceEx");
if (_AddAccessAllowedAceEx)
{
fprintf(stderr, "new API\n");
if (!_AddAccessAllowedAceEx(pNewACL, ACL_REVISION2,
CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE, dwAccessMask, pUserSID))
{
err_msg();
fResult = 8;
goto EXITFUNC;
}
}
else
{
if (!AddAccessAllowedAce(pNewACL, ACL_REVISION2, dwAccessMask,
pUserSID))
{
err_msg();
fResult = 8;
goto EXITFUNC;
}
}

//
// STEP 15: To conform to the new Windows 2000 preferred order,
// we will now copy the rest of inherited ACEs from the
// old DACL to the new DACL.
//
if (fDaclPresent && AclInfo.AceCount)
{
for (; CurrentAceIndex < AclInfo.AceCount; CurrentAceIndex++)
{
//
// STEP 16: Get an ACE.
//
if (!GetAce(pACL, CurrentAceIndex, &pTempAce))
{
err_msg();
fResult = 8;
goto EXITFUNC;
}

//
// STEP 17: Add the ACE to the new ACL.
//
if (!AddAce(pNewACL, ACL_REVISION, MAXDWORD, pTempAce,
((PACE_HEADER) pTempAce)->AceSize))
{
err_msg();
fResult = 9;
goto EXITFUNC;
}
}
}

//
// STEP 18: Set the new DACL to the new SD.
//
if (!SetSecurityDescriptorDacl(&newSD, TRUE, pNewACL, FALSE))
{
err_msg();
fResult = 10;
goto EXITFUNC;
}

//
// STEP 19: Copy the old security descriptor control flags
// regarding DACL automatic inheritance for Windows 2000 or
// later where SetSecurityDescriptorControl() API is available
// in advapi32.dll.
//
_SetSecurityDescriptorControl =
(SetSecurityDescriptorControlFnPtr)GetProcAddress(GetModuleHandle(TEXT("advapi32.dll")),"SetSecurityDescriptorControl");
if (_SetSecurityDescriptorControl)
{
SECURITY_DESCRIPTOR_CONTROL controlBitsOfInterest = 0;
SECURITY_DESCRIPTOR_CONTROL controlBitsToSet = 0;
SECURITY_DESCRIPTOR_CONTROL oldControlBits = 0;
DWORD dwRevision = 0;

if (!GetSecurityDescriptorControl(pFileSD, &oldControlBits,
&dwRevision))
{
err_msg();
fResult = 10;
goto EXITFUNC;
}

if (oldControlBits & SE_DACL_AUTO_INHERITED)
{
controlBitsOfInterest = SE_DACL_AUTO_INHERIT_REQ |
SE_DACL_AUTO_INHERITED;
controlBitsToSet = controlBitsOfInterest;
}
else if (oldControlBits & SE_DACL_PROTECTED)
{
controlBitsOfInterest = SE_DACL_PROTECTED;
controlBitsToSet = controlBitsOfInterest;
}

if (controlBitsOfInterest)
{
if (!_SetSecurityDescriptorControl(&newSD, controlBitsOfInterest,
controlBitsToSet))
{
err_msg();
fResult = 11;
goto EXITFUNC;
}
}
}

//
// STEP 20: Set the new SD to the File.
//
if (!SetFileSecurity(lpszFileName, secInfo, &newSD))
{
err_msg();
fResult = 12;
goto EXITFUNC;
}

fResult = 0;

EXITFUNC:
//
// STEP 21: Free allocated memory
//
if (pFileSD) free(pFileSD);
if (pNewACL) free(pNewACL);

return fResult;
}
 
H

Herb Martin

I've got a function that I'm using to create an inheritable ACE. I
want to grant permission on a directory, and have it be inherited by
its children.

The function was largely stolen from MSDN, and it almost works. If I
go into Explorer and pull up the directory, everyhing looks fine -- all
of the correct switches are set, etc... If I look at the children --
no permissions are inherited.

Most people don't realize but there are actually
two kinds (possible) of inheritable ACL sets on
each directory: One for containers (other
subdirectories) and one for non-containers.

I would check that first -- you might need to post
the code to a developer list; although some
of us here are programmers that is not the primary
focus here.

You might look at the source from SetAcl which
is on SourceForge.net.
 
R

Roger Abell

The code seems to have hidden all that is most relevant in the
unsupplied call parameter for the ACE flags. IIRC there is a
newer flag, but I think it is a SD flag not an ACE flag called
Inheritance Requested that may help.
The main issue you are dealing with is that what you see is
in fact how inheritance works, i.e. it is not guaranteed to be
immediately recalculated/propagated. An change event to an
ACL in the inheriting substructure will trigger recalculation
on the path to it, etc. which you have confirmed in your post
where you used Explorer to trigger the effective to be shown
as expected.
I would suggest that you post to an MSDN group (if not already)
as someone there may know the C++ Api that would allow you
to trigger the recalculation.
 
J

Joe Richards [MVP]

I didn't look at your code, sorry. I hate looking at other people's code.

However I have often found the issue with doing this stuff is that the flags
aren't set quite the way they should be. Do not use explorer to see if what you
did matches what Explorer does, it hides a lot of the detail. You will want to
dump the ACL of what you did and one you are trying to mimic and look at all of
the specific fields to verify they are all identical.

If you don't want to write the code to do that dump from scratch, look at
http://www.rallenhome.com/books/adcookbook/src/PerlChkSec.pls.txt which has a
perl script for doing that for AD permissions which should give you a jump on
how to do it with other permissions (they are all handled very similarly).

joe
 
R

Roger Abell

It is not just that the ACL editor accessed from Explorer hides
detail of the SD, ACL, and ACE headers and flags, it also will
alter them to what is believed equivalent but more "simple",
whatever the criteria are for that . . .
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top