Programmatically controlling which handles are inherited by new processes in Win32


In unix, file descriptors are inherited by child processes by default. This wasn’t so much an active decision as it was a consequence of the fork/exec model. To exclude a file descriptor from being inherited by children, you set the FD_CLO­EXEC flag on the file descriptor.

Win32 sort of works like that, but backwards, and maybe a little upside-down. And in high heels.

In Win32, handles default to not inherited. Ways to make a handle inherited during Create­Process have grown during the evolution of Win32.

As far as I can tell, back in the old days, inheritability of handles was established at handle creation time. For most handle creation functions, you do this by passing a SECURITY_ATTRIBUTES structure with bInherit­Handle set to TRUE. Functions which created handles from existing objects don’t have a SECURITY_ATTRIBUTES parameter, so they instead have an explicit bInherit­Handle parameter. (For examples, see Open­Event and Duplicate­Handle.)

But just marking a handle as inheritable isn’t good enough to get it inherited. You also have to pass TRUE as the bInherit­Handles parameter to Create­Process. A handle will be inherited only if if the bInherit­Handles parameter is TRUE and the handle is marked as inheritable. Miss either of those steps, and you don’t get your inheritance. (To make sure you get your inheritance IRL, be nice to your grandmother.)

In Windows 2000, Win32 gained the ability to alter the inheritability of a handle after it is created. The Set­Handle­Information function lets you turn the HANDLE_FLAG_INHERIT flag on and off on a handle.

But all this inheritability fiddling still had a fatal flaw: What if two threads within the same process both call Create­Process but disagree on which handles they want to be inherited? For example, suppose you have a function Create­Process­With­Shared­Memory whose job it is to launch a process, passing it a custom-made shared memory block. Suppose two threads run this function simultaneously.

A   B
CreateFileMapping(inheritable=TRUE) CreateFileMapping(inheritable=TRUE)
returns handle H1 returns handle H2
CreateProcess(“A”, bInheritHandles=TRUE) CreateProcess(“B”, bInheritHandles=TRUE)
CloseHandle(H1) CloseHandle(H2)

What just happened? Since inheritability is a property of the handle, processes A and B inherited both handles H1 and H2, even though what we wanted was for process A to inherit handle H1 and for process B to inherit handle H2.

For a long time, the answer to this problem was the unsatisfactory “You’ll just have to serialize your calls to Create­Process­With­Shared­Memory so that thread B won’t accidentally cause a handle from thread A to be inherited by process B.” Actually, the answer was even worse. You had to serialize all functions that created inheritable handles from the time the handle was created, through the call to Create­Process, and waiting until after all those inheritable handles were made no longer inheritable.

This was a serious problem since who knows what other parts of your program are going to call Create­Process with bInherit­Handles set to TRUE? Sure you can control the calls that your own code made, but what about calls from plug-ins or other unknown components? (This is another case of kernel-colored glasses.)

Windows Vista addresses this problem by allowing you to pass an explicit list of handles you want the bInherit­Handles parameter to apply to. (If you pass an explicit list, then you must pass TRUE for bInherit­Handles.) And as before, for a handle to be inherited, it must be also be marked as inheritable.

Passing the list of handles you want to inherit is a multi-step affair. Let’s walk through it:

