Programmatically controlling which handles are inherited by new processes in Win32

Raymond Chen

In unix, file descriptors are inherited by child processes by default.This wasn’t so much an active decision as it was a consequenceof 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 littleupside-down.And in high heels.

In Win32, handles default to not inherited.Ways to make a handle inherited duringCreate­Processhave 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 passinga SECURITY_ATTRIBUTES structurewith bInherit­Handle setto TRUE.Functions which created handles from existing objects don’t havea SECURITY_ATTRIBUTES parameter,so they instead have an explicit bInherit­Handleparameter.(For examples, seeOpen­Event and Duplicate­Handle.)

But just marking a handle as inheritable isn’t good enough to getit inherited.You also have to passTRUEas thebInherit­Handles parameter to Create­Process.A handle will be inherited only ifif the bInherit­Handles parameter isTRUE 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 handleafter it is created.TheSet­Handle­Information functionlets you turn the HANDLE_FLAG_INHERITflag 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 callCreate­Process but disagree on which handlesthey want to be inherited?For example, suppose you have a functionCreate­Process­With­Shared­Memorywhose job it is tolaunch a process, passing ita custom-made shared memory block.Suppose two threads run this function simultaneously.

A B
CreateFileMapping(inheritable=TRUE)CreateFileMapping(inheritable=TRUE)
returns handle H1returns 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 handlesH1 and H2, even though what we wanted wasfor process A to inherit handle H1 andfor 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 toCreate­Process­With­Shared­Memoryso that thread B won’t accidentally cause a handle fromthread A to be inherited by process B.”Actually, the answer was even worse.You had to serialize all functions that created inheritablehandles from the time the handle was created,through the call toCreate­Process,and waiting until after all those inheritable handles were madeno longer inheritable.

This was a serious problem since who knows what other parts ofyour program are going to call Create­Processwith 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 isanother case ofkernel-colored glasses.)

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

Passing the list of handles you want to inherit is a multi-stepaffair.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_LISTis a two-step affair.First you callInitialize­Proc­Thread­Attribute­Listwith a NULL attribute list in order to determine howmuch memory you need to allocate for a one-entry attribute list.After allocating the memory, you callInitialize­Proc­Thread­Attribute­Lista second time to do the actual initialization.

After creating the attribute list, you set the one entryby callingUpdate­Proc­Thread­Attribute­List.

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