This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: [Security] "python3 directory" inserts "directory" at sys.path[0] even in isolated mode
Type: security Stage: resolved
Components: Versions: Python 3.7, Python 3.6, Python 2.7
process
Status: closed Resolution: not a bug
Dependencies: Superseder:
Assigned To: Nosy List: brett.cannon, christian.heimes, ncoghlan, steve.dower, vstinner
Priority: normal Keywords:

Created on 2017-12-14 16:09 by vstinner, last changed 2022-04-11 14:58 by admin. This issue is now closed.

Messages (11)
msg308310 - (view) Author: STINNER Victor (vstinner) * (Python committer) Date: 2017-12-14 16:09
Christian Heimes, author of the -I option (isolated mode), asked me to open an issue to check if the following behaviour is correct (safe in term of security).

"python3 directory" inserts "directory" at sys.path[0], even in isolated mode. Example:
---
vstinner@apu$ mkdir directory
vstinner@apu$ echo "import pprint, sys; pprint.pprint(sys.path)" > directory/__main__.py

vstinner@apu$ python3 directory
['directory',
 '/usr/lib64/python3.6',
 ...]

# Same behaviour with -I
vstinner@apu$ python3 -I directory
['directory',
 '/usr/lib64/python3.6',
 ...]
---


Same behaviour for a ZIP file:
---
vstinner@apu$ cd directory/
vstinner@apu$ zip ../testzip.zp __main__.py 
  adding: __main__.py (deflated 20%)
vstinner@apu$ cd ..
vstinner@apu$ python3 testzip.zip
python3: can't open file 'testzip.zip': [Errno 2] No such file or directory
vstinner@apu$ mv testzip.zp testzip.zip 
'testzip.zp' -> 'testzip.zip'

vstinner@apu$ python3 testzip.zip
['testzip.zip',
 '/usr/lib64/python3.6',
 ...]

# Same behaviour with -I
vstinner@apu$ python3 -I testzip.zip
['testzip.zip',
 '/usr/lib64/python3.6',
 ...]
---

The -I option:
https://docs.python.org/dev/using/cmdline.html#id2
msg308311 - (view) Author: STINNER Victor (vstinner) * (Python committer) Date: 2017-12-14 16:13
"python3 directory" and "python3 testzip.zip" are implemented by calling runpy._run_module_as_main("__main__", set_argv0). Currently, sys.path is modified before calling _run_module_as_main().

Would it be possible to pass an additional path to importlib to import __main__, without modifying sys.path?
msg308313 - (view) Author: STINNER Victor (vstinner) * (Python committer) Date: 2017-12-14 16:23
See also https://github.com/python/cpython/pull/4868 : I propose a change to factorize the code, but the side effect is that sys.path is now modified before "import readline" (when using the -i option, like "python3 -i directory").
msg308331 - (view) Author: Brett Cannon (brett.cannon) * (Python committer) Date: 2017-12-14 19:41
Since the directory is where the code that is being executed exists don't you have to implicitly trust that directory is secure? Otherwise how can you even trust the code you're choosing to execute?
msg308390 - (view) Author: STINNER Victor (vstinner) * (Python committer) Date: 2017-12-15 13:53
> Since the directory is where the code that is being executed exists don't you have to implicitly trust that directory is secure? Otherwise how can you even trust the code you're choosing to execute?

The question is if the following example must work with -I:

---
vstinner@apu$ mkdir directory
vstinner@apu$ echo "import submodule" > directory/__main__.py
vstinner@apu$ echo 'print("submodule", __file__)' > directory/submodule.py

vstinner@apu$ python3 directory
submodule directory/submodule.py

vstinner@apu$ python3 -I directory
submodule directory/submodule.py
---

Do you expect that "python3 directory" allows imports from directory/?

The second question is if directory must be prepended to sys.path (start), or if it must be appended to sys.path (end)?

