[GHC] #7997: waitForProcess and getProcessExitCode are unsafe against asynchronous exceptions

GHC ghc-devs at haskell.org
Wed Jun 19 20:33:24 CEST 2013


#7997: waitForProcess  and getProcessExitCode are unsafe against asynchronous
exceptions
----------------------------------------+-----------------------------------
Reporter:  dfranke                      |          Owner:                   
    Type:  bug                          |         Status:  new              
Priority:  normal                       |      Component:  libraries/process
 Version:  7.6.3                        |       Keywords:                   
      Os:  Unknown/Multiple             |   Architecture:  Unknown/Multiple 
 Failure:  Incorrect result at runtime  |      Blockedby:                   
Blocking:                               |        Related:                   
----------------------------------------+-----------------------------------
 In this description of the current behavior of ''waitForProcess'', assume
 for simplicity the following:

 1. The process being waited on has already terminated.

 2. Neither ''waitForProcess'' nor ''getProcessExitCode'' has previously
 been called for this ''ProcessHandle''.

 3. ''waitpid()'' returns success on the first try; it does not get
 interrupted by a signal.

 Under these assumptions, ''waitForProcess'' currently behaves as follows:

 1. It is passed a ''ProcessHandle'' named ''ph''.  ''ProcessHandle'' is
 defined like so:

 {{{
 data ProcessHandle__ = OpenHandle PHANDLE | ClosedHandle ExitCode
 newtype ProcessHandle = ProcessHandle (MVar ProcessHandle__)
 }}}

 2. It allocate a ''CInt'' using ''alloca''; the pointer to it is named
 ''pret''.

 3. It passes ''pret'' to ''c_waitForProcess''.  ''c_waitForProcess'' makes
 a system call to ''waitpid()'', and populates ''pret'' with the result.

 4. ''waitForProcess'' peeks into ''pret'' and then mutates ''ph'',
 changing it from an ''OpenHandle'' to a ''ClosedHandle''.

 There is already a comment in the source code mentioning that this
 approach is unsafe:


 {{{
         -- don't hold the MVar while we call c_waitForProcess...
         -- (XXX but there's a small race window here during which another
         -- thread could close the handle or call waitForProcess)
 }}}

 , which is correct. However, it is also unsafe against asynchronous
 exceptions, and would remain so even if the ''MVar'' were held during the
 ''c_waitForProcess'' call.  If an asynchronous exception occurs in between
 steps 3 and 4, then the system will be left in a state in which the child
 process has been successfully waited on by the OS, but the
 ''ProcessHandle'' is still in an ''OpenHandle'' state and the exit code
 has been lost. A subsequent call to ''waitForProcess'' will result in
 ''waitpid()'' unexpectedly returning ECHILD, or worse, the OS will have
 recycled the child process's PID and ''waitpid()'' will wait on the wrong
 process.

 ''getProcessExitCode'' has the same bug.

 I propose fixing this by redefining ''ProcessHandle'' like so:

 {{{
 data ProcessHandle = ProcessHandle PHANDLE (Ptr CInt) (MVar (Ptr CInt))
 }}}

 , where the second argument maybe points to the process's return code, and
 the third argument contains a pointer to a boolean flag indicating whether
 the second argument points to something meaningful.  Extend the signature
 of ''c_waitForProcess'' to take both pointers. Then, let
 ''c_waitForProcess'' provide the following contract.  If called as
 ''c_waitForProcess ph pret pflag'':

 1. ''*pflag'' is required to be false at the start of the call.

 2. If and only if the call to ''waitpid()'' returns successfully, then
 ''*pflag'' will be set to true and ''*pret'' will be populated with the
 exit code.

 Then, ''waitForProcess'' can behave as follows:

 1. The entire routine is wrapped in ''withMVar''.  Asynchronous exceptions
 are allowed to occur.

 2. If ''*pflag'' is true, then construct an ''ExitCode'' from ''*pret''
 and return it.

 3. Otherwise, call ''c_waitForProcess'' (with the same retry behavior as
 presently), and then construct and return an ''ExitCode'' after a
 successful return.

 And ''getProcessExitCode'' can:

 1. Mask exceptions.

 2. ''tryTakeMVar''. If the ''MVar'' is already held, unmask exceptions and
 return ''Nothing''.

 3. Peek at ''*pflag''.

 4. Replace the ''MVar''.

 5. Unmask exceptions.

 6. If ''*pflag'' is true, then construct and return an ''ExitCode'' from
 ''*pret''.

 7. Otherwise, return ''Nothing''.

-- 
Ticket URL: <http://hackage.haskell.org/trac/ghc/ticket/7997>
GHC <http://www.haskell.org/ghc/>
The Glasgow Haskell Compiler



More information about the ghc-tickets mailing list