An application wants to parse and "execute" a file, and wants to assert the file is executable for security reasons.
A moments thought and you realize this initial code has a race condition that makes the security scheme ineffective:
import os
class ExecutionError (Exception):
pass
def execute_file(filepath):
"""Execute serialized command inside @filepath
The file must be executable (comparable to a shell script)
>>> execute_file(__file__) # doctest: +ELLIPSIS
Traceback (most recent call last):
...
ExecutionError: ... (not executable)
"""
if not os.path.exists(filepath):
raise IOError('"%s" does not exist' % (filepath, ))
if not os.access(filepath, os.X_OK):
raise ExecutionError('No permission to run "%s" (not executable)' %
filepath)
data = open(filepath).read()
print '"Dummy execute"'
print data
The race condition exists between
os.access(filepath, os.X_OK)
and
data = open(filepath).read()
Since there is a possibility of the file being overwritten with a non-executable file of different content between these two system calls.
The first solution I have is to change the order of the critical calls (and skip the now-redundant existance check):
fobj = open(filepath, "rb")
if not os.access(filepath, os.X_OK):
raise ExecutionError('No permission to run "%s" (not executable)' %
filepath)
data = fobj.read()
Does this solve the race condition? How can I solve it properly?
Security scheme rationale, briefly (I thought)
The file will be able to carry out arbitrary commands inside its environment, so it is comparable to a shell script.
There was a security hole on free desktops with .desktop files that define applications: The file may specify any executable with arguments, and it may choose its own icon and name. So a randomly downloaded file could hide behind any name or icon and do anything. That was bad.
This was solved by requiring that .desktop files have the executable bit set, otherwise they will not be rendered with name/icon, and the free desktop will ask the user if it wants to start the program before commencing.
Compare this to Mac OS X's very good design: "This program has been downloaded from the web, are you sure you want to open it?".
So in allegory with this, and the fact that you have to chmod +x
shell
scripts that you download, I thought about the design in the question above.
Closing words
Maybe in conclusion, maybe we should keep it simple: If the file must be executable, make it executable and let the kernel execute it when invoked by the user. Delegation of the task to where it belongs.