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 all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -543,6 +543,25 @@ 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's
directory. If it's a symbolic link, resolve symbolic links.
* Otherwise (``python -c code`` and ``python``), prepend an empty string,
which means the current working directory.

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`.
@@ -809,13 +828,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.

@@ -1029,12 +1049,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``.

@@ -1419,9 +1440,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`

@@ -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
@@ -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

@@ -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's directory.
If it's a symbolic link, resolve symbolic links.
* ``python -c code`` and ``python`` (REPL) command lines: prepend an empty
string, which means the current working directory.

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
@@ -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

@@ -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
@@ -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'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, which means the current working directory.

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.
@@ -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`
@@ -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, the script's directory or an empty string.
(Contributed by Victor Stinner in :gh:`57684`.)


Other CPython Implementation Changes
====================================
@@ -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
---------
@@ -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
----------------------
@@ -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;
@@ -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")
@@ -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")
@@ -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):
@@ -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:
@@ -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,
@@ -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,
@@ -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)
@@ -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)
@@ -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)
@@ -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"]
@@ -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,
}
@@ -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,
}
@@ -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,
}
@@ -522,6 +522,7 @@ def test_args_from_interpreter_flags(self):
['-E'],
['-v'],
['-b'],
['-P'],
['-q'],
['-I'],
# same option multiple times
@@ -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):
@@ -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))
@@ -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.