BOOL CreateProcessWithExplicitHandles(
  __in_opt     LPCTSTR lpApplicationName,
  __inout_opt  LPTSTR lpCommandLine,
  __in_opt     LPSECURITY_ATTRIBUTES lpProcessAttributes,
  __in_opt     LPSECURITY_ATTRIBUTES lpThreadAttributes,
  __in         BOOL bInheritHandles,
  __in         DWORD dwCreationFlags,
  __in_opt     LPVOID lpEnvironment,
  __in_opt     LPCTSTR lpCurrentDirectory,
  __in         LPSTARTUPINFO lpStartupInfo,
  __out        LPPROCESS_INFORMATION lpProcessInformation,
    // here is the new stuff
  __in         DWORD cHandlesToInherit,
  __in_ecount(cHandlesToInherit) HANDLE *rgHandlesToInherit)
{
 BOOL fSuccess;
 BOOL fInitialized = FALSE;
 SIZE_T size = 0;
 LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList = NULL;

 fSuccess = cHandlesToInherit < 0xFFFFFFFF / sizeof(HANDLE) &&
            lpStartupInfo->cb == sizeof(*lpStartupInfo);
 if (!fSuccess) {
  SetLastError(ERROR_INVALID_PARAMETER);
 }
 if (fSuccess) {
  fSuccess = InitializeProcThreadAttributeList(NULL, 1, 0, &size) ||
             GetLastError() == ERROR_INSUFFICIENT_BUFFER;
 }
 if (fSuccess) {
  lpAttributeList = reinterpret_cast<LPPROC_THREAD_ATTRIBUTE_LIST>
                                (HeapAlloc(GetProcessHeap(), 0, size));
  fSuccess = lpAttributeList != NULL;
 }
 if (fSuccess) {
  fSuccess = InitializeProcThreadAttributeList(lpAttributeList,
                    1, 0, &size);
 }
 if (fSuccess) {
  fInitialized = TRUE;
  fSuccess = UpdateProcThreadAttribute(lpAttributeList,
                    0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
                    rgHandlesToInherit,
                    cHandlesToInherit * sizeof(HANDLE), NULL, NULL);
 }
 if (fSuccess) {
  STARTUPINFOEX info;
  ZeroMemory(&info, sizeof(info));
  info.StartupInfo = *lpStartupInfo;
  info.StartupInfo.cb = sizeof(info);
  info.lpAttributeList = lpAttributeList;
  fSuccess = CreateProcess(lpApplicationName,
                           lpCommandLine,
                           lpProcessAttributes,
                           lpThreadAttributes,
                           bInheritHandles,
                           dwCreationFlags | EXTENDED_STARTUPINFO_PRESENT,
                           lpEnvironment,
                           lpCurrentDirectory,
                           &info.StartupInfo,
                           lpProcessInformation);
 }
 
 if (fInitialized) DeleteProcThreadAttributeList(lpAttributeList);
 if (lpAttributeList) HeapFree(GetProcessHeap(), 0, lpAttributeList);
 return fSuccess;
}

After some initial sanity checks, we start doing real work.

Initializing a PROC_THREAD_ATTRIBUTE_LIST is a two-step affair. First you call Initialize­Proc­Thread­Attribute­List with a NULL attribute list in order to determine how much memory you need to allocate for a one-entry attribute list. After allocating the memory, you call Initialize­Proc­Thread­Attribute­List a second time to do the actual initialization.

After creating the attribute list, you set the one entry by calling Update­Proc­Thread­Attribute­List.

And then it’s off to the races. Put that attribute list in a STARTUP­INFO­EX structure, set the EXTENDED_STARTUPINFO_PRESENT flag, and hand everything off to Create­Process.

