[Python-Dev] fork or exec?

Charles-François Natali cf.natali at gmail.com
Thu Jan 10 20:55:56 CET 2013


> Network servers like inetd or apache MPM (prefork) uses a process
> listening on a socket, and then fork to execute a request in a child
> process. I don't know how it works exactly, but I guess that the child
> process need a socket from the parent to send the answer to the
> client. If the socket is closed on execute (ex: Apache with CGI), it
> does not work :-)

Yes, but the above (setting close-on-exec by default) would *not*
apply to stdin, stdout and stderr. inetd servers use dup(socket, 0);
dup(socket, 1, dup(socket, 2) before forking, so it would still work.

> Example with CGIHTTPRequestHandler.run_cgi(), self.connection is the
> socket coming from accept():
>
>     self.rfile = self.connection.makefile('rb', self.rbufsize)
>     self.wfile = self.connection.makefile('wb', self.wbufsize)
>     ...
>     try:
>         os.setuid(nobody)
>     except OSError:
>         pass
>     os.dup2(self.rfile.fileno(), 0)
>     os.dup2(self.wfile.fileno(), 1)
>     os.execve(scriptfile, args, env)

Same thing here.

And the same thing holds for shell-type pipelines: you're always using
stdin, stdout or stderr.

> Do you have an example of what that "something" may be?
> Apart from standard streams, I can't think of any inherited file
> descriptor an external program would want to rely on.

Indeed, it should be really rare.

There are far more programs that are bitten by FD inheritance upon
exec than programs relying on it, and whereas failures and security
issues in the first category are hard to debug and unpredictable
(especially in a multi-threaded program), a program relying on a FD
that would be closed will fail immediately with EBADF, and so could be
updated quickly and easily.

> In other words, I think close-on-exec by default is probably a
> reasonable decision.

close-on-exec should probably have been the default in Unix, and is a
much saner option.

The only question is whether we're willing to take the risk of
breaking - admittedly a handful - of applications to avoid a whole
class of difficult to debug and potential security issues.

Note that if we do choose to set all file descriptors close-on-exec by
default, there are several questions open:
- This would hold for open(), Socket() and other high-level
file-descriptor wrappers. Should it be enabled also for low-level
syscall wrappers like os.open(), os.pipe(), etc?
- On platforms that don't support atomic close-on-exec (e.g. open()
with O_CLOEXEC, socket() with SOCK_CLOEXEC, pipe2(), etc), this would
require extra fcntl()/ioctl() syscalls. The cost is probably
negligible, but we'd have to check the impact on some benchmarks.


More information about the Python-Dev mailing list