Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gh-57684: Add -P cmdline option and PYTHONSAFEPATH env var #31542

Merged
merged 2 commits into from
May 5, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 36 additions & 9 deletions Doc/c-api/init_config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,24 @@ PyConfig

See also the :c:member:`~PyConfig.orig_argv` member.

.. c:member:: int safe_path

If equals to zero, ``Py_RunMain()`` prepends a potentially unsafe path to
:data:`sys.path` at startup:

* If :c:member:`argv[0] <PyConfig.argv>` is equal to ``L"-m"``
(``python -m module``), prepend the current working directory.
* If running a script (``python script.py``), prepend the script path.
If it's a symbolic link, resolve symbolic links.
* Otherwise (``python -c code`` and ``python``), prepend an empty string.

Set to 1 by the :option:`-P` command line option and the
:envvar:`PYTHONSAFEPATH` environment variable.

Default: ``0`` in Python config, ``1`` in isolated config.

.. versionadded:: 3.11

.. c:member:: wchar_t* base_exec_prefix

:data:`sys.base_exec_prefix`.
Expand Down Expand Up @@ -809,13 +827,14 @@ PyConfig

If greater than 0, enable isolated mode:

* :data:`sys.path` contains neither the script's directory (computed from
``argv[0]`` or the current directory) nor the user's site-packages
directory.
* Set :c:member:`~PyConfig.safe_path` to 1:
don't prepend a potentially unsafe path to :data:`sys.path` at Python
startup.
* Set :c:member:`~PyConfig.use_environment` to 0.
* Set :c:member:`~PyConfig.user_site_directory` to 0: don't add the user
site directory to :data:`sys.path`.
* Python REPL doesn't import :mod:`readline` nor enable default readline
configuration on interactive prompts.
* Set :c:member:`~PyConfig.use_environment` and
:c:member:`~PyConfig.user_site_directory` to 0.

Default: ``0`` in Python mode, ``1`` in isolated mode.

Expand Down Expand Up @@ -1029,12 +1048,13 @@ PyConfig
.. c:member:: wchar_t* run_filename

Filename passed on the command line: trailing command line argument
without :option:`-c` or :option:`-m`.
without :option:`-c` or :option:`-m`. It is used by the
:c:func:`Py_RunMain` function.

For example, it is set to ``script.py`` by the ``python3 script.py arg``
command.
command line.

Used by :c:func:`Py_RunMain`.
See also the :c:member:`PyConfig.skip_source_first_line` option.

Default: ``NULL``.

Expand Down Expand Up @@ -1419,9 +1439,16 @@ site-package directory to :data:`sys.path`.
The following configuration files are used by the path configuration:

* ``pyvenv.cfg``
* ``python._pth`` (Windows only)
* ``._pth`` file (ex: ``python._pth``)
* ``pybuilddir.txt`` (Unix only)

If a ``._pth`` file is present:

* Set :c:member:`~PyConfig.isolated` to 1.
* Set :c:member:`~PyConfig.use_environment` to 0.
* Set :c:member:`~PyConfig.site_import` to 0.
* Set :c:member:`~PyConfig.safe_path` to 1.

The ``__PYVENV_LAUNCHER__`` environment variable is used to set
:c:member:`PyConfig.base_executable`

Expand Down
24 changes: 16 additions & 8 deletions Doc/library/sys.rst
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,7 @@ always available.
:const:`hash_randomization` :option:`-R`
:const:`dev_mode` :option:`-X dev <-X>` (:ref:`Python Development Mode <devmode>`)
:const:`utf8_mode` :option:`-X utf8 <-X>`
:const:`safe_path` :option:`-P`
============================= ================================================================

