Why does the CREATOR_OWNER SID sometimes reset itself to the object’s current owner rather than its original owner?


I noted some time ago that the CREATOR_OWNER security identifier (SID) is not a shorthand that refers to the object's current owner. Rather, the CREATOR_OWNER SID is a template that is applied when the object is created. When the template is applied, all occurrences of CREATOR_OWNER are replaced with the object's current owner, and it is the replaced SID that controls access. Changing the object's owner doesn't cause these access control entries to be recalculated;¹ they continue to refer to the captured value.

A customer observed this phenomenon when they created a folder with an inheritable access control entry (ACE) for CREATOR_OWNER. They observed that those access control entries were indeed propagated to child objects, with the CREATOR_OWNER changed to the object's actual owner. Furthermore, if they went to the Security properties and changed the child object's owner, the ACE was not recalculated to update the ACE's SID from the old owner to the new owner.

However (and this is the weird part), if they use the Security properties to make some unrelated change to the object's access control list (ACL), then this has a side effect of recalculating the ACEs and updating the CREATOR_OWNER-sourced ACEs to refer to the new owner.

This recalculation is not being done by the security infrastructure. It's being done by the ACL editor.

When you change the access control list for an item, the ACL editor calls Tree­Set­Named­Security­Info and passes an ACL that consists only of the non-inherited ACEs, and it sets the UNPROTECTED_DACL_SECURITY_INFORMATION flag, which means "Oh, and also inherit ACEs from my parent, as if I were newly-created."

In other words, the ACL edit deletes all the ACEs that were obtained by inherance, and then creates new ACEs based on the current parent's inheritable ACEs.

The ACL editor is trying to be helpful, but it ends up being confusing.

¹ What would this recalculation even mean if the object was moved to a new folder after being created, or if the containing folder's access control list were modified in the interim? I guess you could have a bit somewhere in the ACE that says, "This was originally created from a template that used CREATOR_OWNER." The closest thing to that is the INHERITED_ACE bit, which says "This ACE was autogenerated via inheritance," but it doesn't give any information as to what the original ACE was. Suppose the object's current owner is Bob. If an ACE applies to Bob and has the INHERITED_ACE bit set, it could mean that the original template ACE's SID was CREATOR_OWNER that was changed to Bob during template application because Bob was the original owner, or it could mean that the original template ACE's SID was CREATOR_GROUP that was changed to Bob during template application because Bob was the original group, or it could mean that the original template ACE's SID was Bob all along.