Prepend allows to override stdlib imports, whereas append risks of conflicts with others paths in sys.path and so loading the "wrong" submodule.

For example, I still expect to run __main__ from directory in the following example:

---
vstinner@apu$ mkdir other
vstinner@apu$ echo "print('other main')" > other/__main__.py
vstinner@apu$ PYTHONPATH=other python3 directory
submodule directory/submodule.py
---
msg308431 - (view) Author: Brett Cannon (brett.cannon) * (Python committer) Date: 2017-12-15 23:22
I vote that it has to work and it should be prepended.
msg308478 - (view) Author: Steve Dower (steve.dower) * (Python committer) Date: 2017-12-16 16:33
The more interesting part is the relative directory and how often is it resolved? Can you change a program's imports by inducing it to change its working directory immediately after startup (or before any lazy imports)? If so, we should resolve it to a full path when adding it, regardless of -I.

Having the first entry there is required for local imports to work from the startup script, so I don't see it going anywhere. There have been many discussions about how to deal with the shadowing "problem", but the eventual answer was consenting adults.
msg308599 - (view) Author: STINNER Victor (vstinner) * (Python committer) Date: 2017-12-19 00:50
A compromise would be to refuse to start on "python3 -I directory" to remain secure and respect -I documentation:

https://docs.python.org/dev/using/cmdline.html#id2

"In isolated mode sys.path contains neither the script’s directory nor the user’s site-packages directory."

Example of error message:

"Running a Python package is incompatible with the isolated mode (-I option)"
msg308617 - (view) Author: Steve Dower (steve.dower) * (Python committer) Date: 2017-12-19 06:37
> A compromise would be to refuse to start on "python3 -I directory" to remain secure

What would that achieve, considering that "python3 -I directory/__main__.py" would let you start with exactly the same sys.path?[*]

The only change that might be of any value would be to resolve the path as early as possible so that an absolute path is added to sys.path[0]. 

Not adding the directory of the startup script is a breaking change with no security benefits -- it has to stay there. -I is not a protection against command-line arguments.

[*] On Windows, using the filename seems to resolve the directory while using just the directory name does not. I'm not sure why they aren't identical, and obviously I think they should be, but I'd expect the initialization work to streamline it (when getpath.c becomes sensible).
msg309050 - (view) Author: Nick Coghlan (ncoghlan) * (Python committer) Date: 2017-12-26 00:19
When executing a sys.path entry, you're executing that *entire* entry (whether it's a directory or zipfile). This isn't a bug, and it isn't in conflict with the assurances offered by isolated mode (it would only be a problem if running "~/code/mydirectory" added "~/code" to sys.path).
msg309051 - (view) Author: Nick Coghlan (ncoghlan) * (Python committer) Date: 2017-12-26 00:22
(However, the behaviour Steve is describing suggests that sys.path[0] initialisation may have problems on Windows that the test suite isn't picking up - "-I" should *not* add the script directory to the path, and directory execution should always add an absolute path, not a relative one)
History
Date User Action Args
2022-04-11 14:58:55adminsetgithub: 76505
2017-12-26 00:22:36ncoghlansetmessages: + msg309051
2017-12-26 00:19:41ncoghlansetstatus: open -> closed
resolution: not a bug
messages: + msg309050

stage: resolved
2017-12-19 06:37:49steve.dowersetmessages: + msg308617
2017-12-19 00:50:48vstinnersetmessages: + msg308599
2017-12-16 16:33:41steve.dowersetmessages: + msg308478
2017-12-15 23:22:10brett.cannonsetmessages: + msg308431
2017-12-15 13:53:23vstinnersetmessages: + msg308390
2017-12-14 19:41:01brett.cannonsetnosy: + christian.heimes
messages: + msg308331
2017-12-14 16:23:30vstinnersetmessages: + msg308313
2017-12-14 16:13:38vstinnersetnosy: + brett.cannon, ncoghlan
messages: + msg308311
2017-12-14 16:09:15vstinnercreate