[Python-Dev] Weird error handling in os._execvpe

Zack Weinberg zack@codesourcery.com
Thu, 1 Aug 2002 11:23:40 -0700

On Thu, Aug 01, 2002 at 01:23:03PM -0400, Guido van Rossum wrote:
> > Can anyone explain why it is done this way?
> Because not all systems report the same error for this error condition
> (attempting to execute a file that doesn't exist).

That's unfortunate.  The existing code is buggy on at least three

First and most important, it's currently trivial to cause any program
that uses os.execvp[e] to invoke a program of the attacker's choice,
rather than the intended one, on any platform that supports symbolic
links and has predictable PIDs.  My tempfile rewrite will make this
much harder, but still not impossible.

Second, the BeOS code will silently delete the file '/_#.# ## #.#' if
it exists, which is unlikely, but not impossible.  A user who had
created such a file would certainly be surprised to discover it gone
after running an apparently-innocuous Python program.

Third, if an error other than the expected one comes back, the loop
clobbers the saved exception info and keeps going.  Consider the
situation where PATH=/bin:/usr/bin, /bin/foobar exists but is not
executable by the invoking user, and /usr/bin/foobar does not exist.
The exception thrown will be 'No such file or directory', not the
expected 'Permission denied'.

Also, I'm not certain what will happen if two threads go through the
if not _notfound: block at the same time, but it could be bad,
depending on how much implicit locking there is in the interpreter.

I see three possible fixes.  In order of personal preference:

1. Make os.execvp[e] just call the C library's execvp[e]; it has to
   get this stuff right anyway.  We are already counting on it for
   execv - I would be surprised to find a system that had execv and
   not execvp, as long as PATH was a meaningful concept (it isn't, for
   instance, on classic MacOS).

2. Enumerate all the platform-specific errno values for this failure
   mode, and check them all.  On Unix, ENOENT and arguably ENOTDIR.  I
   don't know about others.

3. If we must do the temporary file thing, create a temporary
   _directory_; we control the contents of that directory, so we can
   be sure that the file name we choose does not exist.  Cleanup is
   messier than the other two possibilities.