How to create a folder that inherits its parent’s ACL, and then overrides part of it


A customer wants to create a folder that inherits its parent's ACL but then overrides part of it. Specifically, the customer wanted to disallow the creation of subfolders. The customer reported that when they used the SH­Create­Directory function to create the folder, the folder did not inherit any ACLs at all from its parent. The only thing it got was the "deny creation of subfolders" part.

The customer provided this sample code to demonstrate what they were doing.

int main()
{
  PSECURITY_DESCRIPTOR pSD;
  ULONG ulSDDL;
  LPTSTR pszPath = L"C:\\my\\test\\directory";
  LPTSTR pszDacl = L"D:(D;;0x4;;;WD)";

  if (ConvertStringSecurityDescriptorToSecurityDescriptor(
    pszDacl, SDDL_REVISION_1, &pSD, &ulSDDL))
  {
    wprintf(L"Created security descriptor\n");
    SECURITY_ATTRIBUTES sa;
    sa.lpSecurityDescriptor = pSD;
    sa.nLength = sizeof(sa);
    sa.bInheritHandle = TRUE;
    if (SUCCEEDED(SHCreateDirectoryEx(nullptr, pszPath, &sa)))
    {
        wprintf(L"Created folder %s\n", pszPath);
    }
  }
  return 0;
}

Notice the importance of reduction, simplifying the problem to the smallest program that still demonstrates the issue. This boils the problem down to its essence, thereby allowing the development team to focus on the issue and not have to wade through (and possibly debug) unrelated code. Reduction is also a useful exercise on the part of the person reporting the problem, in order to verify that the problem really is what you think it is, rather than being a side effect of some other part of the program.

The customer added, "The ACL we are using D:(D;;0x4;;;WD) denies folder creation to everyone. We tried adding flags like P, AI, OICI, etc., but none of them seem to work."

The shell takes the security descriptor passed to the SH­Create­Directory­Ex function and passes it through to the Create­Directory function, so any issues you have with the security descriptor are really issues with the Create­Directory function. The shell is just the middle man.

But even though this wasn't really the expertise of the shell team, we were able to figure out the problem.

First off, we have a red herring: The bInherit­Handle member controls handle inheritance, not ACL inheritance. Setting it to TRUE causes the handle to be inherited by child processes. But that has no effect on the ACL. And since the Create­Directory function doesn't return a handle at all, fiddling with the bInherit­Handle means nothing since there is no handle in the first place. It's a double red herring.

When you specify an explicit security descriptor to the Create­Directory function, that establishes the security descriptor on the newly-created object. There is no inheritance from the parent. Inheritance rules are applied at creation only when you create the object with the default security attributes:¹

If lpSecurityAttributes is NULL, the directory gets a default security descriptor. The ACLs in the default security descriptor for a directory are inherited from its parent directory.

Passing an explicit security descriptor overrides the default behvaior.

If you want a blend of default behavior and custom behavior, then you have a few options available.

One option is to read the security descriptor of the parent object and propagate the inheritable ACEs to the child in the appropriate manner. This is a complicated endeavor and probably is best left to the experts. It's not a simple matter of copying them from the parent to the child. You also have to to adapt the ACEs based on flags like "inherit only" and "container inherit".

The second option is to create the directory without an explicit security descriptor and let the experts create it with the default security descriptor, which takes into account all the inheritance rules. And then modify the security descriptor post-creation to include the new ACE you want. Fortunately, MSDN has sample code for how to add an ACE to an existing security descriptor.

The customer reported that they adapted the code from MSDN and it worked perfectly.

¹ Inheritance rules are also applied when you use functions like Set­Named­Security­Info and Set­Security­Info.

