Home page > OCaml > Unix.open_process* and file descriptors

Unix.open_process* and file descriptors

Thursday 15 March 2012, by Toots

Hi fellow OCamlers, long time no see! :-)

I would like to mention here an issue with Unix.open_process* that may concern some of you here.

Processes opened using Unix.open_process in POSIX systems are spawned using fork(). Therefore, they inherit all file descriptors opened by the calling process.

This leads to many unfortunate minor issues. For instance, if the calling process has opened a port and crashes after calling the external process, then that port remains open until the external process terminates..

More importantly, this is a source of potentially important security issues: by default, processes spawned with Unix.open_process* will have access to any opened file or socket, allowing them to those read files’ content or sniff network traffic..

The problem is not easy to fix using the standard POSIX API. If you have control over all opened file descriptor in your code, you can use close-on-exec on each of them, which makes sure that they are closed when forking. However, for large programs this quickly turns out to be impractical, in particular if your program is using libraries that open file descriptors.

Another common solution is to emulate BSD’s closefrom() function, which closes all file descriptors higher than a given number. You can find a portable but possibly very slow implementation of this function in openssh’s code: http://anoncvs.mindrot.org/index.cgi/openssh/openbsd-compat/bsd-closefrom.c?view=markup

OCaml developers are aware of the situation but haven’t found a suitable fix for them. Here is a comment from Xavier Leroy:

Feel free to publicize this PR so that Unix experts out there can chime in.

This is an old issue that popped up during the development of Cash (the Caml shell), in particular. The "ideal" solution is to have all file descriptors in close-on-exec mode, but it requires discipline from the programmers. (File descriptors created by Pervasives.open_in and Pervasives.open_out are close-on-exec, but not those created by Unix.openfile or Unix.socket, for compatibility with the Unix specs.)

Also, a long time ago we experimented with a closeall()/closefrom() function that did not use /proc/<pid>/fd, and it was really slow to call close() on thousands of potential file descriptors. The /proc/<pid>/fd trick is nice but not terribly portable.

This issue can be a problem for some of you so I wanted with this post to properly document it. It is particularly crucial if you implement any kind of delegation to a less powerful system user using forked processes, for instance when implementing a CGI interface..

Take care y’all!

2 Forum messages

  • Unix.open_process* and file descriptors Le 15 March 2012 à 15:20 , by till

    Note that if you are doing fork/exec on a multi-threaded process you can only call async-signal safe functions (i.e. safe to call in a signal handler) between the fork and the exec. The list of those functions is really small and malloc is not one of them. You cannot implement the forked side in ocaml and need to be uber careful about your C code.

    The function that you want to close the extra fds is probably something smart that toggle between trying all the FDs to MAX_FD and reading /dev/fds. Do not trust MAX_FDs under linux 2.6 -> 3 it’s kept for compatibilty reasons but MAX_FD is now softcoded (and potentially huge). [CloseSuperfluousFds] in chromium’s source code is a good implementation of such a function:
    https://src.chromium.org/viewvc/chr...
    Readddir is not async-signal-safe so you have to choose between having potential race conditions by reading the dir before the fork and doing it on the forked side and going in unspecified behaviour territory.

    Oh and just to make things more fun the atomic support for open/pipe/... with close-on-exec was only added in the linux kernel 2.6.27 by Ulrich Drepper
    http://udrepper.livejournal.com/204...

    Fun times.

    I warn people about fork-exec every couple of months. I should probably write a blog post that summarises the issues.

    Reply to this message

    • Unix.open_process* and file descriptors Le 15 March 2012 à 19:32 , by Toots

      Our current patch loops through opened file descriptors and marks them all as close-on-exec before calling Unix.open_process. This is not perfect b/c we might still miss descriptors opened between the beginning and the end of the loop. However, in our context where we do not have serious security concerns and where file descriptors don’t pop up like crazy, it should good enough(tm).

      Reply to this message

Reply to this article