Comments (21)
  1. John says:

    I don't understand the point of inheritable handles; maybe it makes more sense on Unix, but I am not a Unix guy.  The child process needs a way to know what the handle values are so you still have to use some kind of IPC to communicate that (even if it's just via the command line).  That being the case, what is the benefit of using inheritable handles over manually invoking DuplicateHandle() other than convenience?

    [Duplicate­Handle may not be practical if the parent and child run in different security contexts. And besides, unix has it. -Raymond]
  2. nathan_works says:

    Pipes, John, Pipes. Not win32 pipes, but unix pipes. Quick/simple IPC between parent/child.

  3. Killer{R} says:

    BTW socket handles (handles to DeviceAfd opened by winsock) are always inheritable by default and seems this is not changable, and this caused few very interesting (read – hard to find) bugs in my experince. The most complicated was heap corruption caused by overlapped socket operation that was not cancelled as app expected after called closesocket cause this app started another process and that another process inherited socket handle. Since operation was not cancelled, but app released memory (after proper closesocket) used for OVERLAPPED structure – memory got randomly corrupted. Or just not corrupted and nothing visible happened if memory was decommited on the time when pending operation complete, cause it is kernel who writes into that memory.

    BTW it was svchost process, that hosts many services and even 3rd party dlls, one of which (ours) called CreateProcess wit bInheritehandles set to TRUE.

  4. Joshua says:

    I suppose the upshot of all this is it might end up being easier in the end to use DuplicateHandleEx to transit handles between processes. Or maybe not.

  5. VS says:

    WOW! Killer{R} – good point for a blog entry ;)

  6. poday says:

    I love these API archaeology articles.  Seeing how an API has evolved/extended to cover more functionality is fascinating.  Every step is usually the best solution for the problem(s) being faced by the current tech generation but by the end the solution is significantly more complicated then the first generation's solution to a problem but still has to retain backwards compatibility.

  7. Daev says:

    Although you don't mention it, it sounds like the inheritable handles list solves another long-standing problem: the all-or-nothing nature of standard handle inheritance.  If I spawn a new console process from a non-console application, that process gets the new console's input & output as its stdin, stdout, and stderr by default.  But, in Windows XP and earlier, if I wanted to redirect the new process's stdout to a pipe back to my original program, I needed to set the standard handles to inherited by passing bInheritHandles = TRUE to CreateProcess.  I couldn't get redirected stdout but default stdin and stderr in my new process.  Now this limitation is solved — set stdout to inherited, stdin and stderr to non-inherited in the PROC_THREAD_ATTRIBUTE_HANDLE_LIST.  Right?

  8. Random832 says:

    @Karellen Windows doesn't have fork.

  9. Nick says:

    Speaking of fork and Windows, Cygwin's fork implementation is somewhat interesting, if you're curious.

    http://www.cygwin.com/…/highlights.html

  10. Karellen says:

    FD_CLOEXEC doesn't stop file descriptors being inherited by child processes across a fork(). It stops file descriptors from being persistent across replacing the current process with a new one during exec().

    Part of the point is that you can fork() without exec(). You can create a child process with an exact copy of the same address space as the parent (except for the return value of fork()), and a copy of the file descriptor table, and do multiprocessing in the child without the worry of synchronisation/mutexes. You can communicate between the child and parent using a pipe you'd set up before, because the child and parent know which are which, and therefore whether they should be read()ing from or write()ing to it.

    The child process can close any file descriptors it knows it won't be needing to use, like the end of the pipe it's not interested in, and the parent likewise.

    Similarly, if you *are* doing an exec(), the child is capable of calling close() on all the file descriptors that should not be inherited by the replacement process. Setting FD_CLOEXEC is not needed.

    Except for libraries. The problem is with libraries, which may either create file descriptors, or fork()/exec() themselves, as an implementation detail that the application is not aware of. So child processes might not be aware of all the file descriptors they're supposed to close before exec()ing. Which is why FD_CLOEXEC is now available.

  11. Killer{R} says:

    @Random832 Win32 doesn't have fork, but fork can be implemeted on NT services – NtCreateProcess can create process from parent's address space instead of file (section) + handles&views inheritance == fork

  12. Dan says:

    Why does a handle supplied as an inherit list have to be marked as inherited? In order to get safe, concurrent CreateProcess, every CreateProcess must either pass FALSE for bInheritHandles or use an explicit HANDLE list.

    If PROC_THREAD_ATTRIBUTE_HANDLE_LIST caused inheritable and non-inheritable handles alike to be inherited, then a single process could safely host both old- and new-style handle inheritance. As it is, it's an all-or-nothing affair because someone using PROC_THREAD_ATTRIBUTE_HANDLE_LIST needs to mark all inherited handled inheritable, and these inherited handles might be accidentally included in a subprocess by an old-fashioned inherit-all CreateProcess call.

  13. Crescens2k says:

    @Dan

    Are you also the kind of person who would rather all privileges be enabled on an access token so you wouldn't need to call AdjustTokenPrivilege to use them? Or maybe that functions which use them automatically enable them?

    If you did do as you want to happen, just imagine what tracking down a bug where you pass the wrong handle to the new process would be like. It makes much more sense for Windows to treat this situation as an error, and it is much more consistant with the model currently in place. That model is that a handle must be marked as inheritable before a process will inherit it.

  14. Someone says:

    @Crescens2k: Your argument is bogus, sorry. "That model is that a handle must be marked as inheritable before a process will inherit it." So what? The whole point of the new handle list for CreateProcess is to EXPLICITLY give a handle to the other process. This is a local solution for a local problem. By still requiring the handle to be marked as inheritable, you want to use a global solution for a local problem (this marking counts for all threads in your process), which is clearly wrong. I'm with Dan on that.

  15. Adam Rosenfield says:

    Those sure are a lot of hoops you have to jump through to solve this unusual problem.  Don't you also have to call DeleteProcThreadAttributeList when you're done?

    On a tangential note, I prefer using goto's to handle code like this that can fail in a number of different ways. It avoids a dozen nested if statements and the pattern shown here of if(fSuccess) { fSuccess = doNextStep(); } over and over. A good optimizing compiler should be able to optimize those into singule jumps to the appropriate location (instead of a series of jumps and tests that will always fail), but even so, I think it's harder to read.

    [Good catch on the missing Delete. Fixed. and yes, it's a bunch of hoops but at least it's a generic set of hoops that works for the other attributes too. You can wrap it into a single helper if you like. Hm, that sounds like an interesting topic for a future entry. -Raymond]
  16. Crescens2k says:

    @Someone

    Your argument is bogus, sorry. This is feels less like a global solution for a local problem and more like a double redundency on the local solution. It is the same as how your user privileges mostly require you to enable them before you can do something like take control of arbritrary files if you are an administrator.

    I'll admit that CreateProcess failing with error 87 (ERROR_INVALID_PARAMETER) is annoying because it is hard to figure out WHICH parameter it is referring to, but IMO, it is better that the call to CreateProcess fails with an error when you try to pass in the wrong handle compared to a really annoying debugging session. This is saying to Windows, "Yes I'm really sure I want to do this", and it is the same model used in other places.

    Without a doubt, if Microsoft had done what you wanted, there would be complaints from people bitten by this that there is less safety in this method. As normal Microsoft are in the situation of them being damned if they do and damned if they don't. I just prefer the situation where Windows checks that you are doing things right.

    (See, I can also claim that your argument is bogus when it is just a case of me not agreeing with your opinion. But I honestly don't see your view as wrong, where it seems like you see mine as wrong like "Your argument is bogus" heavily implies).

  17. voo says:

    @Crescens2k So you're saying it's likely that people accidentally "explicitly" add a handle to the list of inherited handles? E.g.: "So I want the process to inherit HandleA and B, so I'll just write.. HandleA, HandleC, HandleB – yep perfect".

    Seems extremely far fetched sorry, I'm with Dan and Someone there, a local solution to a local problem has less problematic side effects ("What happens if I mark this handle inheritable in my gigantic, ancient code base?") and follows the DRY principle.

  18. Crescens2k says:

    @voo

    If you can't think of a way that this can accidentally happen then it is possible that you haven't managed to sink to a low enough level.

    Of course, any example that I'd write would easily be dismissed as people can't be that stupid, but again that is just a case of you can't believe people would do something like this. But it isn't a case of the explicitly adding, but more along the lines of typoing a variable name and putting it in instead of an intended handle, or you make an array of handles multi purpose (to ease using WaitForMultipleObjects) putting the inheritable handles first and pass the wrong upper bound or when copying them into the array that you use with the handle list, you give the wrong indicese. Or if you aren't smart enough to put the inheritable handles first, you start from the wrong index completely.

    Unfortunately, it seems to be common that people can't imagine other programmers sinking to the level that you would need this kind of protection, but unfortunately we have TDWTF and other places to counter that. Unfortunately, a lot of programmers are lazy and are capable of stupid mistakes. So while you may think this kind of error isn't possible, it would seem that someone somewhere thinks otherwise.

  19. 640k says:

    @poday: This is yet an example of an API where features are added without thinking ahead of time.

    When you add a public interface/method/whatever that other should use, ask yourself: How is this going to look/being expanded/be used in 5-15 years? If you cannot answer that, you shouldn't be doing software design to begin with. Windows is usually good in this regard, heavy-weight enterprise software is usually a nightmare in this regard.

  20. Random832 says:

    @Crescens2k then clearly the solution is to have a "can inherit" flag [which has no effect on its own, unless either the file handle is in a list or the old-style CreateProcess is used] that can be set separately from (but is implied by, obviously) the old-style "inherit with old-style CreateProcess" flag.

    Requiring the latter to be set means this explicit function solves no problems and is therefore worthless.

  21. Myria says:

    I once solved this race condition in a Linux and Mac OS application by calling fork(), then closing all unnecessary open handles in the child before calling exec().  I listed the contents of the /proc/self/fd (Linux) or /dev/fd (Mac OS) pseudo-directories to get a list of handles, and closed any that weren't recognized.

    Something that is quite annoying in Windows is when an AppInit DLL decides to CreateProcess from within your process, and you can't control this at all.  Since there is no way to disable the automatic heritability of socket handles (short of doing evil things), sometimes sockets wouldn't get disconnected because some child process still had them open.

Comments are closed.