diff --git a/SOURCES/00443-gh-124651-quote-template-strings-in-venv-activation-scripts.patch b/SOURCES/00443-gh-124651-quote-template-strings-in-venv-activation-scripts.patch new file mode 100644 index 0000000..aa44096 --- /dev/null +++ b/SOURCES/00443-gh-124651-quote-template-strings-in-venv-activation-scripts.patch @@ -0,0 +1,281 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Victor Stinner +Date: Fri, 1 Nov 2024 14:11:47 +0100 +Subject: [PATCH] 00443: gh-124651: Quote template strings in `venv` activation + scripts + +(cherry picked from 3.9) +--- + Lib/test/test_venv.py | 82 +++++++++++++++++++ + Lib/venv/__init__.py | 42 ++++++++-- + Lib/venv/scripts/common/activate | 8 +- + 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(+), 17 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 842470fef0..67fdcd86bb 100644 +--- a/Lib/test/test_venv.py ++++ b/Lib/test/test_venv.py +@@ -13,6 +13,8 @@ import struct + import subprocess + import sys + import tempfile ++import shlex ++import shutil + from test.support import (captured_stdout, captured_stderr, requires_zlib, + can_symlink, EnvironmentVarGuard, rmtree) + import unittest +@@ -80,6 +82,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.""" + +@@ -293,6 +299,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 716129d139..0c44dfd07d 100644 +--- a/Lib/venv/__init__.py ++++ b/Lib/venv/__init__.py +@@ -10,6 +10,7 @@ import shutil + import subprocess + import sys + import types ++import shlex + + logger = logging.getLogger(__name__) + +@@ -280,11 +281,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): +@@ -321,6 +352,7 @@ class EnvBuilder: + with open(srcfile, 'rb') as f: + data = f.read() + if not srcfile.endswith('.exe'): ++ 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 fff0765af5..c2e2f968fa 100644 +--- a/Lib/venv/scripts/common/activate ++++ b/Lib/venv/scripts/common/activate +@@ -37,11 +37,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 +@@ -54,8 +54,8 @@ fi + + if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then + _OLD_VIRTUAL_PS1="${PS1:-}" +- if [ "x__VENV_PROMPT__" != x ] ; then +- PS1="__VENV_PROMPT__${PS1:-}" ++ if [ "x"__VENV_PROMPT__ != x ] ; then ++ PS1=__VENV_PROMPT__"${PS1:-}" + else + if [ "`basename \"$VIRTUAL_ENV\"`" = "__" ] ; then + # special case for Aspen magic directories +diff --git a/Lib/venv/scripts/posix/activate.csh b/Lib/venv/scripts/posix/activate.csh +index b0c7028a92..0e90d54008 100644 +--- a/Lib/venv/scripts/posix/activate.csh ++++ b/Lib/venv/scripts/posix/activate.csh +@@ -8,17 +8,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 +- if ("__VENV_NAME__" != "") then +- set env_name = "__VENV_NAME__" ++ if (__VENV_NAME__ != "") then ++ set env_name = __VENV_NAME__ + else + if (`basename "VIRTUAL_ENV"` == "__") then + # special case for Aspen magic directories +diff --git a/Lib/venv/scripts/posix/activate.fish b/Lib/venv/scripts/posix/activate.fish +index 4d4f0bd7a4..0407f9c7be 100644 +--- a/Lib/venv/scripts/posix/activate.fish ++++ b/Lib/venv/scripts/posix/activate.fish +@@ -29,10 +29,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 +@@ -52,8 +52,8 @@ if test -z "$VIRTUAL_ENV_DISABLE_PROMPT" + set -l old_status $status + + # Prompt override? +- if test -n "__VENV_PROMPT__" +- printf "%s%s" "__VENV_PROMPT__" (set_color normal) ++ if test -n __VENV_PROMPT__ ++ printf "%s%s" __VENV_PROMPT__ (set_color normal) + else + # ...Otherwise, prepend env + set -l _checkbase (basename "$VIRTUAL_ENV") +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 0000000000..17fc917139 +--- /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/SOURCES/00444-security-fix-for-cve-2024-11168.patch b/SOURCES/00444-security-fix-for-cve-2024-11168.patch new file mode 100644 index 0000000..d447c86 --- /dev/null +++ b/SOURCES/00444-security-fix-for-cve-2024-11168.patch @@ -0,0 +1,110 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: "Miss Islington (bot)" + <31488909+miss-islington@users.noreply.github.com> +Date: Tue, 9 May 2023 23:35:24 -0700 +Subject: [PATCH] 00444: Security fix for CVE-2024-11168 +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +gh-103848: Adds checks to ensure that bracketed hosts found by urlsplit are of IPv6 or IPvFuture format (GH-103849) + +Tests are adjusted because Python <3.9 don't support scoped IPv6 addresses. + +(cherry picked from commit 29f348e232e82938ba2165843c448c2b291504c5) + +Co-authored-by: JohnJamesUtley <81572567+JohnJamesUtley@users.noreply.github.com> +Co-authored-by: Gregory P. Smith +Co-authored-by: Lumír Balhar +--- + Lib/test/test_urlparse.py | 26 +++++++++++++++++++ + Lib/urllib/parse.py | 15 +++++++++++ + ...-04-26-09-54-25.gh-issue-103848.aDSnpR.rst | 2 ++ + 3 files changed, 43 insertions(+) + create mode 100644 Misc/NEWS.d/next/Library/2023-04-26-09-54-25.gh-issue-103848.aDSnpR.rst + +diff --git a/Lib/test/test_urlparse.py b/Lib/test/test_urlparse.py +index 7fd61ffea9..090d2f17bf 100644 +--- a/Lib/test/test_urlparse.py ++++ b/Lib/test/test_urlparse.py +@@ -1076,6 +1076,32 @@ class UrlParseTestCase(unittest.TestCase): + self.assertEqual(p2.scheme, 'tel') + self.assertEqual(p2.path, '+31641044153') + ++ def test_invalid_bracketed_hosts(self): ++ self.assertRaises(ValueError, urllib.parse.urlsplit, 'Scheme://user@[192.0.2.146]/Path?Query') ++ self.assertRaises(ValueError, urllib.parse.urlsplit, 'Scheme://user@[important.com:8000]/Path?Query') ++ self.assertRaises(ValueError, urllib.parse.urlsplit, 'Scheme://user@[v123r.IP]/Path?Query') ++ self.assertRaises(ValueError, urllib.parse.urlsplit, 'Scheme://user@[v12ae]/Path?Query') ++ self.assertRaises(ValueError, urllib.parse.urlsplit, 'Scheme://user@[v.IP]/Path?Query') ++ self.assertRaises(ValueError, urllib.parse.urlsplit, 'Scheme://user@[v123.]/Path?Query') ++ self.assertRaises(ValueError, urllib.parse.urlsplit, 'Scheme://user@[v]/Path?Query') ++ self.assertRaises(ValueError, urllib.parse.urlsplit, 'Scheme://user@[0439:23af::2309::fae7:1234]/Path?Query') ++ self.assertRaises(ValueError, urllib.parse.urlsplit, 'Scheme://user@[0439:23af:2309::fae7:1234:2342:438e:192.0.2.146]/Path?Query') ++ self.assertRaises(ValueError, urllib.parse.urlsplit, 'Scheme://user@]v6a.ip[/Path') ++ ++ def test_splitting_bracketed_hosts(self): ++ p1 = urllib.parse.urlsplit('scheme://user@[v6a.ip]/path?query') ++ self.assertEqual(p1.hostname, 'v6a.ip') ++ self.assertEqual(p1.username, 'user') ++ self.assertEqual(p1.path, '/path') ++ p2 = urllib.parse.urlsplit('scheme://user@[0439:23af:2309::fae7]/path?query') ++ self.assertEqual(p2.hostname, '0439:23af:2309::fae7') ++ self.assertEqual(p2.username, 'user') ++ self.assertEqual(p2.path, '/path') ++ p3 = urllib.parse.urlsplit('scheme://user@[0439:23af:2309::fae7:1234:192.0.2.146]/path?query') ++ self.assertEqual(p3.hostname, '0439:23af:2309::fae7:1234:192.0.2.146') ++ self.assertEqual(p3.username, 'user') ++ self.assertEqual(p3.path, '/path') ++ + def test_telurl_params(self): + p1 = urllib.parse.urlparse('tel:123-4;phone-context=+1-650-516') + self.assertEqual(p1.scheme, 'tel') +diff --git a/Lib/urllib/parse.py b/Lib/urllib/parse.py +index 717e990997..bf186b7984 100644 +--- a/Lib/urllib/parse.py ++++ b/Lib/urllib/parse.py +@@ -34,6 +34,7 @@ It serves as a useful guide when making changes. + import os + import sys + import collections ++import ipaddress + + __all__ = ["urlparse", "urlunparse", "urljoin", "urldefrag", + "urlsplit", "urlunsplit", "urlencode", "parse_qs", +@@ -425,6 +426,17 @@ def _remove_unsafe_bytes_from_url(url): + url = url.replace(b, "") + return url + ++# Valid bracketed hosts are defined in ++# https://www.rfc-editor.org/rfc/rfc3986#page-49 and https://url.spec.whatwg.org/ ++def _check_bracketed_host(hostname): ++ if hostname.startswith('v'): ++ if not re.match(r"\Av[a-fA-F0-9]+\..+\Z", hostname): ++ raise ValueError(f"IPvFuture address is invalid") ++ else: ++ ip = ipaddress.ip_address(hostname) # Throws Value Error if not IPv6 or IPv4 ++ if isinstance(ip, ipaddress.IPv4Address): ++ raise ValueError(f"An IPv4 address cannot be in brackets") ++ + def urlsplit(url, scheme='', allow_fragments=True): + """Parse a URL into 5 components: + :///?# +@@ -480,6 +492,9 @@ def urlsplit(url, scheme='', allow_fragments=True): + if (('[' in netloc and ']' not in netloc) or + (']' in netloc and '[' not in netloc)): + raise ValueError("Invalid IPv6 URL") ++ if '[' in netloc and ']' in netloc: ++ bracketed_host = netloc.partition('[')[2].partition(']')[0] ++ _check_bracketed_host(bracketed_host) + if allow_fragments and '#' in url: + url, fragment = url.split('#', 1) + if '?' in url: +diff --git a/Misc/NEWS.d/next/Library/2023-04-26-09-54-25.gh-issue-103848.aDSnpR.rst b/Misc/NEWS.d/next/Library/2023-04-26-09-54-25.gh-issue-103848.aDSnpR.rst +new file mode 100644 +index 0000000000..81e5904aa6 +--- /dev/null ++++ b/Misc/NEWS.d/next/Library/2023-04-26-09-54-25.gh-issue-103848.aDSnpR.rst +@@ -0,0 +1,2 @@ ++Add checks to ensure that ``[`` bracketed ``]`` hosts found by ++:func:`urllib.parse.urlsplit` are of IPv6 or IPvFuture format. diff --git a/SPECS/python3.spec b/SPECS/python3.spec index 1192767..81f994e 100644 --- a/SPECS/python3.spec +++ b/SPECS/python3.spec @@ -14,7 +14,7 @@ URL: https://www.python.org/ # WARNING When rebasing to a new Python version, # remember to update the python3-docs package as well Version: %{pybasever}.8 -Release: 67%{?dist} +Release: 69%{?dist} License: Python @@ -904,6 +904,20 @@ Patch435: 00435-cve-2024-6923.patch # Cherry-picked from 3.8. Patch437: 00437-cve-2024-6232.patch +# 00443 # 49e939f29e3551ec4e7bdb2cc8b8745e3d1fca35 +# gh-124651: Quote template strings in `venv` activation scripts +# +# (cherry picked from 3.9) +Patch443: 00443-gh-124651-quote-template-strings-in-venv-activation-scripts.patch + +# 00444 # fed0071c8c86599091f93967a5fa2cce42ceb840 +# Security fix for CVE-2024-11168 +# +# gh-103848: Adds checks to ensure that bracketed hosts found by urlsplit are of IPv6 or IPvFuture format (GH-103849) +# +# Tests are adjusted because Python <3.9 don't support scoped IPv6 addresses. +Patch444: 00444-security-fix-for-cve-2024-11168.patch + # (New patches go here ^^^) # # When adding new patches to "python" and "python3" in Fedora, EL, etc., @@ -1271,6 +1285,8 @@ git apply %{PATCH351} %patch431 -p1 %patch435 -p1 %patch437 -p1 +%patch443 -p1 +%patch444 -p1 # Remove files that should be generated by the build # (This is after patching, so that we can use patches directly from upstream) @@ -2202,6 +2218,14 @@ fi # ====================================================== %changelog +* Thu Nov 14 2024 Lumír Balhar - 3.6.8-69 +- Security fix for CVE-2024-11168 +Resolves: RHEL-67252 + +* Tue Nov 05 2024 Lumír Balhar - 3.6.8-68 +- Security fix for CVE-2024-9287 +Resolves: RHEL-64878 + * Thu Sep 05 2024 Lumír Balhar - 3.6.8-67 - Security fix for CVE-2024-6232 Resolves: RHEL-57399