Sdílet prostřednictvím


Subtle bugs #2

Came across this in some code recently.  The code excerpt below is part of a larger cmd line application that can be run as both a console app and a service.  One of the things it can do is start other processes, optionally trapping their output in a file it has previously opened.  The code compiles and runs fine in most circumstances, but there's a subtle bug in it.  Can you find it?

  STARTUPINFO siStartup;

   ...

  ZeroMemory(&siStartup,sizeof(siStartup));
  siStartup.cb=sizeof(siStartup);

   ...
  
  if (ISVALIDHANDLE(hChildOutputTrap)) {
    siStartup.dwFlags=siStartup.dwFlags | STARTF_USESTDHANDLES;
    siStartup.hStdOutput=hChildOutputTrap;
    siStartup.hStdError=hChildOutputTrap; 
    siStartup.hStdInput=GetStdHandle(STD_INPUT_HANDLE);
  }

  bRes=(CreateProcess((LPCTSTR)strExecStr,pszParms,NULL,NULL,true,dwPriority | CREATE_NEW_PROCESS_GROUP,
   NULL,pszDir,&siStartup,pProcInfo)>0);

By virtue of setting STARTF_USESTDHANDLES in st.Starup.dwFlags, we are telling the subprocess to use the console handles we give it in the STARTUPINFO structure.  We redirect its stdout and stderr to our previously-opened child process output trap file.  For stdin, however, we just supply our own stdin handle, which we retrieve via the Win32 GetStdHandle(STD_INPUT_HANDLE) call. 

What's wrong with this code?  Recall that the app can be ran as either a console app or a service.  Service's don't have a stdin handle, and a GetStdHandle(STD_INPUT_HANDLE) call in a service will return NULL.  Some applications don't behave well when handed a NULL stdin handle, most notably many .exe-based OS commands.  XCopy.exe will fail to run at all, Find.exe will AV, and many other .exe-based OS command will do something similar.  I suspect many console-mode user applications would behave the same way.  The subtle bug here is only seen when a particular type of .exe is called -- an .exe that is probably exhibiting buggy behavior itself by not handling the possibility that it may not receive a console from its caller.

You fix this by sending a valid handle in for stdin in siStartup.  Also, if you happen to have source for the process you're calling, it wouldn't hurt to make it more tolerant of not being provided a console.