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!
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.