0

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?

Joey
  • 209
  • 2
  • 7
  • If you set `shell=True` you have to pass the command as string, not as list to `subprocess.run` => `subprocess.run('/usr/bin/python foo.py', shell=True, capture_output=True)` – phibel Jan 09 '23 at 03:08
  • Okay, thanks. Why do I have to do that for Python programs but not e.g., C or C++ programs? – Joey Jan 09 '23 at 03:09
  • See comments on question: https://stackoverflow.com/questions/3172470/actual-meaning-of-shell-true-in-subprocess – phibel Jan 09 '23 at 03:15

0 Answers0