Comments (17)
  1. Stefan Kanthak says:

    The callback function of Tree­Set­Named­Security­Info() https://msdn.microsoft.com/en-us/library/aa965849.aspx is declared without calling convention; with compiler switches /Gr or /Gz this will generate WRONG code.
    Most (if not all) other callback functions used in the Win32 API are but declared CALLBACK alias __stdcall
    Is there a deeper reason for this bug, or is this just sloppy coding?

    1. Darran Rowe says:

      To be a bit picky, the code generated is correct for the definitions, but this is an ODR violation.

      1. Stefan Kanthak says:

        No ODR here!
        The first compilation unit, the DLL, is compiled with /Gz, so the callback function gets __stdcall calling convention.
        The second compilation unit, the user program, is typically compiled without /Gz, so the callback function gets __cdecl calling convention.

        1. Darran Rowe says:

          Well, if you want to be picky to my picky, then fine, I’ll be picky back.
          The exact rule that governs this is, in the C++17 standard 6.5 [basic.link] paragraph 10.
          “After all adjustments of types (…), the types specified by all declarations referring to a given variable or function shall be identical, except that declarations for an array object can specify array types that differ by the presence or absence of a major array bound. A violation of this rule on type identity does not require a diagnostic.”
          Since the type of TreeSetNamedSecurityInfo has external linkage and the types are different then we have a function with external linkage declared with two different types in two different translation units.
          Either way, the code being generated isn’t wrong for the definitions.

          1. Stefan Kanthak says:

            1. who said something about C++?
            https://msdn.microsoft.com/en-us/library/aa965849.aspx is the pure ANSI C interface
            2. the difference between theory and practice is the practice.
            Unless compiled with /Gz the callee doesn’t match the caller’s expectation and will lead to a crash or vulnerability.
            Do I REALLY need to come up with an exploit?

          2. Darran Rowe says:

            Stefan Kanthak:
            Who said anything about the C++ standard’s rules only apply to C++. The standard itself is quite compatible with C, and in fact the rules are usually the same, just worded differently. The C11 standard, 6.7 paragraph 4, states that all declaration in the same scope that refer to the same object must have compatible types. So even for a C interface the rule is the same.
            Also, I don’t think you quite get what I mean. If the compiler is given incorrect information on a function type, you can’t call the code it generates wrong, since for the declarations the compiler is given, the code generated is correct. But note, this is NOT the same as me saying this isn’t a problem, this is a problem. It is just that this is caused by the declarations violating the rules of the C standard.

    2. I filed a bug against the team that owns that header file. Just sloppiness. Forgetting the calling convention is a common error. Since the unit test builds with the same compiler switches as the OS code (namely /Gz – stdcall as default), the lack of an explicit convention specifier is not detected.

      1. Stefan Kanthak says:

        So it’s a double happy^Wsloppiness when such simple to spot bugs go undetected.
        Insane question: why doesn’t Microsoft let customers file such bugs directly?
        Thanks for picking this up.

        1. In practice, what happens is that most of the bugs are low quality, and it’s hard to find the needle of good bugs in the haystack of bad ones.

          1. Stefan Kanthak says:

            I consider every bug low quality.-)

  2. Stefan Kanthak says:

    Are you able to find the MSVC keywords __cdecl, __stdcall or __fastcall (or anything like them which other compilers might support) in the standards you reference?
    Or the words “calling convention”?

    1. Darran Rowe says:

      Why does that matter? This isn’t as if this kind of problem stems from just the calling convention. Any time the declaration between two translation units is different, be it a compiler extension or from something in the language itself (32 bit type vs 64 bit type as one of the parameters) then this type of problem can still surface.

      But the C++17 standard does actually use the term calling convention in relation to linkage specification. You can find this in 10.5 paragraph 1.

      The C11 itself is a completely different thing. It implies that the description in the standard is for an abstract machine, and any implementation will have to fill in the gaps when porting to a real machine. So it does refer to abstract semantics and actual semantics. For example, 5.1.2.3 paragraph 9. So this acknowledges that the standard itself isn’t enough for a real machine and extra semantics are needed on real processors. There is also a note in 6.2.5 paragraph 14, note 44 that says an implementation is allowed to add keywords to provide alternative ways to designate a type. Function pointers are types, function prototypes are types, so this does allow for keywords like __cdecl and __stdcall to be used

      1. Stefan Kanthak says:

        The definition of the function FN_PROGRESS was introduced with Windows XP (or earlier) for the TreeResetNamedSecurityInfo() function.
        This predates the introduction of the ODR: the ISO C++ standard 2003 came a little bit later.
        And it predates C11 by a decade.

        So: was this bug NO bug before the introduction of the ODR with the ISO C standard (2011)?

        You argue with something invented/introduced after the fact.

        1. Darran Rowe says:

          Of course not, I was just using the C11 standard since it was the latest. The C99 standard 6.2.5 paragraph 14 has note 34, and it has the same information. The C99, and C89/90 standards are also written to indicate that the behaviour in the standard is written for an abstract machine, and a real machine is allowed to have stricter semantics. The example that I gave, 5.1.2.3 paragraph 9 is 5.1.2.3 paragraph 8 in the C99 standard and it has the same information. Also, the C99 standard predates XP. Another thing, C++ was first standardised in 98, 03 was just a bug fixed version of 98 so the actual ODR and declarations across translation units issues was in standardised C++ prior to the release of Windows XP.
          But again this doesn’t matter, the issue with different declarations of the same functions predated the C++ standardisation, and even the C standardisation. This can be traced as far back to K&R C where function declarations didn’t include parameter types. So it was well known back in the early 1980s how problematic calling functions with the incorrect function prototypes was. This actually seems like it was inherited from the B programming language too. When Dennis Ritchie took over maintaining B and this was a completely untyped language, one of the first things he did was to add variable types to the language, but functions still didn’t have prototypes. An interesting note, C actually added function prototypes to the standardised C89/90 standard after taking them from the pre-standardised C++.

          1. Stefan Kanthak says:

            Thanks! You just showed that your pickies weren’t pick^Wprecise enough in the first place.
            Regarding your also: note the slight difference between TreeResetNamedSecurityInfo() and TreeSetNamedSecurityInfo(), both in their names and the dates of their introduction.
            Don’t try to be picky…

          2. Darran Rowe says:

            @Stefan Kanthak
            Eh? I am confused here. The C standard has several clauses, that I have mentioned that declarations of objects in the same scope must match, this does include across translation units. That the calling convention is a machine specific semantic that the C standard supports, and is part of the function type and function pointer type.
            My being picky was based upon you stating that the compiler will generate the wrong code. I disagreed with you stating that the declaration of the functions is different between two translation units, and the code generated for the function given the type was correct. But this was an invalid program due to it violating the standard.
            You can find this in the C standard all the way back to the C89/90 standard, which predates Windows NT. While there is plenty of evidence in the C standard to support this, you told me that I wasn’t precise enough and that I shouldn’t be picky without providing any kind of counter debate?
            I’m sorry, but I do not understand this at all. Did you actually read the standards? Did it not even logically make sense to you that for the compiler to do the right thing that it needs to be given the correct information? Since two translation units are given different information, (the TreeSetNamedSecurityInfo function and your application) then how could it be a case of wrong code generation. It is like giving someone incorrect instructions regarding a task, and after they did the work you tell them that they did the wrong work.
            Finally, so what if TreeResetNamedSecurityInfo was in Windows XP. It wasn’t as if that counters the C standard definitions being standardised since 1989. Windows XP was released in 2001, this is a whole 12 years after the C standard initially stated that two declarations in the same scope referring to the same object must match. So please tell me, where was I not precise enough?

        2. Darran Rowe says:

          Oh, and also, the Windows headers indicate that TreeSetNamedSecurityInfo was added in Windows Vista. The function definitions are wrapped in #if (NTDDI_VERSION >= NTDDI_VISTA).

Comments are closed.

Skip to main content