Search code examples
pythonlinuxsubprocesspython-oschroot

Python subprocess.run cannot execute in chroot


I have two files, a.py:

import os, subprocess

os.chroot(".")
subprocess.run(["./b.sh"])

and b.sh:

#!/usr/bin/env bash
echo whateva

in an otherwise empty directory. b.sh has executable permission. Why does python3 a.py fail with:

Traceback (most recent call last):
  File "/yadda/yadda/a.py", line 5, in <module>
  File "/usr/lib/python3.11/subprocess.py", line 548, in run
  File "/usr/lib/python3.11/subprocess.py", line 1024, in __init__
  File "/usr/lib/python3.11/subprocess.py", line 1917, in _execute_child
FileNotFoundError: [Errno 2] No such file or directory: './b.sh'

This is running as root, uname -a is Linux me 6.4.1-arch2-1 #1 SMP PREEMPT_DYNAMIC Tue, 04 Jul 2023 08:39:40 +0000 x86_64 GNU/Linux (I get the same behavior in a Ubuntu Docker container). What am I not understanding? Do I need a shell binary in the chroot?

Thank you!


Solution

  • You are missing the env executable, or it exist in a different path under your chrooted tree (i.e. not under /usr/env/). This can be easily demonstrated even without chroot. Let's remove the chroot, and change b.sh as follows:

    #!/usr/bin/no_such_file bash
    echo whateva
    

    Unix (in my case MacOS, but also Linux) will fail executing no_such_file like so:

    >>> subprocess.run(["./b.sh"])
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "/Library/Developer/CommandLineTools/Library/Frameworks/Python3.framework/Versions/3.9/lib/python3.9/subprocess.py", line 505, in run
        with Popen(*popenargs, **kwargs) as process:
      File "/Library/Developer/CommandLineTools/Library/Frameworks/Python3.framework/Versions/3.9/lib/python3.9/subprocess.py", line 951, in __init__
        self._execute_child(args, executable, preexec_fn, close_fds,
      File "/Library/Developer/CommandLineTools/Library/Frameworks/Python3.framework/Versions/3.9/lib/python3.9/subprocess.py", line 1821, in _execute_child
        raise child_exception_type(errno_num, err_msg, err_filename)
    FileNotFoundError: [Errno 2] No such file or directory: './b.sh'
    >>>
    

    But simply changing it to a valid executable (back to env) will load just fine:

    >>> subprocess.run(["./b.sh"])
    whateva
    CompletedProcess(args=['./b.sh'], returncode=0)
    >>>
    

    If you don't need the shell script to be portable, and as you mentioned in a comment that you already have a bash cross-compiled binary, you can simply use that instead of env, such as #!/bin/bash. The path to the binary must be within the chroot environment, so in case it resides elsewhere - make sure to update it accordingly.

    Diving a bit deeper, execve(2)'s man page somewhat explains that ENOENT is set if the executable cannot be found. I've also found this page which might provide a better explanation as to why the failure seems to be related to b.sh existence instead of the binary itself.