Skip to content

Commit 796d7cc

Browse files
committed
python3: patch CVE-2024-9287
Taken from python/cpython#126185 which is a 3.12 backport of python/cpython#124712 Signed-off-by: Saul Paredes <[email protected]>
1 parent 32d38c5 commit 796d7cc

File tree

6 files changed

+324
-5
lines changed

6 files changed

+324
-5
lines changed

SPECS/python3/CVE-2024-9287.patch

+315
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,315 @@
1+
From 8450b2482586857d689b6658f08de9c8179af7db Mon Sep 17 00:00:00 2001
2+
From: Victor Stinner <[email protected]>
3+
Date: Thu, 31 Oct 2024 19:09:20 +0100
4+
Subject: [PATCH] [3.12] gh-124651: Quote template strings in `venv` activation
5+
scripts (GH-124712) (GH-126185)
6+
7+
(cherry picked from commit d48cc82ed25e26b02eb97c6263d95dcaa1e9111b)
8+
---
9+
Lib/test/test_venv.py | 81 +++++++++++++++++++
10+
Lib/venv/__init__.py | 42 ++++++++--
11+
Lib/venv/scripts/common/activate | 10 +--
12+
Lib/venv/scripts/nt/activate.bat | 6 +-
13+
Lib/venv/scripts/posix/activate.csh | 8 +-
14+
Lib/venv/scripts/posix/activate.fish | 8 +-
15+
...-09-28-02-03-04.gh-issue-124651.bLBGtH.rst | 1 +
16+
7 files changed, 135 insertions(+), 21 deletions(-)
17+
create mode 100644 Misc/NEWS.d/next/Library/2024-09-28-02-03-04.gh-issue-124651.bLBGtH.rst
18+
19+
diff --git a/Lib/test/test_venv.py b/Lib/test/test_venv.py
20+
index 83d03aa3bf0d9c..8254a701e09a10 100644
21+
--- a/Lib/test/test_venv.py
22+
+++ b/Lib/test/test_venv.py
23+
@@ -17,6 +17,7 @@
24+
import sys
25+
import sysconfig
26+
import tempfile
27+
+import shlex
28+
from test.support import (captured_stdout, captured_stderr,
29+
skip_if_broken_multiprocessing_synchronize, verbose,
30+
requires_subprocess, is_emscripten, is_wasi,
31+
@@ -97,6 +98,10 @@ def get_text_file_contents(self, *args, encoding='utf-8'):
32+
result = f.read()
33+
return result
34+
35+
+ def assertEndsWith(self, string, tail):
36+
+ if not string.endswith(tail):
37+
+ self.fail(f"String {string!r} does not end with {tail!r}")
38+
+
39+
class BasicTest(BaseTest):
40+
"""Test venv module functionality."""
41+
42+
@@ -446,6 +451,82 @@ def test_executable_symlinks(self):
43+
'import sys; print(sys.executable)'])
44+
self.assertEqual(out.strip(), envpy.encode())
45+
46+
+ # gh-124651: test quoted strings
47+
+ @unittest.skipIf(os.name == 'nt', 'contains invalid characters on Windows')
48+
+ def test_special_chars_bash(self):
49+
+ """
50+
+ Test that the template strings are quoted properly (bash)
51+
+ """
52+
+ rmtree(self.env_dir)
53+
+ bash = shutil.which('bash')
54+
+ if bash is None:
55+
+ self.skipTest('bash required for this test')
56+
+ env_name = '"\';&&$e|\'"'
57+
+ env_dir = os.path.join(os.path.realpath(self.env_dir), env_name)
58+
+ builder = venv.EnvBuilder(clear=True)
59+
+ builder.create(env_dir)
60+
+ activate = os.path.join(env_dir, self.bindir, 'activate')
61+
+ test_script = os.path.join(self.env_dir, 'test_special_chars.sh')
62+
+ with open(test_script, "w") as f:
63+
+ f.write(f'source {shlex.quote(activate)}\n'
64+
+ 'python -c \'import sys; print(sys.executable)\'\n'
65+
+ 'python -c \'import os; print(os.environ["VIRTUAL_ENV"])\'\n'
66+
+ 'deactivate\n')
67+
+ out, err = check_output([bash, test_script])
68+
+ lines = out.splitlines()
69+
+ self.assertTrue(env_name.encode() in lines[0])
70+
+ self.assertEndsWith(lines[1], env_name.encode())
71+
+
72+
+ # gh-124651: test quoted strings
73+
+ @unittest.skipIf(os.name == 'nt', 'contains invalid characters on Windows')
74+
+ def test_special_chars_csh(self):
75+
+ """
76+
+ Test that the template strings are quoted properly (csh)
77+
+ """
78+
+ rmtree(self.env_dir)
79+
+ csh = shutil.which('tcsh') or shutil.which('csh')
80+
+ if csh is None:
81+
+ self.skipTest('csh required for this test')
82+
+ env_name = '"\';&&$e|\'"'
83+
+ env_dir = os.path.join(os.path.realpath(self.env_dir), env_name)
84+
+ builder = venv.EnvBuilder(clear=True)
85+
+ builder.create(env_dir)
86+
+ activate = os.path.join(env_dir, self.bindir, 'activate.csh')
87+
+ test_script = os.path.join(self.env_dir, 'test_special_chars.csh')
88+
+ with open(test_script, "w") as f:
89+
+ f.write(f'source {shlex.quote(activate)}\n'
90+
+ 'python -c \'import sys; print(sys.executable)\'\n'
91+
+ 'python -c \'import os; print(os.environ["VIRTUAL_ENV"])\'\n'
92+
+ 'deactivate\n')
93+
+ out, err = check_output([csh, test_script])
94+
+ lines = out.splitlines()
95+
+ self.assertTrue(env_name.encode() in lines[0])
96+
+ self.assertEndsWith(lines[1], env_name.encode())
97+
+
98+
+ # gh-124651: test quoted strings on Windows
99+
+ @unittest.skipUnless(os.name == 'nt', 'only relevant on Windows')
100+
+ def test_special_chars_windows(self):
101+
+ """
102+
+ Test that the template strings are quoted properly on Windows
103+
+ """
104+
+ rmtree(self.env_dir)
105+
+ env_name = "'&&^$e"
106+
+ env_dir = os.path.join(os.path.realpath(self.env_dir), env_name)
107+
+ builder = venv.EnvBuilder(clear=True)
108+
+ builder.create(env_dir)
109+
+ activate = os.path.join(env_dir, self.bindir, 'activate.bat')
110+
+ test_batch = os.path.join(self.env_dir, 'test_special_chars.bat')
111+
+ with open(test_batch, "w") as f:
112+
+ f.write('@echo off\n'
113+
+ f'"{activate}" & '
114+
+ f'{self.exe} -c "import sys; print(sys.executable)" & '
115+
+ f'{self.exe} -c "import os; print(os.environ[\'VIRTUAL_ENV\'])" & '
116+
+ 'deactivate')
117+
+ out, err = check_output([test_batch])
118+
+ lines = out.splitlines()
119+
+ self.assertTrue(env_name.encode() in lines[0])
120+
+ self.assertEndsWith(lines[1], env_name.encode())
121+
+
122+
@unittest.skipUnless(os.name == 'nt', 'only relevant on Windows')
123+
def test_unicode_in_batch_file(self):
124+
"""
125+
diff --git a/Lib/venv/__init__.py b/Lib/venv/__init__.py
126+
index d5dec4ab44b8b5..aeb522c3199ca2 100644
127+
--- a/Lib/venv/__init__.py
128+
+++ b/Lib/venv/__init__.py
129+
@@ -11,6 +11,7 @@
130+
import sys
131+
import sysconfig
132+
import types
133+
+import shlex
134+
135+
136+
CORE_VENV_DEPS = ('pip',)
137+
@@ -422,11 +423,41 @@ def replace_variables(self, text, context):
138+
:param context: The information for the environment creation request
139+
being processed.
140+
"""
141+
- text = text.replace('__VENV_DIR__', context.env_dir)
142+
- text = text.replace('__VENV_NAME__', context.env_name)
143+
- text = text.replace('__VENV_PROMPT__', context.prompt)
144+
- text = text.replace('__VENV_BIN_NAME__', context.bin_name)
145+
- text = text.replace('__VENV_PYTHON__', context.env_exe)
146+
+ replacements = {
147+
+ '__VENV_DIR__': context.env_dir,
148+
+ '__VENV_NAME__': context.env_name,
149+
+ '__VENV_PROMPT__': context.prompt,
150+
+ '__VENV_BIN_NAME__': context.bin_name,
151+
+ '__VENV_PYTHON__': context.env_exe,
152+
+ }
153+
+
154+
+ def quote_ps1(s):
155+
+ """
156+
+ This should satisfy PowerShell quoting rules [1], unless the quoted
157+
+ string is passed directly to Windows native commands [2].
158+
+ [1]: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_quoting_rules
159+
+ [2]: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_parsing#passing-arguments-that-contain-quote-characters
160+
+ """
161+
+ s = s.replace("'", "''")
162+
+ return f"'{s}'"
163+
+
164+
+ def quote_bat(s):
165+
+ return s
166+
+
167+
+ # gh-124651: need to quote the template strings properly
168+
+ quote = shlex.quote
169+
+ script_path = context.script_path
170+
+ if script_path.endswith('.ps1'):
171+
+ quote = quote_ps1
172+
+ elif script_path.endswith('.bat'):
173+
+ quote = quote_bat
174+
+ else:
175+
+ # fallbacks to POSIX shell compliant quote
176+
+ quote = shlex.quote
177+
+
178+
+ replacements = {key: quote(s) for key, s in replacements.items()}
179+
+ for key, quoted in replacements.items():
180+
+ text = text.replace(key, quoted)
181+
return text
182+
183+
def install_scripts(self, context, path):
184+
@@ -466,6 +497,7 @@ def install_scripts(self, context, path):
185+
with open(srcfile, 'rb') as f:
186+
data = f.read()
187+
if not srcfile.endswith(('.exe', '.pdb')):
188+
+ context.script_path = srcfile
189+
try:
190+
data = data.decode('utf-8')
191+
data = self.replace_variables(data, context)
192+
diff --git a/Lib/venv/scripts/common/activate b/Lib/venv/scripts/common/activate
193+
index 44df44a7404b43..74825877c38313 100644
194+
--- a/Lib/venv/scripts/common/activate
195+
+++ b/Lib/venv/scripts/common/activate
196+
@@ -40,14 +40,14 @@ deactivate nondestructive
197+
if [ "${OSTYPE:-}" = "cygwin" ] || [ "${OSTYPE:-}" = "msys" ] ; then
198+
# transform D:\path\to\venv to /d/path/to/venv on MSYS
199+
# and to /cygdrive/d/path/to/venv on Cygwin
200+
- export VIRTUAL_ENV=$(cygpath "__VENV_DIR__")
201+
+ export VIRTUAL_ENV=$(cygpath __VENV_DIR__)
202+
else
203+
# use the path as-is
204+
- export VIRTUAL_ENV="__VENV_DIR__"
205+
+ export VIRTUAL_ENV=__VENV_DIR__
206+
fi
207+
208+
_OLD_VIRTUAL_PATH="$PATH"
209+
-PATH="$VIRTUAL_ENV/__VENV_BIN_NAME__:$PATH"
210+
+PATH="$VIRTUAL_ENV/"__VENV_BIN_NAME__":$PATH"
211+
export PATH
212+
213+
# unset PYTHONHOME if set
214+
@@ -60,9 +60,9 @@ fi
215+
216+
if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then
217+
_OLD_VIRTUAL_PS1="${PS1:-}"
218+
- PS1="__VENV_PROMPT__${PS1:-}"
219+
+ PS1=__VENV_PROMPT__"${PS1:-}"
220+
export PS1
221+
- VIRTUAL_ENV_PROMPT="__VENV_PROMPT__"
222+
+ VIRTUAL_ENV_PROMPT=__VENV_PROMPT__
223+
export VIRTUAL_ENV_PROMPT
224+
fi
225+
226+
diff --git a/Lib/venv/scripts/nt/activate.bat b/Lib/venv/scripts/nt/activate.bat
227+
index c1c3c82ee37f10..715b21b13fbe35 100644
228+
--- a/Lib/venv/scripts/nt/activate.bat
229+
+++ b/Lib/venv/scripts/nt/activate.bat
230+
@@ -8,7 +8,7 @@ if defined _OLD_CODEPAGE (
231+
"%SystemRoot%\System32\chcp.com" 65001 > nul
232+
)
233+
234+
-set VIRTUAL_ENV=__VENV_DIR__
235+
+set "VIRTUAL_ENV=__VENV_DIR__"
236+
237+
if not defined PROMPT set PROMPT=$P$G
238+
239+
@@ -24,8 +24,8 @@ set PYTHONHOME=
240+
if defined _OLD_VIRTUAL_PATH set PATH=%_OLD_VIRTUAL_PATH%
241+
if not defined _OLD_VIRTUAL_PATH set _OLD_VIRTUAL_PATH=%PATH%
242+
243+
-set PATH=%VIRTUAL_ENV%\__VENV_BIN_NAME__;%PATH%
244+
-set VIRTUAL_ENV_PROMPT=__VENV_PROMPT__
245+
+set "PATH=%VIRTUAL_ENV%\__VENV_BIN_NAME__;%PATH%"
246+
+set "VIRTUAL_ENV_PROMPT=__VENV_PROMPT__"
247+
248+
:END
249+
if defined _OLD_CODEPAGE (
250+
diff --git a/Lib/venv/scripts/posix/activate.csh b/Lib/venv/scripts/posix/activate.csh
251+
index 5e8d66fa9e5061..08f79296f59352 100644
252+
--- a/Lib/venv/scripts/posix/activate.csh
253+
+++ b/Lib/venv/scripts/posix/activate.csh
254+
@@ -9,17 +9,17 @@ alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PA
255+
# Unset irrelevant variables.
256+
deactivate nondestructive
257+
258+
-setenv VIRTUAL_ENV "__VENV_DIR__"
259+
+setenv VIRTUAL_ENV __VENV_DIR__
260+
261+
set _OLD_VIRTUAL_PATH="$PATH"
262+
-setenv PATH "$VIRTUAL_ENV/__VENV_BIN_NAME__:$PATH"
263+
+setenv PATH "$VIRTUAL_ENV/"__VENV_BIN_NAME__":$PATH"
264+
265+
266+
set _OLD_VIRTUAL_PROMPT="$prompt"
267+
268+
if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then
269+
- set prompt = "__VENV_PROMPT__$prompt"
270+
- setenv VIRTUAL_ENV_PROMPT "__VENV_PROMPT__"
271+
+ set prompt = __VENV_PROMPT__"$prompt"
272+
+ setenv VIRTUAL_ENV_PROMPT __VENV_PROMPT__
273+
endif
274+
275+
alias pydoc python -m pydoc
276+
diff --git a/Lib/venv/scripts/posix/activate.fish b/Lib/venv/scripts/posix/activate.fish
277+
index 91ad6442e05692..508cab0db468dd 100644
278+
--- a/Lib/venv/scripts/posix/activate.fish
279+
+++ b/Lib/venv/scripts/posix/activate.fish
280+
@@ -33,10 +33,10 @@ end
281+
# Unset irrelevant variables.
282+
deactivate nondestructive
283+
284+
-set -gx VIRTUAL_ENV "__VENV_DIR__"
285+
+set -gx VIRTUAL_ENV __VENV_DIR__
286+
287+
set -gx _OLD_VIRTUAL_PATH $PATH
288+
-set -gx PATH "$VIRTUAL_ENV/__VENV_BIN_NAME__" $PATH
289+
+set -gx PATH "$VIRTUAL_ENV/"__VENV_BIN_NAME__ $PATH
290+
291+
# Unset PYTHONHOME if set.
292+
if set -q PYTHONHOME
293+
@@ -56,7 +56,7 @@ if test -z "$VIRTUAL_ENV_DISABLE_PROMPT"
294+
set -l old_status $status
295+
296+
# Output the venv prompt; color taken from the blue of the Python logo.
297+
- printf "%s%s%s" (set_color 4B8BBE) "__VENV_PROMPT__" (set_color normal)
298+
+ printf "%s%s%s" (set_color 4B8BBE) __VENV_PROMPT__ (set_color normal)
299+
300+
# Restore the return status of the previous command.
301+
echo "exit $old_status" | .
302+
@@ -65,5 +65,5 @@ if test -z "$VIRTUAL_ENV_DISABLE_PROMPT"
303+
end
304+
305+
set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV"
306+
- set -gx VIRTUAL_ENV_PROMPT "__VENV_PROMPT__"
307+
+ set -gx VIRTUAL_ENV_PROMPT __VENV_PROMPT__
308+
end
309+
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
310+
new file mode 100644
311+
index 00000000000000..17fc9171390dd9
312+
--- /dev/null
313+
+++ b/Misc/NEWS.d/next/Library/2024-09-28-02-03-04.gh-issue-124651.bLBGtH.rst
314+
@@ -0,0 +1 @@
315+
+Properly quote template strings in :mod:`venv` activation scripts.

SPECS/python3/python3.spec

+5-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
Summary: A high-level scripting language
77
Name: python3
88
Version: 3.12.3
9-
Release: 4%{?dist}
9+
Release: 5%{?dist}
1010
License: PSF
1111
Vendor: Microsoft Corporation
1212
Distribution: Azure Linux
@@ -21,6 +21,7 @@ Patch1: CVE-2024-7592.patch
2121
Patch2: CVE-2024-6923.patch
2222
Patch3: CVE-2024-6232.patch
2323
Patch4: CVE-2024-8088.patch
24+
Patch5: CVE-2024-9287.patch
2425

2526
BuildRequires: bzip2-devel
2627
BuildRequires: expat-devel >= 2.1.0
@@ -242,6 +243,9 @@ rm -rf %{buildroot}%{_bindir}/__pycache__
242243
%{_libdir}/python%{majmin}/test/*
243244

244245
%changelog
246+
* Wed Nov 6 2024 Saul Paredes <[email protected]> - 3.12.3-5
247+
- Patch CVE-2024-9287
248+
245249
* Fri Sep 20 2024 Himaja Kesari <[email protected]> - 3.12.3-4
246250
- Patch CVE-2024-6232 and CVE-2024-8088
247251

toolkit/resources/manifests/package/pkggen_core_aarch64.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ ca-certificates-base-3.0.0-7.azl3.noarch.rpm
240240
ca-certificates-3.0.0-7.azl3.noarch.rpm
241241
dwz-0.14-2.azl3.aarch64.rpm
242242
unzip-6.0-20.azl3.aarch64.rpm
243-
python3-3.12.3-4.azl3.aarch64.rpm
243+
python3-3.12.3-5.azl3.aarch64.rpm
244244
python3-devel-3.12.3-4.azl3.aarch64.rpm
245245
python3-libs-3.12.3-4.azl3.aarch64.rpm
246246
python3-setuptools-69.0.3-4.azl3.noarch.rpm

toolkit/resources/manifests/package/pkggen_core_x86_64.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ ca-certificates-base-3.0.0-7.azl3.noarch.rpm
240240
ca-certificates-3.0.0-7.azl3.noarch.rpm
241241
dwz-0.14-2.azl3.x86_64.rpm
242242
unzip-6.0-20.azl3.x86_64.rpm
243-
python3-3.12.3-4.azl3.x86_64.rpm
243+
python3-3.12.3-5.azl3.x86_64.rpm
244244
python3-devel-3.12.3-4.azl3.x86_64.rpm
245245
python3-libs-3.12.3-4.azl3.x86_64.rpm
246246
python3-setuptools-69.0.3-4.azl3.noarch.rpm

toolkit/resources/manifests/package/toolchain_aarch64.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -529,7 +529,7 @@ pyproject-rpm-macros-1.12.0-2.azl3.noarch.rpm
529529
pyproject-srpm-macros-1.12.0-2.azl3.noarch.rpm
530530
python-markupsafe-debuginfo-2.1.3-1.azl3.aarch64.rpm
531531
python-wheel-wheel-0.43.0-1.azl3.noarch.rpm
532-
python3-3.12.3-4.azl3.aarch64.rpm
532+
python3-3.12.3-5.azl3.aarch64.rpm
533533
python3-audit-3.1.2-1.azl3.aarch64.rpm
534534
python3-cracklib-2.9.11-1.azl3.aarch64.rpm
535535
python3-curses-3.12.3-4.azl3.aarch64.rpm

toolkit/resources/manifests/package/toolchain_x86_64.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -535,7 +535,7 @@ pyproject-rpm-macros-1.12.0-2.azl3.noarch.rpm
535535
pyproject-srpm-macros-1.12.0-2.azl3.noarch.rpm
536536
python-markupsafe-debuginfo-2.1.3-1.azl3.x86_64.rpm
537537
python-wheel-wheel-0.43.0-1.azl3.noarch.rpm
538-
python3-3.12.3-4.azl3.x86_64.rpm
538+
python3-3.12.3-5.azl3.x86_64.rpm
539539
python3-audit-3.1.2-1.azl3.x86_64.rpm
540540
python3-cracklib-2.9.11-1.azl3.x86_64.rpm
541541
python3-curses-3.12.3-4.azl3.x86_64.rpm

0 commit comments

Comments
 (0)