Comments (18)
  1. ipoverscsi says:

    While the idea of creating the folder and then modifying the permissions is certainly the easier option, it does open up a race condition. It similar to the problem with using sequence tmpnam() -> open() in C/C++ and why they now suggest using tmpfile().

    I wish the Windows security model were easier to use as the first option would avoid the race condition. But as they say “A wish in one hand and $10 in the other will get you a Starbucks moca frappachino”

    1. Pierre B. says:

      You can avoid the race condition: create a first dummy directory using the process Raymond outlined. Then use the ACL of that dumym directory to create the real directory you wanted and delete the dummy.

      I wonder if there might be corner cases where the ACL of the dummy cannot be directly mimicked for the real directory?

      1. MarcK4096 says:

        Creating a dummy folder and deleting it later opens up the possibility that AV will interfere and the delete will fail.

      2. Harry Johnston says:

        It would probably be preferable to create the real directory immediately, but with an empty ACL, blocking all access. You can then assign the permissions you want using SetSecurityInfo with the UNPROTECTED_DACL_SECURITY_INFORMATION option.

    2. Joshua says:

      The trick here is to not use the shell functions and just read the ACL of the parent.

      1. cheong00 says:

        Reading ACL of parent and fiddle it yourself is error prone. In addition to “inherit only” and “container inherit”, if there are “explicit set on this object” rule on the parent ACL, copying it to child directly means if users/administrator somehow want to remove that rule from the parent folder, users will still be able to access this folder using this rule.

    3. skSdnW says:

      The NT API lets you solve this without a race in multiple ways:

      A) Use NtCreateFile to create the directory because it gives you a handle to the directory. Then modify the ACL.

      B) Call CreateFile on the parent directory to get its handle. Get the ACL from the handle and then use NtCreateFile to create the new directory relative to the parents handle.

      There is still a race on the ACL itself of course but without functions that manipulate ACLs while holding a lock on filesystem there is not much you can do about that.

      1. skSdnW says:

        …and if the NT API is unacceptable you can use transactions instead (but they are deprecated).

        1. Joshua says:

          Too bad that feature would have had to wait a decade to get any traction. It was a good idea. It was abandoned before most people who would use it could decide to use it.

    4. Harry Johnston says:

      I don’t think there’s a race condition here. Any attackers that could create a subdirectory in the newly created folder before the security was changed could also create subdirectories in the parent folder, i.e., they could have created the folder and any contents they wanted ahead of time, no need to wait for the posted code to run, hence no race.

      (For that matter, the problem with tmpnam isn’t the race condition per se, it is that UNIX traditionally uses a shared temporary directory.)

      1. skSdnW says:

        The race is still there even though you are probably the only person on the racetrack. It is not even all about an attacker even though we are talking about locking down ACLs. A race in the filesystem also affects general robustness and should be avoided if it is possible to perform the multi-step operation on a handle rather than a path.

        1. Harry Johnston says:

          Can you provide an example of how robustness could be affected? I don’t see it. (I’m not sure you’re using the word “race” in the sense I’m familiar with. How can there be a race when there is only one thread acting on the object in question?)

          1. Richard says:

            The assumption is that your program only works properly if there are no subfolders – otherwise you wouldn’t care.

            Your program creates the folder, then its timeslice ends
            Another application creates a subfolder.
            Your timeslice starts again, the ACLs are set.

            Now your program does not work properly, because there is a subfolder. Your assumption is broken.

            The other program may not work properly as it could create a subfolder before and now can’t. The subfolder may also have a different set of ACLs, which may lead to further issues.

            ACLs are quite nasty tbh. An extremely powerful feature that is very hard to understand – perhaps something for future blog posts?

          2. Harry Johnston says:

            @Richard, as I said above, that’s not really a race condition, because even if your code creates the folder with the correct permissions atomically, the other application could have created both the folder AND the subfolder before you even got started. skSdnW seemed to be thinking of something else.

        2. MarcK4096 says:

          You might think you are the only one in the race, but AV is usually right there with you ready to get in the way.

          1. Harry Johnston says:

            True, but it had better not be creating new files and folders in arbitrary locations, and even if it is, setting security permissions isn’t likely to stop it.

  2. Neil says:

    There used to be an application that thought that when moving a file from one directory to another that it would be a good idea to reset all the permissions to the new inherited permissions. Fortunately that was an easier fix (read the old explicit permissions first).

  3. henke37 says:

    Pity that there is no function to ask the experts to do their thing, and only their thing, without the folder creation.

Comments are closed.

Skip to main content