another general problem:
between the time a descriptor is allocated by the kernel (by open(), pipe(), socket(), accept(), etc.) and the time that a child cleanup can be registered for it, apr_proc_create() could have fork()-ed and allowed a descriptor to be inappropriately inherited
in order to fix this problem in general:
Establish a critical section covering the path between descriptor allocation and registration of the cleanup; any number of threads allocating descriptors can enter that critical section at any given time, but a thread performing a fork() must have exclusive access (read-only access vs. read-write access).
APIs that allocate descriptors may allow the caller to specify whether or not the file should be inherited, and APIs without such a parameter default to not-inherited and the user can call apr_foo_inherit_set(), as with apr_file_open() currently.
issues:
open() and accept() are potentially blocking... we can't hold the mutex for this critical section across an open() or accept() since that could deadlock the APR application. Thus, the descriptor would be allocated by the kernel and inherited by new processes created by other threads in the window prior to registering a child cleanup.
hack for open(): disable blocking on the open of a named pipe by passing O_NONBLOCK (not without side-effects)
hack for accept() on a blocking socket: make it non-blocking temporarily, wait in select() or poll() until appropriate time, get mutex, call accept(), release mutex
--/--
back to the original complaint about pipes created by apr_procattr_io_set() and the related apr_proc_create()...
the client side of those pipes actually must be inherited by the one new process intended to use them, so whatever was done to avoid arbitrary processes inheriting them must be undone for the intended process in apr_proc_create() while holding the critical section