.. versionchanged:: 3.2
Expand All @@ -539,6 +540,9 @@ always available.
Mode <devmode>` and the ``utf8_mode`` attribute for the new :option:`-X`
``utf8`` flag.

.. versionchanged:: 3.11
Added the ``safe_path`` attribute for :option:`-P` option.


.. data:: float_info

Expand Down Expand Up @@ -1138,15 +1142,19 @@ always available.
the environment variable :envvar:`PYTHONPATH`, plus an installation-dependent
default.

As initialized upon program startup, the first item of this list, ``path[0]``,
is the directory containing the script that was used to invoke the Python
interpreter. If the script directory is not available (e.g. if the interpreter
is invoked interactively or if the script is read from standard input),
``path[0]`` is the empty string, which directs Python to search modules in the
current directory first. Notice that the script directory is inserted *before*
the entries inserted as a result of :envvar:`PYTHONPATH`.
By default, as initialized upon program startup, a potentially unsafe path
is prepended to :data:`sys.path` (*before* the entries inserted as a result
of :envvar:`PYTHONPATH`):

* ``python -m module`` command line: prepend the current working
directory.
* ``python script.py`` command line: prepend the script path.
vstinner marked this conversation as resolved.
Show resolved Hide resolved
If it's a symbolic link, resolve symbolic links.
* ``python -c code`` and ``python`` (REPL) command lines: prepend an empty
string.
vstinner marked this conversation as resolved.
Show resolved Hide resolved

The initialization of :data:`sys.path` is documented at :ref:`sys-path-init`.
To not prepend this potentially unsafe path, use the :option:`-P` command
line option or the :envvar:`PYTHONSAFEPATH` environment variable?

A program is free to modify this list for its own purposes. Only strings
and bytes should be added to :data:`sys.path`; all other data types are
Expand Down
31 changes: 30 additions & 1 deletion Doc/using/cmdline.rst
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,8 @@ Miscellaneous options
Ignore all :envvar:`PYTHON*` environment variables, e.g.
:envvar:`PYTHONPATH` and :envvar:`PYTHONHOME`, that might be set.

See also the :option:`-P` and :option:`-I` (isolated) options.


.. cmdoption:: -i

Expand All @@ -271,7 +273,9 @@ Miscellaneous options

.. cmdoption:: -I

Run Python in isolated mode. This also implies -E and -s.
Run Python in isolated mode. This also implies :option:`-E`, :option:`-P`
and :option:`-s` options.

In isolated mode :data:`sys.path` contains neither the script's directory nor
the user's site-packages directory. All :envvar:`PYTHON*` environment
variables are ignored, too. Further restrictions may be imposed to prevent
Expand Down Expand Up @@ -301,6 +305,23 @@ Miscellaneous options
Modify ``.pyc`` filenames according to :pep:`488`.


.. cmdoption:: -P

Don't prepend a potentially unsafe path to :data:`sys.path`:

* ``python -m module`` command line: Don't prepend the current working
directory.
* ``python script.py`` command line: Don't prepend the script path.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* ``python script.py`` command line: Don't prepend the script path.
* ``python script.py`` command line: Don't prepend the script's directory.

If it's a symbolic link, resolve symbolic links.
* ``python -c code`` and ``python`` (REPL) command lines: Don't prepend an
empty string.

See also the :envvar:`PYTHONSAFEPATH` environment variable, and :option:`-E`
and :option:`-I` (isolated) options.

.. versionadded:: 3.11


.. cmdoption:: -q

Don't display the copyright and version messages even in interactive mode.
Expand Down Expand Up @@ -583,6 +604,14 @@ conflict.
within a Python program as the variable :data:`sys.path`.


.. envvar:: PYTHONSAFEPATH

If this is set to a non-empty string, don't prepend a potentially unsafe
path to :data:`sys.path`: see the :option:`-P` option for details.

.. versionadded:: 3.11


.. envvar:: PYTHONPLATLIBDIR

If this is set to a non-empty string, it overrides the :data:`sys.platlibdir`
Expand Down
10 changes: 10 additions & 0 deletions Doc/whatsnew/3.11.rst
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,11 @@ Other Language Changes
pickles instance attributes implemented as :term:`slots <__slots__>`.
(Contributed by Serhiy Storchaka in :issue:`26579`.)

* Add :option:`-P` command line option and :envvar:`PYTHONSAFEPATH` environment
variable to not prepend a potentially unsafe path to :data:`sys.path` such as
the current directory, script directory or empty string.
(Contributed by Victor Stinner in :gh:`57684`.)


Other CPython Implementation Changes
====================================
Expand Down Expand Up @@ -636,6 +641,9 @@ sys
(equivalent to ``sys.exc_info()[1]``).
(Contributed by Irit Katriel in :issue:`46328`.)

* Add the :data:`sys.flags.safe_path <sys.flags>` flag.
(Contributed by Victor Stinner in :gh:`57684`.)


sysconfig
---------
Expand Down Expand Up @@ -1480,6 +1488,8 @@ New Features
representation of exceptions.
(Contributed by Irit Katriel in :issue:`46343`.)

* Added the :c:member:`PyConfig.safe_path` member.
(Contributed by Victor Stinner in :gh:`57684`.)

Porting to Python 3.11
----------------------
Expand Down
1 change: 1 addition & 0 deletions Include/cpython/initconfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ typedef struct PyConfig {
#endif
wchar_t *check_hash_pycs_mode;
int use_frozen_modules;
int safe_path;

/* --- Path configuration inputs ------------ */
int pathconfig_warnings;
Expand Down
6 changes: 4 additions & 2 deletions Lib/subprocess.py
Original file line number Diff line number Diff line change
Expand Up @@ -313,12 +313,14 @@ def _args_from_interpreter_flags():
args.append('-E')
if sys.flags.no_user_site:
args.append('-s')
if sys.flags.safe_path:
args.append('-P')

# -W options
warnopts = sys.warnoptions[:]
bytes_warning = sys.flags.bytes_warning
xoptions = getattr(sys, '_xoptions', {})
dev_mode = ('dev' in xoptions)
bytes_warning = sys.flags.bytes_warning
dev_mode = sys.flags.dev_mode

if bytes_warning > 1:
warnopts.remove("error::BytesWarning")
Expand Down
10 changes: 6 additions & 4 deletions Lib/test/test_cmd_line.py
Original file line number Diff line number Diff line change
Expand Up @@ -579,13 +579,13 @@ def test_unknown_options(self):
'Cannot run -I tests when PYTHON env vars are required.')
def test_isolatedmode(self):
self.verify_valid_flag('-I')
self.verify_valid_flag('-IEs')
self.verify_valid_flag('-IEPs')
rc, out, err = assert_python_ok('-I', '-c',
'from sys import flags as f; '
'print(f.no_user_site, f.ignore_environment, f.isolated)',
'print(f.no_user_site, f.ignore_environment, f.isolated, f.safe_path)',
# dummyvar to prevent extraneous -E
dummyvar="")
self.assertEqual(out.strip(), b'1 1 1')
self.assertEqual(out.strip(), b'1 1 1 True')
gpshead marked this conversation as resolved.
Show resolved Hide resolved
with os_helper.temp_cwd() as tmpdir:
fake = os.path.join(tmpdir, "uuid.py")
main = os.path.join(tmpdir, "main.py")
Expand Down Expand Up @@ -880,14 +880,16 @@ def test_sys_flags_not_set(self):
# Issue 31845: a startup refactoring broke reading flags from env vars
expected_outcome = """
(sys.flags.debug == sys.flags.optimize ==
sys.flags.dont_write_bytecode == sys.flags.verbose == 0)
sys.flags.dont_write_bytecode ==
sys.flags.verbose == sys.flags.safe_path == 0)
"""
self.run_ignoring_vars(
expected_outcome,
PYTHONDEBUG="1",
PYTHONOPTIMIZE="1",
PYTHONDONTWRITEBYTECODE="1",
PYTHONVERBOSE="1",
PYTHONSAFEPATH="1",
)

class SyntaxErrorTests(unittest.TestCase):
Expand Down
13 changes: 11 additions & 2 deletions Lib/test/test_embed.py
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
'_init_main': 1,
'_isolated_interpreter': 0,
'use_frozen_modules': not Py_DEBUG,
'safe_path': 0,
'_is_python_build': IGNORE_CONFIG,
}
if MS_WINDOWS:
Expand All @@ -496,6 +497,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
isolated=1,
use_environment=0,
user_site_directory=0,
safe_path=1,
dev_mode=0,
install_signal_handlers=0,
use_hash_seed=0,
Expand Down Expand Up @@ -855,6 +857,7 @@ def test_init_from_config(self):
'faulthandler': 1,
'platlibdir': 'my_platlibdir',
'module_search_paths': self.IGNORE_CONFIG,
'safe_path': 1,

'check_hash_pycs_mode': 'always',
'pathconfig_warnings': 0,
Expand Down Expand Up @@ -889,6 +892,7 @@ def test_init_compat_env(self):
'warnoptions': ['EnvVar'],
'platlibdir': 'env_platlibdir',
'module_search_paths': self.IGNORE_CONFIG,
'safe_path': 1,
}
self.check_all_configs("test_init_compat_env", config, preconfig,
api=API_COMPAT)
Expand Down Expand Up @@ -919,6 +923,7 @@ def test_init_python_env(self):
'warnoptions': ['EnvVar'],
'platlibdir': 'env_platlibdir',
'module_search_paths': self.IGNORE_CONFIG,
'safe_path': 1,
}
self.check_all_configs("test_init_python_env", config, preconfig,
api=API_PYTHON)
Expand Down Expand Up @@ -959,12 +964,13 @@ def test_preinit_parse_argv(self):
}
config = {
'argv': ['script.py'],
'orig_argv': ['python3', '-X', 'dev', 'script.py'],
'orig_argv': ['python3', '-X', 'dev', '-P', 'script.py'],
'run_filename': os.path.abspath('script.py'),
'dev_mode': 1,
'faulthandler': 1,
'warnoptions': ['default'],
'xoptions': ['dev'],
'safe_path': 1,
}
self.check_all_configs("test_preinit_parse_argv", config, preconfig,
api=API_PYTHON)
Expand All @@ -975,7 +981,7 @@ def test_preinit_dont_parse_argv(self):
'isolated': 0,
}
argv = ["python3",
"-E", "-I",
"-E", "-I", "-P",
"-X", "dev",
"-X", "utf8",
"script.py"]
Expand All @@ -990,6 +996,7 @@ def test_preinit_dont_parse_argv(self):
def test_init_isolated_flag(self):
config = {
'isolated': 1,
'safe_path': 1,
'use_environment': 0,
'user_site_directory': 0,
}
Expand All @@ -999,6 +1006,7 @@ def test_preinit_isolated1(self):
# _PyPreConfig.isolated=1, _PyCoreConfig.isolated not set
config = {
'isolated': 1,
'safe_path': 1,
'use_environment': 0,
'user_site_directory': 0,
}
Expand All @@ -1008,6 +1016,7 @@ def test_preinit_isolated2(self):
# _PyPreConfig.isolated=0, _PyCoreConfig.isolated=1
config = {
'isolated': 1,
'safe_path': 1,
'use_environment': 0,
'user_site_directory': 0,
}
Expand Down
4 changes: 3 additions & 1 deletion Lib/test/test_support.py
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,7 @@ def test_args_from_interpreter_flags(self):
['-E'],
['-v'],
['-b'],
['-P'],
['-q'],
['-I'],
# same option multiple times
Expand All @@ -541,7 +542,8 @@ def test_args_from_interpreter_flags(self):
with self.subTest(opts=opts):
self.check_options(opts, 'args_from_interpreter_flags')

self.check_options(['-I', '-E', '-s'], 'args_from_interpreter_flags',
self.check_options(['-I', '-E', '-s', '-P'],
'args_from_interpreter_flags',
['-I'])

def test_optim_args_from_interpreter_flags(self):
Expand Down
4 changes: 2 additions & 2 deletions Lib/test/test_sys.py
Original file line number Diff line number Diff line change
Expand Up @@ -669,10 +669,10 @@ def test_sys_flags(self):
"dont_write_bytecode", "no_user_site", "no_site",
"ignore_environment", "verbose", "bytes_warning", "quiet",
"hash_randomization", "isolated", "dev_mode", "utf8_mode",
"warn_default_encoding")
"warn_default_encoding", "safe_path")
for attr in attrs:
self.assertTrue(hasattr(sys.flags, attr), attr)
attr_type = bool if attr == "dev_mode" else int
attr_type = bool if attr in ("dev_mode", "safe_path") else int
self.assertEqual(type(getattr(sys.flags, attr)), attr_type, attr)
self.assertTrue(repr(sys.flags))
self.assertEqual(len(sys.flags), len(attrs))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Add the :option:`-P` command line option and the :envvar:`PYTHONSAFEPATH`
environment variable to not prepend a potentially unsafe path to
:data:`sys.path`. Patch by Victor Stinner.
Loading