parent
0452de66fc
commit
dcf5143837
@ -0,0 +1,293 @@
|
||||
From 9f8d3a48361db210f246b7409490b6d13ee40ae1 Mon Sep 17 00:00:00 2001
|
||||
From: Victor Stinner <vstinner@python.org>
|
||||
Date: Thu, 31 Oct 2024 19:09:20 +0100
|
||||
Subject: [PATCH] gh-124651: Quote template strings in `venv` activation
|
||||
scripts (GH-124712) (GH-126185)
|
||||
|
||||
(cherry picked from commit d48cc82ed25e26b02eb97c6263d95dcaa1e9111b)
|
||||
---
|
||||
Lib/test/test_venv.py | 81 +++++++++++++++++++
|
||||
Lib/venv/__init__.py | 42 ++++++++--
|
||||
Lib/venv/scripts/common/activate | 10 +--
|
||||
Lib/venv/scripts/posix/activate.csh | 8 +-
|
||||
Lib/venv/scripts/posix/activate.fish | 8 +-
|
||||
...-09-28-02-03-04.gh-issue-124651.bLBGtH.rst | 1 +
|
||||
6 files changed, 132 insertions(+), 18 deletions(-)
|
||||
create mode 100644 Misc/NEWS.d/next/Library/2024-09-28-02-03-04.gh-issue-124651.bLBGtH.rst
|
||||
|
||||
diff --git a/Lib/test/test_venv.py b/Lib/test/test_venv.py
|
||||
index feaf978..8926014 100644
|
||||
--- a/Lib/test/test_venv.py
|
||||
+++ b/Lib/test/test_venv.py
|
||||
@@ -17,6 +17,7 @@ import subprocess
|
||||
import sys
|
||||
import sysconfig
|
||||
import tempfile
|
||||
+import shlex
|
||||
from test.support import (captured_stdout, captured_stderr,
|
||||
skip_if_broken_multiprocessing_synchronize, verbose,
|
||||
requires_subprocess, is_emscripten, is_wasi,
|
||||
@@ -97,6 +98,10 @@ class BaseTest(unittest.TestCase):
|
||||
result = f.read()
|
||||
return result
|
||||
|
||||
+ def assertEndsWith(self, string, tail):
|
||||
+ if not string.endswith(tail):
|
||||
+ self.fail(f"String {string!r} does not end with {tail!r}")
|
||||
+
|
||||
class BasicTest(BaseTest):
|
||||
"""Test venv module functionality."""
|
||||
|
||||
@@ -446,6 +451,82 @@ class BasicTest(BaseTest):
|
||||
'import sys; print(sys.executable)'])
|
||||
self.assertEqual(out.strip(), envpy.encode())
|
||||
|
||||
+ # gh-124651: test quoted strings
|
||||
+ @unittest.skipIf(os.name == 'nt', 'contains invalid characters on Windows')
|
||||
+ def test_special_chars_bash(self):
|
||||
+ """
|
||||
+ Test that the template strings are quoted properly (bash)
|
||||
+ """
|
||||
+ rmtree(self.env_dir)
|
||||
+ bash = shutil.which('bash')
|
||||
+ if bash is None:
|
||||
+ self.skipTest('bash required for this test')
|
||||
+ env_name = '"\';&&$e|\'"'
|
||||
+ env_dir = os.path.join(os.path.realpath(self.env_dir), env_name)
|
||||
+ builder = venv.EnvBuilder(clear=True)
|
||||
+ builder.create(env_dir)
|
||||
+ activate = os.path.join(env_dir, self.bindir, 'activate')
|
||||
+ test_script = os.path.join(self.env_dir, 'test_special_chars.sh')
|
||||
+ with open(test_script, "w") as f:
|
||||
+ f.write(f'source {shlex.quote(activate)}\n'
|
||||
+ 'python -c \'import sys; print(sys.executable)\'\n'
|
||||
+ 'python -c \'import os; print(os.environ["VIRTUAL_ENV"])\'\n'
|
||||
+ 'deactivate\n')
|
||||
+ out, err = check_output([bash, test_script])
|
||||
+ lines = out.splitlines()
|
||||
+ self.assertTrue(env_name.encode() in lines[0])
|
||||
+ self.assertEndsWith(lines[1], env_name.encode())
|
||||
+
|
||||
+ # gh-124651: test quoted strings
|
||||
+ @unittest.skipIf(os.name == 'nt', 'contains invalid characters on Windows')
|
||||
+ def test_special_chars_csh(self):
|
||||
+ """
|
||||
+ Test that the template strings are quoted properly (csh)
|
||||
+ """
|
||||
+ rmtree(self.env_dir)
|
||||
+ csh = shutil.which('tcsh') or shutil.which('csh')
|
||||
+ if csh is None:
|
||||
+ self.skipTest('csh required for this test')
|
||||
+ env_name = '"\';&&$e|\'"'
|
||||
+ env_dir = os.path.join(os.path.realpath(self.env_dir), env_name)
|
||||
+ builder = venv.EnvBuilder(clear=True)
|
||||
+ builder.create(env_dir)
|
||||
+ activate = os.path.join(env_dir, self.bindir, 'activate.csh')
|
||||
+ test_script = os.path.join(self.env_dir, 'test_special_chars.csh')
|
||||
+ with open(test_script, "w") as f:
|
||||
+ f.write(f'source {shlex.quote(activate)}\n'
|
||||
+ 'python -c \'import sys; print(sys.executable)\'\n'
|
||||
+ 'python -c \'import os; print(os.environ["VIRTUAL_ENV"])\'\n'
|
||||
+ 'deactivate\n')
|
||||
+ out, err = check_output([csh, test_script])
|
||||
+ lines = out.splitlines()
|
||||
+ self.assertTrue(env_name.encode() in lines[0])
|
||||
+ self.assertEndsWith(lines[1], env_name.encode())
|
||||
+
|
||||
+ # gh-124651: test quoted strings on Windows
|
||||
+ @unittest.skipUnless(os.name == 'nt', 'only relevant on Windows')
|
||||
+ def test_special_chars_windows(self):
|
||||
+ """
|
||||
+ Test that the template strings are quoted properly on Windows
|
||||
+ """
|
||||
+ rmtree(self.env_dir)
|
||||
+ env_name = "'&&^$e"
|
||||
+ env_dir = os.path.join(os.path.realpath(self.env_dir), env_name)
|
||||
+ builder = venv.EnvBuilder(clear=True)
|
||||
+ builder.create(env_dir)
|
||||
+ activate = os.path.join(env_dir, self.bindir, 'activate.bat')
|
||||
+ test_batch = os.path.join(self.env_dir, 'test_special_chars.bat')
|
||||
+ with open(test_batch, "w") as f:
|
||||
+ f.write('@echo off\n'
|
||||
+ f'"{activate}" & '
|
||||
+ f'{self.exe} -c "import sys; print(sys.executable)" & '
|
||||
+ f'{self.exe} -c "import os; print(os.environ[\'VIRTUAL_ENV\'])" & '
|
||||
+ 'deactivate')
|
||||
+ out, err = check_output([test_batch])
|
||||
+ lines = out.splitlines()
|
||||
+ self.assertTrue(env_name.encode() in lines[0])
|
||||
+ self.assertEndsWith(lines[1], env_name.encode())
|
||||
+
|
||||
@unittest.skipUnless(os.name == 'nt', 'only relevant on Windows')
|
||||
def test_unicode_in_batch_file(self):
|
||||
"""
|
||||
diff --git a/Lib/venv/__init__.py b/Lib/venv/__init__.py
|
||||
index d5dec4a..aeb522c 100644
|
||||
--- a/Lib/venv/__init__.py
|
||||
+++ b/Lib/venv/__init__.py
|
||||
@@ -11,6 +11,7 @@ import subprocess
|
||||
import sys
|
||||
import sysconfig
|
||||
import types
|
||||
+import shlex
|
||||
|
||||
|
||||
CORE_VENV_DEPS = ('pip',)
|
||||
@@ -422,11 +423,41 @@ class EnvBuilder:
|
||||
:param context: The information for the environment creation request
|
||||
being processed.
|
||||
"""
|
||||
- text = text.replace('__VENV_DIR__', context.env_dir)
|
||||
- text = text.replace('__VENV_NAME__', context.env_name)
|
||||
- text = text.replace('__VENV_PROMPT__', context.prompt)
|
||||
- text = text.replace('__VENV_BIN_NAME__', context.bin_name)
|
||||
- text = text.replace('__VENV_PYTHON__', context.env_exe)
|
||||
+ replacements = {
|
||||
+ '__VENV_DIR__': context.env_dir,
|
||||
+ '__VENV_NAME__': context.env_name,
|
||||
+ '__VENV_PROMPT__': context.prompt,
|
||||
+ '__VENV_BIN_NAME__': context.bin_name,
|
||||
+ '__VENV_PYTHON__': context.env_exe,
|
||||
+ }
|
||||
+
|
||||
+ def quote_ps1(s):
|
||||
+ """
|
||||
+ This should satisfy PowerShell quoting rules [1], unless the quoted
|
||||
+ string is passed directly to Windows native commands [2].
|
||||
+ [1]: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_quoting_rules
|
||||
+ [2]: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_parsing#passing-arguments-that-contain-quote-characters
|
||||
+ """
|
||||
+ s = s.replace("'", "''")
|
||||
+ return f"'{s}'"
|
||||
+
|
||||
+ def quote_bat(s):
|
||||
+ return s
|
||||
+
|
||||
+ # gh-124651: need to quote the template strings properly
|
||||
+ quote = shlex.quote
|
||||
+ script_path = context.script_path
|
||||
+ if script_path.endswith('.ps1'):
|
||||
+ quote = quote_ps1
|
||||
+ elif script_path.endswith('.bat'):
|
||||
+ quote = quote_bat
|
||||
+ else:
|
||||
+ # fallbacks to POSIX shell compliant quote
|
||||
+ quote = shlex.quote
|
||||
+
|
||||
+ replacements = {key: quote(s) for key, s in replacements.items()}
|
||||
+ for key, quoted in replacements.items():
|
||||
+ text = text.replace(key, quoted)
|
||||
return text
|
||||
|
||||
def install_scripts(self, context, path):
|
||||
@@ -466,6 +497,7 @@ class EnvBuilder:
|
||||
with open(srcfile, 'rb') as f:
|
||||
data = f.read()
|
||||
if not srcfile.endswith(('.exe', '.pdb')):
|
||||
+ context.script_path = srcfile
|
||||
try:
|
||||
data = data.decode('utf-8')
|
||||
data = self.replace_variables(data, context)
|
||||
diff --git a/Lib/venv/scripts/common/activate b/Lib/venv/scripts/common/activate
|
||||
index d5914e0..47602ca 100644
|
||||
--- a/Lib/venv/scripts/common/activate
|
||||
+++ b/Lib/venv/scripts/common/activate
|
||||
@@ -39,14 +39,14 @@ deactivate nondestructive
|
||||
if [ "${OSTYPE:-}" = "cygwin" ] || [ "${OSTYPE:-}" = "msys" ] ; then
|
||||
# transform D:\path\to\venv to /d/path/to/venv on MSYS
|
||||
# and to /cygdrive/d/path/to/venv on Cygwin
|
||||
- export VIRTUAL_ENV=$(cygpath "__VENV_DIR__")
|
||||
+ export VIRTUAL_ENV=$(cygpath __VENV_DIR__)
|
||||
else
|
||||
# use the path as-is
|
||||
- export VIRTUAL_ENV="__VENV_DIR__"
|
||||
+ export VIRTUAL_ENV=__VENV_DIR__
|
||||
fi
|
||||
|
||||
_OLD_VIRTUAL_PATH="$PATH"
|
||||
-PATH="$VIRTUAL_ENV/__VENV_BIN_NAME__:$PATH"
|
||||
+PATH="$VIRTUAL_ENV/"__VENV_BIN_NAME__":$PATH"
|
||||
export PATH
|
||||
|
||||
# unset PYTHONHOME if set
|
||||
@@ -59,9 +59,9 @@ fi
|
||||
|
||||
if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then
|
||||
_OLD_VIRTUAL_PS1="${PS1:-}"
|
||||
- PS1="__VENV_PROMPT__${PS1:-}"
|
||||
+ PS1=__VENV_PROMPT__"${PS1:-}"
|
||||
export PS1
|
||||
- VIRTUAL_ENV_PROMPT="__VENV_PROMPT__"
|
||||
+ VIRTUAL_ENV_PROMPT=__VENV_PROMPT__
|
||||
export VIRTUAL_ENV_PROMPT
|
||||
fi
|
||||
|
||||
diff --git a/Lib/venv/scripts/posix/activate.csh b/Lib/venv/scripts/posix/activate.csh
|
||||
index 5e8d66f..08f7929 100644
|
||||
--- a/Lib/venv/scripts/posix/activate.csh
|
||||
+++ b/Lib/venv/scripts/posix/activate.csh
|
||||
@@ -9,17 +9,17 @@ alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PA
|
||||
# Unset irrelevant variables.
|
||||
deactivate nondestructive
|
||||
|
||||
-setenv VIRTUAL_ENV "__VENV_DIR__"
|
||||
+setenv VIRTUAL_ENV __VENV_DIR__
|
||||
|
||||
set _OLD_VIRTUAL_PATH="$PATH"
|
||||
-setenv PATH "$VIRTUAL_ENV/__VENV_BIN_NAME__:$PATH"
|
||||
+setenv PATH "$VIRTUAL_ENV/"__VENV_BIN_NAME__":$PATH"
|
||||
|
||||
|
||||
set _OLD_VIRTUAL_PROMPT="$prompt"
|
||||
|
||||
if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then
|
||||
- set prompt = "__VENV_PROMPT__$prompt"
|
||||
- setenv VIRTUAL_ENV_PROMPT "__VENV_PROMPT__"
|
||||
+ set prompt = __VENV_PROMPT__"$prompt"
|
||||
+ setenv VIRTUAL_ENV_PROMPT __VENV_PROMPT__
|
||||
endif
|
||||
|
||||
alias pydoc python -m pydoc
|
||||
diff --git a/Lib/venv/scripts/posix/activate.fish b/Lib/venv/scripts/posix/activate.fish
|
||||
index 91ad644..508cab0 100644
|
||||
--- a/Lib/venv/scripts/posix/activate.fish
|
||||
+++ b/Lib/venv/scripts/posix/activate.fish
|
||||
@@ -33,10 +33,10 @@ end
|
||||
# Unset irrelevant variables.
|
||||
deactivate nondestructive
|
||||
|
||||
-set -gx VIRTUAL_ENV "__VENV_DIR__"
|
||||
+set -gx VIRTUAL_ENV __VENV_DIR__
|
||||
|
||||
set -gx _OLD_VIRTUAL_PATH $PATH
|
||||
-set -gx PATH "$VIRTUAL_ENV/__VENV_BIN_NAME__" $PATH
|
||||
+set -gx PATH "$VIRTUAL_ENV/"__VENV_BIN_NAME__ $PATH
|
||||
|
||||
# Unset PYTHONHOME if set.
|
||||
if set -q PYTHONHOME
|
||||
@@ -56,7 +56,7 @@ if test -z "$VIRTUAL_ENV_DISABLE_PROMPT"
|
||||
set -l old_status $status
|
||||
|
||||
# Output the venv prompt; color taken from the blue of the Python logo.
|
||||
- printf "%s%s%s" (set_color 4B8BBE) "__VENV_PROMPT__" (set_color normal)
|
||||
+ printf "%s%s%s" (set_color 4B8BBE) __VENV_PROMPT__ (set_color normal)
|
||||
|
||||
# Restore the return status of the previous command.
|
||||
echo "exit $old_status" | .
|
||||
@@ -65,5 +65,5 @@ if test -z "$VIRTUAL_ENV_DISABLE_PROMPT"
|
||||
end
|
||||
|
||||
set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV"
|
||||
- set -gx VIRTUAL_ENV_PROMPT "__VENV_PROMPT__"
|
||||
+ set -gx VIRTUAL_ENV_PROMPT __VENV_PROMPT__
|
||||
end
|
||||
diff --git a/Misc/NEWS.d/next/Library/2024-09-28-02-03-04.gh-issue-124651.bLBGtH.rst b/Misc/NEWS.d/next/Library/2024-09-28-02-03-04.gh-issue-124651.bLBGtH.rst
|
||||
new file mode 100644
|
||||
index 0000000..17fc917
|
||||
--- /dev/null
|
||||
+++ b/Misc/NEWS.d/next/Library/2024-09-28-02-03-04.gh-issue-124651.bLBGtH.rst
|
||||
@@ -0,0 +1 @@
|
||||
+Properly quote template strings in :mod:`venv` activation scripts.
|
||||
--
|
||||
2.47.1
|
||||
|
@ -0,0 +1,62 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: "Miss Islington (bot)"
|
||||
<31488909+miss-islington@users.noreply.github.com>
|
||||
Date: Fri, 6 Dec 2024 06:12:40 +0100
|
||||
Subject: [PATCH] 00445: CVE-2024-12254: Ensure
|
||||
_SelectorSocketTransport.writelines pauses the protocol if needed
|
||||
|
||||
Ensure _SelectorSocketTransport.writelines pauses the protocol if it reaches the high water mark as needed.
|
||||
|
||||
Resolved upstream: https://github.com/python/cpython/issues/127655
|
||||
|
||||
Co-authored-by: J. Nick Koston <nick@koston.org>
|
||||
Co-authored-by: Kumar Aditya <kumaraditya@python.org>
|
||||
---
|
||||
Lib/asyncio/selector_events.py | 1 +
|
||||
Lib/test/test_asyncio/test_selector_events.py | 12 ++++++++++++
|
||||
.../2024-12-05-21-35-19.gh-issue-127655.xpPoOf.rst | 1 +
|
||||
3 files changed, 14 insertions(+)
|
||||
create mode 100644 Misc/NEWS.d/next/Security/2024-12-05-21-35-19.gh-issue-127655.xpPoOf.rst
|
||||
|
||||
diff --git a/Lib/asyncio/selector_events.py b/Lib/asyncio/selector_events.py
|
||||
index 790711f834..dd79ad18df 100644
|
||||
--- a/Lib/asyncio/selector_events.py
|
||||
+++ b/Lib/asyncio/selector_events.py
|
||||
@@ -1183,6 +1183,7 @@ def writelines(self, list_of_data):
|
||||
# If the entire buffer couldn't be written, register a write handler
|
||||
if self._buffer:
|
||||
self._loop._add_writer(self._sock_fd, self._write_ready)
|
||||
+ self._maybe_pause_protocol()
|
||||
|
||||
def can_write_eof(self):
|
||||
return True
|
||||
diff --git a/Lib/test/test_asyncio/test_selector_events.py b/Lib/test/test_asyncio/test_selector_events.py
|
||||
index 47693ea4d3..736c19796e 100644
|
||||
--- a/Lib/test/test_asyncio/test_selector_events.py
|
||||
+++ b/Lib/test/test_asyncio/test_selector_events.py
|
||||
@@ -805,6 +805,18 @@ def test_writelines_send_partial(self):
|
||||
self.assertTrue(self.sock.send.called)
|
||||
self.assertTrue(self.loop.writers)
|
||||
|
||||
+ def test_writelines_pauses_protocol(self):
|
||||
+ data = memoryview(b'data')
|
||||
+ self.sock.send.return_value = 2
|
||||
+ self.sock.send.fileno.return_value = 7
|
||||
+
|
||||
+ transport = self.socket_transport()
|
||||
+ transport._high_water = 1
|
||||
+ transport.writelines([data])
|
||||
+ self.assertTrue(self.protocol.pause_writing.called)
|
||||
+ self.assertTrue(self.sock.send.called)
|
||||
+ self.assertTrue(self.loop.writers)
|
||||
+
|
||||
@unittest.skipUnless(selector_events._HAS_SENDMSG, 'no sendmsg')
|
||||
def test_write_sendmsg_full(self):
|
||||
data = memoryview(b'data')
|
||||
diff --git a/Misc/NEWS.d/next/Security/2024-12-05-21-35-19.gh-issue-127655.xpPoOf.rst b/Misc/NEWS.d/next/Security/2024-12-05-21-35-19.gh-issue-127655.xpPoOf.rst
|
||||
new file mode 100644
|
||||
index 0000000000..76cfc58121
|
||||
--- /dev/null
|
||||
+++ b/Misc/NEWS.d/next/Security/2024-12-05-21-35-19.gh-issue-127655.xpPoOf.rst
|
||||
@@ -0,0 +1 @@
|
||||
+Fixed the :class:`!asyncio.selector_events._SelectorSocketTransport` transport not pausing writes for the protocol when the buffer reaches the high water mark when using :meth:`asyncio.WriteTransport.writelines`.
|
Loading…
Reference in new issue