GeezerVison is showing github.com — sys.executable is sometimes wrong · Issue #124241 · python/cpython
sys.executable is sometimes wrong #124241Labels interpreter-core(Objects, Python, Grammar, and Parser dirs)(Objects, Python, Grammar, and Parser dirs)type-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error DescriptionBug report
Bug description:
On Linux, sys.executable is determined using argv[0], and trying to find that in the PATH. This presumes that the Python interpreter has been started by a shell that interprets PATH in the canonical way. There is no guarantee that this happens at all, so sys.executable may point to something completely different, or is even the empty string.
As an example:
bash -c 'exec -a whatever python -c "import sys;print(sys.executable)"'
should output the path of the Python executable, but simply outputs nothing.
Even worse, one can make Python output a wrong executable, as in
bash -c 'exec -a /usr/bin/python python -c "import sys;print(sys.executable)"'
CPython versions tested on:
CPython main branch
Operating systems tested on:
Linux
Linked PRs
Activityskirpichev commented on Sep 19, 2024 I can confirm first behaviour:
sk@note:~/src/peps $ which python
sk@note:~/src/peps $ which python3
/usr/local/bin/python3
sk@note:~/src/peps $ bash -c 'exec -a whatever python3 -c "import sys;print(sys.executable)"'
But not the second:
$ bash -c 'exec -a /usr/local/bin/python3 python3 -c "import sys;print(sys.executable)"'
/usr/local/bin/python3
Could you, please, show us output of which python?
Anyway, this seems to be documented: "If Python is unable to retrieve the real path to its executable, sys.executable will be an empty string or None."
Meanwhile, I'll relabel your issue as a feature request. ZeroIntensity commented on Sep 19, 2024 @skirpichev, maybe this is a permissions issue? I'm on a root user, and I was able to reproduce the second behavior. Running the following on my results in /usr/bin/echo being printed:
$ bash -c 'exec -a echo python3 -c "import sys;print(sys.executable)"'
/usr/bin/echo
Interestingly, running it with a path that doesn't exist results in a fatal error:
$ bash -c 'exec -a evil python3 -c "import sys;print(sys.executable)"'
Fatal Python error: init_fs_encoding: failed to get the Python codec of the filesystem encoding
Python runtime state: core initialized
ModuleNotFoundError: No module named 'encodings'
Current thread 0x00007f8899f14080 (most recent call first):
<no Python frame>
Injection of arbitrary files into sys.executable seems like a security problem to me (but maybe not if this only occurs on root users). skirpichev commented on Sep 19, 2024
maybe this is a permissions issue?
Maybe PATH settings?
That does work for me and doesn't look right:
$ bash -c 'exec -a /usr/bin/evil python3 -c "import sys;print(sys.executable)"'
/usr/bin/evil
Edit:
Perhaps, this should mentioned somehow in docs.
Injection of arbitrary files into sys.executable seems like a security problem
I doubt you can do too much with this mechanism. Unless there are other options to override arg0. There are e.g. various exec*() calls in POSIX, but it's essentially same. Yes, you can abuse mechanism for yourself (regardless on your rights), but what you gain? I doubt this will be a security issue without decent part of social engineering;) ZeroIntensity commented on Sep 19, 2024 I could see it being a problem for applications that use subprocess.blah_blah([sys.executable, "..."))
It's not a terrible vulnerability, but e.g. a user on a Linux machine owned by their company could use it to mess with whatever software the employer has installed to their computer. (Kind of a bad example, but you get the general idea.) skirpichev commented on Sep 19, 2024 But what evil you could do with this, assuming you can point sys.executable to an arbitrary file? Company software will execute one?
Wait, but that means that someone else put in system some file, that you already can run (suitable permissions, including executable bit). Then why not run directly? ZeroIntensity commented on Sep 19, 2024 Possibly, I'm just speculating 😃
Regardless, this is a bug. I think we should try to stop sys.executable from holding the wrong thing, rather than just documenting it. skirpichev commented on Sep 20, 2024
I think we should try to stop sys.executable from holding the wrong thing
I don't know universal ways to do so. E.g. on Linux (and I think BSD nowadays), you can use /proc//exe symlink. ZeroIntensity commented on Sep 20, 2024
I don't know universal ways to do so.
Me neither. But I'm not too sure who to CC on this. I guess if this only affects Linux then /proc/exe should work. skirpichev commented on Sep 20, 2024
I guess if this only affects Linux
No, I think it should affect any system with POSIX exec*() functions, for example. tecki commented on Sep 21, 2024 ContributorAuthor More actions I tried to solve the problem myself, but I am now convinced that it is impossible. Let me explain.
sys.executable contains the executable before symlinks have been resolved. Unfortunately this is information is stored nowhere, so what getpath.py does is to guess what the shell probably did to get from argv[0] to the path of the executable. Unfortunately even good ole bash has some subtleties which are not reflected in getpath.py. This can lead to a python interpreter using another interpreters standard library.
Now one could argue: who cares about the path before symlink resolution, as long as sys.executable is pointing to the right file, all is OK, just set it to what /proc/self/exe points to, and we are done.
Unfortunately, venv cares. The python interpreter looks for pyenv.cfg at the place where the symlink is. This has interesting consequences, e.g. if you create a symlink to the interpreter of a venv, or to its containing directory from elsewhere, it will suddenly behave like the original, non-venv interpreter again.
I personally don't like that python interpreters behave different if they are a symlink, IMHO a symlink should just be transparent.
venvs could just use a stub file to refer to the original interpreter, using a file with a content like
#!/path/to/original/python -Y
where -Y would be a new command line option with the meaning "use this file for calculating the prefix for libraries". mxmlnkn commented on Jul 30, 2025 I have stumbled upon this problem with my AppImage. I tried to reduce the number of unnecessarily open processes by calling the last command in the AppRun shell script with exec. That last command is Python, i.e., exec python3 .... The output is something like this: sys.executable: /projects/ratarmount/worktrees/1/ratarmount-x86_64.AppImage.
I would have thought that changing exec "${PYTHON3[0]}" "${ARGS[@]}" to exec -a "${PYTHON3[0]}" "${PYTHON3[0]}" "${ARGS[@]}" would have helped, but it changes nothing. sys.executable still points to the AppImage. Furthermore, the -a option is only bash 4.2+ anyway, not POSIX.
Trying with exec -a python3 "${PYTHON3[0]}" "${ARGS[@]}" gives me:
./ratarmount-x86_64.AppImage
Could not find platform independent libraries <prefix>
Could not find platform dependent libraries <exec_prefix>
Python path configuration:
PYTHONHOME = (not set)
PYTHONPATH = (not set)
program name = 'python3'
isolated = 1
environment = 0
user site = 0
safe_path = 1
import site = 1
is in build tree = 0
stdlib dir = '/opt/_internal/cpython-3.12.11/lib/python3.12'
sys._base_executable = '/tmp/.mount_ratarmblPObH/usr/bin/python3'
sys.base_prefix = '/opt/_internal/cpython-3.12.11'
sys.base_exec_prefix = '/opt/_internal/cpython-3.12.11'
sys.platlibdir = 'lib'
sys.executable = '/tmp/.mount_ratarmblPObH/usr/bin/python3'
sys.prefix = '/opt/_internal/cpython-3.12.11'
sys.exec_prefix = '/opt/_internal/cpython-3.12.11'
sys.path = [
'/opt/_internal/cpython-3.12.11/lib/python312.zip',
'/opt/_internal/cpython-3.12.11/lib/python3.12',
'/opt/_internal/cpython-3.12.11/lib/python3.12/lib-dynload',
]
Fatal Python error: init_fs_encoding: failed to get the Python codec of the filesystem encoding
Python runtime state: core initialized
ModuleNotFoundError: No module named 'encodings'
Current thread 0x0000754a239a7740 (most recent call first):
<no Python frame>
Exporting PYTHONHOME does not work because -I (isolated mode) is used in the AppImage. mxmlnkn commented on Jul 30, 2025 FFY00 commented on Mar 3, 2026 On most Linux systems, this could be solved by reading the symlink target of /proc/self/exe.
MetadataMetadatainterpreter-core(Objects, Python, Grammar, and Parser dirs)(Objects, Python, Grammar, and Parser dirs)type-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error DevelopmentNo branches or pull requestsIssue actions
|