diff --git a/CVE-2024-9287-venv_path_unquoted.patch b/CVE-2024-9287-venv_path_unquoted.patch new file mode 100644 index 0000000..1754af5 --- /dev/null +++ b/CVE-2024-9287-venv_path_unquoted.patch @@ -0,0 +1,268 @@ +From b6a3bbd155c558cdcda482629073e492437db3d0 Mon Sep 17 00:00:00 2001 +From: y5c4l3 +Date: Sat, 28 Sep 2024 02:09:07 +0800 +Subject: [PATCH] Quote template strings in `venv` activation scripts + +This patch properly quotes template strings in `venv` activation +scripts. This mitigates potential command injection. + +Signed-off-by: y5c4l3 +--- + Lib/test/test_venv.py | 81 ++++++++++ + Lib/venv/__init__.py | 42 ++++- + Lib/venv/scripts/common/activate | 6 + Lib/venv/scripts/nt/activate.bat | 6 + Lib/venv/scripts/posix/activate.csh | 6 + Misc/NEWS.d/next/Library/2024-09-28-02-03-04.gh-issue-124651.bLBGtH.rst | 1 + 6 files changed, 128 insertions(+), 14 deletions(-) + create mode 100644 Misc/NEWS.d/next/Library/2024-09-28-02-03-04.gh-issue-124651.bLBGtH.rst + +--- a/Lib/test/test_venv.py ++++ b/Lib/test/test_venv.py +@@ -10,6 +10,7 @@ import ensurepip + import os + import os.path + import re ++import shlex + import shutil + import struct + import subprocess +@@ -85,6 +86,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.""" + +@@ -342,6 +347,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): + """ +--- 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', 'setuptools') +@@ -364,11 +365,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): +@@ -409,6 +440,7 @@ class EnvBuilder: + data = f.read() + if not srcfile.endswith(('.exe', '.pdb')): + try: ++ context.script_path = srcfile + data = data.decode('utf-8') + data = self.replace_variables(data, context) + data = data.encode('utf-8') +--- a/Lib/venv/scripts/common/activate ++++ b/Lib/venv/scripts/common/activate +@@ -38,11 +38,11 @@ deactivate () { + # unset irrelevant variables + deactivate nondestructive + +-VIRTUAL_ENV="__VENV_DIR__" ++VIRTUAL_ENV=__VENV_DIR__ + export VIRTUAL_ENV + + _OLD_VIRTUAL_PATH="$PATH" +-PATH="$VIRTUAL_ENV/__VENV_BIN_NAME__:$PATH" ++PATH="$VIRTUAL_ENV/"__VENV_BIN_NAME__":$PATH" + export PATH + + # unset PYTHONHOME if set +@@ -55,7 +55,7 @@ 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__" + export VIRTUAL_ENV_PROMPT +--- a/Lib/venv/scripts/nt/activate.bat ++++ b/Lib/venv/scripts/nt/activate.bat +@@ -8,7 +8,7 @@ if defined _OLD_CODEPAGE ( + "%SystemRoot%\System32\chcp.com" 65001 > nul + ) + +-set VIRTUAL_ENV=__VENV_DIR__ ++set "VIRTUAL_ENV=__VENV_DIR__" + + if not defined PROMPT set PROMPT=$P$G + +@@ -16,7 +16,7 @@ if defined _OLD_VIRTUAL_PROMPT set PROMP + if defined _OLD_VIRTUAL_PYTHONHOME set PYTHONHOME=%_OLD_VIRTUAL_PYTHONHOME% + + set _OLD_VIRTUAL_PROMPT=%PROMPT% +-set PROMPT=__VENV_PROMPT__%PROMPT% ++set "PROMPT=__VENV_PROMPT__%PROMPT%" + + if defined PYTHONHOME set _OLD_VIRTUAL_PYTHONHOME=%PYTHONHOME% + set PYTHONHOME= +@@ -24,7 +24,7 @@ set PYTHONHOME= + if defined _OLD_VIRTUAL_PATH set PATH=%_OLD_VIRTUAL_PATH% + if not defined _OLD_VIRTUAL_PATH set _OLD_VIRTUAL_PATH=%PATH% + +-set PATH=%VIRTUAL_ENV%\__VENV_BIN_NAME__;%PATH% ++set "PATH=%VIRTUAL_ENV%\__VENV_BIN_NAME__;%PATH%" + set VIRTUAL_ENV_PROMPT=__VENV_PROMPT__ + + :END +--- a/Lib/venv/scripts/posix/activate.csh ++++ b/Lib/venv/scripts/posix/activate.csh +@@ -8,16 +8,16 @@ alias deactivate 'test $?_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" ++ set prompt = __VENV_PROMPT__"$prompt" + setenv VIRTUAL_ENV_PROMPT "__VENV_PROMPT__" + endif + +--- /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. diff --git a/python310.changes b/python310.changes index 20e1f0c..b19ea5a 100644 --- a/python310.changes +++ b/python310.changes @@ -1,3 +1,16 @@ +------------------------------------------------------------------- +Thu Oct 24 16:09:00 UTC 2024 - Matej Cepl + +- Add CVE-2024-9287-venv_path_unquoted.patch to properly quote + path names provided when creating a virtual environment + (bsc#1232241, CVE-2024-9287) + +------------------------------------------------------------------- +Wed Oct 2 16:18:29 UTC 2024 - Matej Cepl + +- Drop .pyc files from docdir for reproducible builds + (bsc#1230906). + ------------------------------------------------------------------- Mon Sep 9 13:41:07 UTC 2024 - Matej Cepl diff --git a/python310.spec b/python310.spec index 48d2192..fe74b4e 100644 --- a/python310.spec +++ b/python310.spec @@ -204,6 +204,9 @@ Patch27: gh120226-fix-sendfile-test-kernel-610.patch # PATCH-FIX-UPSTREAM sphinx-802.patch mcepl@suse.com # status_iterator method moved between the Sphinx versions Patch28: sphinx-802.patch +# PATCH-FIX-UPSTREAM CVE-2024-9287-venv_path_unquoted.patch gh#python/cpython#124651 mcepl@suse.com +# venv should properly quote path names provided when creating a venv +Patch29: CVE-2024-9287-venv_path_unquoted.patch BuildRequires: autoconf-archive BuildRequires: automake BuildRequires: fdupes @@ -483,6 +486,7 @@ other applications. %patch -p1 -P 24 %patch -p1 -P 27 %patch -p1 -P 28 +%patch -p1 -P 29 # drop Autoconf version requirement sed -i 's/^AC_PREREQ/dnl AC_PREREQ/' configure.ac @@ -819,6 +823,11 @@ LD_LIBRARY_PATH=. ./python -O -c "from py_compile import compile; compile('$FAIL echo %{sitedir}/_import_failed > %{buildroot}/%{sitedir}/site-packages/zzzz-import-failed-hooks.pth %endif +# For the purposes of reproducibility, it is necessary to eliminate any *.pyc files inside documentation dirs +if [ -d %{buildroot}%{_defaultdocdir} ] ; then +find %{buildroot}%{_defaultdocdir} -type f -name \*.pyc -ls -exec rm -vf '{}' \; +fi + %if %{with general} %files -n %{python_pkg_name}-tk %{sitedir}/tkinter