I have a Python script foo.py
that I want to invoke with a second Python script using the subprocess
module. The code in foo.py
is provided by a user, and it could be malicious. I have set some security features in place (e.g., resource limits, change UID) prior to running the code to minimize the damage that the user can do.
I'd like to invoke the script with shell=True
kwarg in the subprocess.run
command because I am setting resource limits at the UID level. It seems like these limits do not apply if I do not add this kwarg. My code looks something like this:
import os
import subprocess
def enable_extra_security() -> None:
# set a different user ID for security reasons
os.setuid(1234)
# limit CPU time usage
resource.setrlimit(resource.RLIMIT_CPU, (5, 5))
# limit file size creation
resource.setrlimit(resource.RLIMIT_FSIZE, (100_000, 100_000))
# prevent forking
resource.setrlimit(resource.RLIMIT_NPROC, (5, 5))
if __name__ == '__main__':
# this script is run as root.
pid = os.fork()
if pid == 0:
# child process
enable_extra_security()
res = subprocess.run(['/usr/bin/python', 'foo.py'], shell=True, capture_output=True)
# ...
# child does some processing based on the returncode and output.
# ...
else:
# parent process
os.waitpid(pid, 0)
# ...
# ...
When I invoke enable_extra_security()
, it only applies the additional security features to the child process. Specifically, the limits are only imposed on user ID 1234, which is only set for the child process. If I run subprocess.run
without shell=True
, the security features vanish. Unfortunately, the code above does not work: the subprocess.run
command will actually just start a Python console and hang (i.e., it does not seem to pass in "foo.py" as an argument).
Interestingly, the code does work for C and C++ code (if I were to replace /usr/bin/python
with /usr/bin/gcc
and instead put a .c
or .cpp
file, it would work just fine). Why is this happening, and how can I solve my problem?