The purpose of this article is to give an intermediate understanding of discretionary access control lists (DACLs) and their access control entries (ACEs). Readers should have a firm understanding of C++ and some experience with Microsoft’s Windows platform and their WinAPI as well as access to a system running Windows if they wish to test this code. This code WILL NOT work on any POSIX platform. Please see the link to the source code at the bottom of the page.
DACLs are the lists of permissions under the “Security” tab of a files or folders properties page. They are used to govern what access a user or group has to that particular file or directory. A file without a DACL will allow Everyone full access to it, note that Everyone is a security group in the Microsoft’s Windows platform and is meant to be inclusive of all users, groups and services. If a DACL is present it will only allow the access it has specifically listed to a user or group. Users or groups with no permissions listed are denied access (exceptions to this are made for user or group listed as the owner of the file or folder) this means that a file with a blank DACL is different from a file with no DACL since a blank DACL will deny any access to all users and groups.
The following code creates a file (named “New.txt”) in the current working directory that the executable is running in, any files currently present in this directory which share the same name and extension will be deleted. When first created the file will have the default security permissions granted by the system and any permissions inherited from its parent directory. Readers are encouraged to examine these permissions during the pause given at Line 111 and what is listed to what is given as output from Line 108. Addresses to the appropriate MSDN entries are given as comments in the code for most functions and structs used. This project should be linked against AdvApi32.lib and User32.lib for MSVS users or libadvapi32.a and libuser32.a for Mingw users.
The first actual work that our code does is to declare a pointer to a SECURITY_DESCRIPTOR struct on Line 27. This struct is allocated space in memory using the ‘new’ operator and initialized using the “InitializeSecurityDescriptor()” function. This function sets all of the members, except the security revision level, of the SECURITY_DESCRIPTOR struct to blank. At this time SECUIRTY_DESCRIPTOR_REVISION is the only valid value for ‘dwRevision’ for this function.
The next thing that is done is to calculate the name of the file to be operated on based on the current working directory of the executable. This is done by getting the name of the current directory with the “GetCurrentDirectory()” function and appending “\New.txt” to the end of it with “sprinf_s()” function, this is done simply so that we have one less thing to think about when running this code.
Next is the declaration and initialization of our array of EXPLICIT_ACCESS structs. A note here first, this section was written in a verbose way to ensure that the reader knows what it is we are doing at each step, there are a few things that can be done to save you a lot of typing if that is what you prefer but this article was geared toward readability and not it’s authors convenience. These are what you can think of as the actual ACEs before they are processed into a form usable by the system they are in what is called “self relative format”. In order for these to be usable by the system, entries which deny access to any user or group must be listed BEFORE entries which grant access. This is done automatically by the function we use “SetEntriesInAcl()” for new entries to existing ones only. This is not a “per-user” basis but rather the overall ordering of all permissions, so a pattern of: UserA: Permissions denied, UserA: Permissions granted, UserB: Permissions denied, UserB: Permissions granted; would be invalid. Our first entry (ExplicitAccess[0]) is zeroed out and then initialized with the “BuildExplicitAccessWithName()” function. This function is effectively the same as initializing the instances of the structs in the method I used for the other two entries, neither method offers any more or any less control then the other for our purposes here. We start of by telling the function where in memory our EXPLICIT_ACCESS struct is stored, this is done by passing ExplicitAccess[0] by reference as the first argument. We then tell the function which account we wish to set this entry for, I choose the “Guest” account because it is a built in account for all versions of the Windows operating system and even if it is deactivated (which it is by default starting with Windows XP) this will still work. The third argument is the list of permissions we wish to address, each of these is a stand in for a number so we can combine them into one argument using the bitwise OR operator before it is passed to the function. The fourth argument tells the function what kind of ACE to set this as, we choose DENY_ACCESS because that is what we wish to do and remember all entries which deny access must be entered first. The final argument has a number of possibilities listed in the documentation for this function, I choose NO_PROPAGATE_INHERIT_ACE to show later on how these same methods can be applied to set permissions on a folder by simply changing the file name and swapping out “CreateFile()” with “CreateDirectory()”.
The other two entries in our ExplicitAccess array are defined by hand; when doing this one has to remember that the WinAPI is written for C so there is no constructor that sets default values for these members and anything not entered is technically undefined. The ExplicitAccess[1] entry grants all access (GENERIC_ALL) to the group “Authenticated Users”, this is a group account that is built into Windows by default and should work on your system. The only entries of note here are the Trustee members pMultipleTrustee, MultipleTrusteeOperation and TrusteeType, at the time of writing this article the first two listed are unsupported and must be set to the values I have here. The third is set to TRUSTEE_IS_GROUP to tell the system that the account you are setting permissions up for in this entry is a group and not a user or a system. If you haven’t guessed yet the Trustee member pstrName is the name of the user or group you wish to make an entry for. ExplicitAccess[2] is setup as another entry relating to the “Guest” account. I did this for two reasons, first to show that the two methods for defining an EXPLICIT_ACCESS struct are interchangeable and to show that which accounts the entries pertain to can be done in any order, as long as all entries which deny permission to any account come first.
Now we enter our try catch block, this is used to ensure proper cleanup of the code in the event of an error. The “DeleteFile()” command on Line 80 is there in case you choose to run this process more than once and you don’t want to keep deleting the file it creates every time. I noticed that even with the CREATE_ALWAYS flag set in “CreateFile()” the ACEs remain when the file is overwritten so in order to see the changes being made, the target file must be deleted each time. If the file does not exist then “DeleteFile()” will return FALSE but that value is ignored in our program continues on.
First we call “CreateFile()” to actually create the file we will be working on. The only real thing to note here is our argument to lpSecurityAttributes which is NULL. We do this so that the system will create our file with the default security rights for your system which should only be the user running the program (You) and possibly any security permissions inherited from the parent directory. If successful, this function creates our file and returns a handle which is what we use from here on in to address the file itself; otherwise an error is thrown and caught and our program spits out an error code which you may look up here: http://msdn.microsoft.com/en-us/library/windows/desktop/ms681381(v=vs.85).aspx
Next, on Line 91 we call the “GetNamedSecurityInfo()” function which as you might have guessed allows you to get the Security Information from the object you name. In this case we use the name of the file we just created which will not be a problem since we set the lpSecurityAttributes flag to NULL when created it so you will have the required access rights to do this. For any other argument to lpSecuirytAttributes or for preexisting files some additional work may be required before this point for this function to succeed. This function, “GetNamedSecurityInfo()”, will actually return any number of attributes for an object, assuming they exist, by combining the values of the third argument with the bitwise OR operator; but today we are only focusing on the DACL of this file "New.txt". As the first argument we pass this function our files name. Then second, we tell it that our object is an SE_FILE_OBJECT type. Third is the SECURITY_INFORMATION flag (DWORD) indicating what we are looking for, in this case we want the DACL of the object we named in the first argument. The next two arguments, ppsidGroup and ppsidOwner, could be pointers which would contain pointers to the SID’s of the Owner of the object and\or the primary group, again assuming they exist, but we are not looking at those today so we set them to NULL. Now, at argument 6, we come to ppDacl which is the argument we pass to receive a POINTER to the objects current discretionary access control list. It is crucial here to note that the definition for this argument to this function is for a pointer to a pointer so you cannot pass in an actual instance of an ACL or a reference to an ACL. This argument must be a pointer to an ACL that is passed to by reference using the ampersand operator (&). The next variable, ppSacl, could be a pointer to a pointer for the SACL of the object in being queried but this ACL probably won’t exist in this case and is not the focus of this article so we pass NULL instead. Finally comes the pointer to a pointer which will hold the DACL of the object we named. For this we use a reference to our variable ‘pSecurityDescriptor’ which is defined on Line 27 as a pointer to a SECURITY_DESCRIPTOR data type. This argument will receive the actual Security Descriptor, or combination of Security Descriptors, that was requested from this function.
The Security Descriptor returned by “GetNamedSecurityInfo()” is not in a format that will mean anything to you so in order to actually be able to gather any meaning from it we process the string using the function “ConvertSecurityDescriptorToStringSecurityDescriptor()” at Line 100. This function requires first a pointer to our Security Information struct which is ‘pSecurityDescriptor’ that was filled out for us by the "GetNamedSecurityInfo()" function. Next you are required to specify the revision number of the security descriptor you have passed in, at the time of this articles writing only SDDL_REVISION_1 is supported. The third argument is the same combination of flags that were passed to “GetNamedSecurityInfo()”. The fourth argument is a pointer to a pointer of the actual string that will hold Security Descriptor information as plain text. We use the DACLDescriptorAsString variable defined on Line 41 as an LPSTR pointer to a new LPSTR. This may look confusing but LPSTR is a pointer to a CHAR data type so DACLDescriptorAsString works out to be a pointer to a pointer to a CHAR type. The fifth and final parameter is the length of the Security Descriptor string, this could be NULL if you want it to but I chose to store it in SecDescStringLengthNeeded for no actual reason. Now to output this string we simply use std::cout and dereference DACLDescriptorAsString only ONE time, giving us a pointer to a CHAR array which holds the data we want to display.
It is here that I have used my function “pause()” to cause the program to wait, feel free to use a debugger instead but I did not want to exclude users who don’t know how. The program was paused so that you can compare the string stored in DACLDescriptorAsString with what you see listed under the “Security” tab for the properties of this file in Windows Explorer. To continue process you only need to press enter, but let’s take a minute to look at this string being displayed in your console window since you’ll notice that this is not exactly plain text. Security Descriptor Strings have their own format which is described in detail in the URL on Line 105 of the code. My string starts with a “D:“ which indicates that this string is a DACL. The next part displayed is the first ACE string (the link is at Line 106 if you want to look up the specific meanings of the entries you see for your own string) which starts with “(A;” telling me that the purpose of this descriptor is to define an ACCESS_ALLOWED_ACE_TYPE which allows access to the object it is associated with. I next see “ID;FA;” which are flags telling me that this is INHERITED_ACE type with FILE_ALL_ACCESS rights. The entries for object_gui and inherit_object_gui are “;;” since this is a text file and so it does not have a GUI. The final entry is a long string that is in fact the SID of my Windows account followed by a close brace ")" indicating the end of that Access Control Entry. The next two ACE strings are pretty much the same as the first except for the last entry in each string which indicates which account the strings relate. If these two are present as they are in mine then you'll notice that they are much shorter, consisting of only two characters each: “BA” and “SY”. If we go to the URL on Line 107 we see the SID string format documentation which tells us that these are constant values for the SDDL_BUILTIN_ADMINISTRATORS and the SDDL_LOCAL_SYSTEM accounts.
At this point feel free to press Enter and continue operation of the program. If everything went well your program will continue on the Line 114 where we call the “SetEntriesInAcl()” to use the EXPLICIT_ACCESS structs we defined earlier into actual entries in our files Access Control List (ACL). For the first argument I used the same const expression that I used to define the size of our EXPLICIT_ACCESS array because there is no reason these two values should be different. The second argument requires a pointer to the list of EXPLICIT_ACCESS structs we want to use to define the new ACL. Since our EXPLICI_ACCESS entries are stored in an array we simply pass its name. The third entry is a pointer to our old DACL, this one is optional but since we have it anyway we will pass it in. If we do not include the old DACL then a brand new DACL is created using only the EXPLICIT_ACCESS structs we provided and any permissions inherited from the parent folder. In order to see this happen you would need to comment out the “DeleteFile()” command on Line 80, replace the ‘pOldDacl’ variable on Line 114 with ‘NULL’ and add another user or access permission to the file using properties->security tab in Windows Explorer before you run this program. What you will see is that the entry you made by hand through Windows Explorer will be removed. Finally the last argument to “SetEntriesInAcl()” is a pointer that receives a pointer to the new ACL created by this function. We then check our new ACL for consistency on Line 123 with the “IsValidAcl()” function.
Finally we come to Line 129 which is “SetNamedSecurityInfo()”, this function is pretty much the inverse of the “GetNamedSecurityInfo()” function we used at the start. It sets the security information for the object based on the flags you pass in the third argument and the structs you pass after that. Here you can see that we only tell it to set the DACL_SECURITY_INFORMATION and we only pass it the pNewDacl that was just created. This function sets information and does not return it so there is no entry equivalent to ppSecurityDescriptor.
After that your file is now set with the new security descriptor information you provided. We grab the current DACL (now the new one) and convert it into a string just like we did before so that you can compare the old entries verse the new ones one above the other.
Attachments:
[main.cpp]