Skip to content

Commit a58a205

Browse files
[Medium] Patch python-virtualenv for CVE-2025-50181, CVE-2026-24049, CVE-2026-1703 (#15951)
1 parent 085c900 commit a58a205

6 files changed

Lines changed: 300 additions & 2 deletions

File tree

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
From f05b1329126d5be6de501f9d1e3e36738bc08857 Mon Sep 17 00:00:00 2001
2+
From: Illia Volochii <illia.volochii@gmail.com>
3+
Date: Wed, 18 Jun 2025 16:25:01 +0300
4+
Subject: [PATCH] Merge commit from fork
5+
6+
* Apply Quentin's suggestion
7+
8+
Co-authored-by: Quentin Pradet <quentin.pradet@gmail.com>
9+
10+
* Add tests for disabled redirects in the pool manager
11+
12+
* Add a possible fix for the issue with not raised `MaxRetryError`
13+
14+
* Make urllib3 handle redirects instead of JS when JSPI is used
15+
16+
* Fix info in the new comment
17+
18+
* State that redirects with XHR are not controlled by urllib3
19+
20+
* Remove excessive params from new test requests
21+
22+
* Add tests reaching max non-0 redirects
23+
24+
* Test redirects with Emscripten
25+
26+
* Fix `test_merge_pool_kwargs`
27+
28+
* Add a changelog entry
29+
30+
* Parametrize tests
31+
32+
* Drop a fix for Emscripten
33+
34+
* Apply Seth's suggestion to docs
35+
36+
Co-authored-by: Seth Michael Larson <sethmichaellarson@gmail.com>
37+
38+
* Use a minor release instead of the patch one
39+
40+
---------
41+
42+
Co-authored-by: Quentin Pradet <quentin.pradet@gmail.com>
43+
Co-authored-by: Seth Michael Larson <sethmichaellarson@gmail.com>
44+
Upstream Patch Reference: https://github.com/urllib3/urllib3/commit/f05b1329126d5be6de501f9d1e3e36738bc08857.patch
45+
---
46+
pip/_vendor/urllib3/poolmanager.py | 18 +++++++++++++++++-
47+
1 file changed, 17 insertions(+), 1 deletion(-)
48+
49+
diff --git a/pip/_vendor/urllib3/poolmanager.py b/pip/_vendor/urllib3/poolmanager.py
50+
index fb51bf7..a8de7c6 100644
51+
--- a/pip/_vendor/urllib3/poolmanager.py
52+
+++ b/pip/_vendor/urllib3/poolmanager.py
53+
@@ -170,6 +170,22 @@ class PoolManager(RequestMethods):
54+
55+
def __init__(self, num_pools=10, headers=None, **connection_pool_kw):
56+
RequestMethods.__init__(self, headers)
57+
+ if "retries" in connection_pool_kw:
58+
+ retries = connection_pool_kw["retries"]
59+
+ if not isinstance(retries, Retry):
60+
+ # When Retry is initialized, raise_on_redirect is based
61+
+ # on a redirect boolean value.
62+
+ # But requests made via a pool manager always set
63+
+ # redirect to False, and raise_on_redirect always ends
64+
+ # up being False consequently.
65+
+ # Here we fix the issue by setting raise_on_redirect to
66+
+ # a value needed by the pool manager without considering
67+
+ # the redirect boolean.
68+
+ raise_on_redirect = retries is not False
69+
+ retries = Retry.from_int(retries, redirect=False)
70+
+ retries.raise_on_redirect = raise_on_redirect
71+
+ connection_pool_kw = connection_pool_kw.copy()
72+
+ connection_pool_kw["retries"] = retries
73+
self.connection_pool_kw = connection_pool_kw
74+
self.pools = RecentlyUsedContainer(num_pools)
75+
76+
@@ -389,7 +405,7 @@ class PoolManager(RequestMethods):
77+
kw["body"] = None
78+
kw["headers"] = HTTPHeaderDict(kw["headers"])._prepare_for_method_change()
79+
80+
- retries = kw.get("retries")
81+
+ retries = kw.get("retries", response.retries)
82+
if not isinstance(retries, Retry):
83+
retries = Retry.from_int(retries, redirect=redirect)
84+
85+
--
86+
2.45.4
87+
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
From 4c651b70d60ed91b13663bcda9b3ed41748d0124 Mon Sep 17 00:00:00 2001
2+
From: Seth Michael Larson <seth@python.org>
3+
Date: Fri, 30 Jan 2026 09:49:11 -0600
4+
Subject: [PATCH] Use os.path.commonpath() instead of commonprefix()
5+
6+
Upstream Patch Reference: https://github.com/pypa/pip/commit/8e227a9be4faa9594e05d02ca05a413a2a4e7735.patch
7+
---
8+
news/+1ee322a1.bugfix.rst | 1 +
9+
pip/_internal/utils/unpacking.py | 2 +-
10+
2 files changed, 2 insertions(+), 1 deletion(-)
11+
create mode 100644 news/+1ee322a1.bugfix.rst
12+
13+
diff --git a/news/+1ee322a1.bugfix.rst b/news/+1ee322a1.bugfix.rst
14+
new file mode 100644
15+
index 0000000..edb1b32
16+
--- /dev/null
17+
+++ b/news/+1ee322a1.bugfix.rst
18+
@@ -0,0 +1 @@
19+
+Use a path-segment prefix comparison, not char-by-char.
20+
diff --git a/pip/_internal/utils/unpacking.py b/pip/_internal/utils/unpacking.py
21+
index 87a6d19..b5c736d 100644
22+
--- a/pip/_internal/utils/unpacking.py
23+
+++ b/pip/_internal/utils/unpacking.py
24+
@@ -82,7 +82,7 @@ def is_within_directory(directory: str, target: str) -> bool:
25+
abs_directory = os.path.abspath(directory)
26+
abs_target = os.path.abspath(target)
27+
28+
- prefix = os.path.commonprefix([abs_directory, abs_target])
29+
+ prefix = os.path.commonpath([abs_directory, abs_target])
30+
return prefix == abs_directory
31+
32+
33+
--
34+
2.45.4
35+
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
From 4c651b70d60ed91b13663bcda9b3ed41748d0124 Mon Sep 17 00:00:00 2001
2+
From: Seth Michael Larson <seth@python.org>
3+
Date: Fri, 30 Jan 2026 09:49:11 -0600
4+
Subject: [PATCH] Use os.path.commonpath() instead of commonprefix()
5+
6+
Upstream Patch Reference: https://github.com/pypa/pip/commit/8e227a9be4faa9594e05d02ca05a413a2a4e7735.patch
7+
8+
---
9+
pip/_internal/utils/unpacking.py | 2 +-
10+
1 file changed, 1 insertion(+), 1 deletion(-)
11+
12+
diff --git a/pip/_internal/utils/unpacking.py b/pip/_internal/utils/unpacking.py
13+
index bc950ac..b3f52e8 100644
14+
--- a/pip/_internal/utils/unpacking.py
15+
+++ b/pip/_internal/utils/unpacking.py
16+
@@ -83,7 +83,7 @@ def is_within_directory(directory: str, target: str) -> bool:
17+
abs_directory = os.path.abspath(directory)
18+
abs_target = os.path.abspath(target)
19+
20+
- prefix = os.path.commonprefix([abs_directory, abs_target])
21+
+ prefix = os.path.commonpath([abs_directory, abs_target])
22+
return prefix == abs_directory
23+
24+
25+
--
26+
2.45.4
27+
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
From 7a7d2de96b22a9adf9208afcc9547e1001569fef Mon Sep 17 00:00:00 2001
2+
From: =?UTF-8?q?Alex=20Gr=C3=B6nholm?= <alex.gronholm@nextday.fi>
3+
Date: Thu, 22 Jan 2026 01:41:14 +0200
4+
Subject: [PATCH] Fixed security issue around wheel unpack (#675)
5+
6+
A maliciously crafted wheel could cause the permissions of a file outside the unpack tree to be altered.
7+
8+
Fixes CVE-2026-24049.
9+
Upstream Patch Reference: https://github.com/pypa/wheel/commit/7a7d2de96b22a9adf9208afcc9547e1001569fef.patch
10+
---
11+
setuptools/_vendor/wheel/cli/unpack.py | 4 ++--
12+
1 file changed, 2 insertions(+), 2 deletions(-)
13+
14+
diff --git a/setuptools/_vendor/wheel/cli/unpack.py b/setuptools/_vendor/wheel/cli/unpack.py
15+
index d48840e..83dc742 100644
16+
--- a/setuptools/_vendor/wheel/cli/unpack.py
17+
+++ b/setuptools/_vendor/wheel/cli/unpack.py
18+
@@ -19,12 +19,12 @@ def unpack(path: str, dest: str = ".") -> None:
19+
destination = Path(dest) / namever
20+
print(f"Unpacking to: {destination}...", end="", flush=True)
21+
for zinfo in wf.filelist:
22+
- wf.extract(zinfo, destination)
23+
+ target_path = Path(wf.extract(zinfo, destination))
24+
25+
# Set permissions to the same values as they were set in the archive
26+
# We have to do this manually due to
27+
# https://github.com/python/cpython/issues/59999
28+
permissions = zinfo.external_attr >> 16 & 0o777
29+
- destination.joinpath(zinfo.filename).chmod(permissions)
30+
+ target_path.chmod(permissions)
31+
32+
print("OK")
33+
--
34+
2.45.4
35+
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
From 7a7d2de96b22a9adf9208afcc9547e1001569fef Mon Sep 17 00:00:00 2001
2+
From: =?UTF-8?q?Alex=20Gr=C3=B6nholm?= <alex.gronholm@nextday.fi>
3+
Date: Thu, 22 Jan 2026 01:41:14 +0200
4+
Subject: [PATCH] Fixed security issue around wheel unpack (#675)
5+
6+
A maliciously crafted wheel could cause the permissions of a file outside the unpack tree to be altered.
7+
8+
Fixes CVE-2026-24049.
9+
Upstream Patch Reference: https://github.com/pypa/wheel/commit/7a7d2de96b22a9adf9208afcc9547e1001569fef.patch
10+
---
11+
wheel/cli/unpack.py | 4 ++--
12+
1 file changed, 2 insertions(+), 2 deletions(-)
13+
14+
diff --git a/wheel/cli/unpack.py b/wheel/cli/unpack.py
15+
index d48840e..83dc742 100644
16+
--- a/wheel/cli/unpack.py
17+
+++ b/wheel/cli/unpack.py
18+
@@ -19,12 +19,12 @@ def unpack(path: str, dest: str = ".") -> None:
19+
destination = Path(dest) / namever
20+
print(f"Unpacking to: {destination}...", end="", flush=True)
21+
for zinfo in wf.filelist:
22+
- wf.extract(zinfo, destination)
23+
+ target_path = Path(wf.extract(zinfo, destination))
24+
25+
# Set permissions to the same values as they were set in the archive
26+
# We have to do this manually due to
27+
# https://github.com/python/cpython/issues/59999
28+
permissions = zinfo.external_attr >> 16 & 0o777
29+
- destination.joinpath(zinfo.filename).chmod(permissions)
30+
+ target_path.chmod(permissions)
31+
32+
print("OK")
33+
--
34+
2.45.4
35+

SPECS/python-virtualenv/python-virtualenv.spec

Lines changed: 81 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
11
Summary: Virtual Python Environment builder
22
Name: python-virtualenv
33
Version: 20.36.1
4-
Release: 1%{?dist}
4+
Release: 2%{?dist}
55
License: MIT
66
Vendor: Microsoft Corporation
77
Distribution: Azure Linux
88
Group: Development/Languages/Python
99
URL: https://pypi.python.org/pypi/virtualenv
1010
Source0: https://files.pythonhosted.org/packages/aa/a3/4d310fa5f00863544e1d0f4de93bddec248499ccf97d4791bc3122c9d4f3/virtualenv-20.36.1.tar.gz
1111
Patch0: 0001-replace-to-flit.patch
12+
Patch1000: CVE-2025-50181.patch
13+
Patch1001: CVE-2026-1703v0.patch
14+
Patch1002: CVE-2026-1703v1.patch
15+
Patch1003: CVE-2026-24049v0.patch
16+
Patch1004: CVE-2026-24049v1.patch
1217
BuildArch: noarch
1318

1419
%description
@@ -20,6 +25,7 @@ BuildRequires: python3-devel
2025
BuildRequires: python3-setuptools_scm
2126
BuildRequires: python3-xml
2227
BuildRequires: python3-wheel
28+
BuildRequires: zip
2329

2430
%if 0%{?with_check}
2531
BuildRequires: python3-pip
@@ -37,7 +43,77 @@ Provides: %{name}-doc = %{version}-%{release}
3743
virtualenv is a tool to create isolated Python environment.
3844

3945
%prep
40-
%autosetup -p1 -n virtualenv-%{version}
46+
# Adding -N to enable manual patching, needed for CVE-2025-50181
47+
%autosetup -p1 -n virtualenv-%{version} -N
48+
%patch -P 0 -p1
49+
50+
# Manual patching for CVE-2025-50181 and CVE-2026-1703v0
51+
# For CVE-2025-50181, poolmanager.py file is located in 2 different places and each is of different version so the same patch cannot be applied to all of them.
52+
# For CVE-2026-1703, unpacking.py file is located in 2 different places and each is of different version so the same patch cannot be applied to all of them.
53+
# Affected files are under src and archived inside a .whl file, so we need to unpack it, apply the patch, and then re-zip it.
54+
55+
echo "Manually Patching virtualenv-20.36.1/src/virtualenv/seed/wheels/embed/pip-25.0.1-py3-none-any.whl/pip/_vendor/urllib3/poolmanager.py"
56+
mkdir -p unpacked_pip-25.0.1-py3-none-any
57+
unzip src/virtualenv/seed/wheels/embed/pip-25.0.1-py3-none-any.whl -d unpacked_pip-25.0.1-py3-none-any
58+
patch -p1 -d unpacked_pip-25.0.1-py3-none-any < %{PATCH1000}
59+
echo "Manually Patching virtualenv-20.36.1/src/virtualenv/seed/wheels/embed/pip-25.0.1-py3-none-any.whl/pip/_internal/utils/unpacking.py"
60+
patch -p1 -d unpacked_pip-25.0.1-py3-none-any < %{PATCH1001}
61+
# Remove the original file
62+
rm -f src/virtualenv/seed/wheels/embed/pip-25.0.1-py3-none-any.whl
63+
# After patching, re-zip the contents back into a .whl
64+
pushd unpacked_pip-25.0.1-py3-none-any
65+
zip -r ../src/virtualenv/seed/wheels/embed/pip-25.0.1-py3-none-any.whl *
66+
popd
67+
rm -rf unpacked_pip-25.0.1-py3-none-any
68+
69+
# Manual patching for CVE-2025-50181 and CVE-2026-1703v1
70+
echo "Manually Patching virtualenv-20.36.1/src/virtualenv/seed/wheels/embed/pip-25.3-py3-none-any.whl/pip/_vendor/urllib3/poolmanager.py"
71+
mkdir -p unpacked_pip-25.3-py3-none-any
72+
unzip src/virtualenv/seed/wheels/embed/pip-25.3-py3-none-any.whl -d unpacked_pip-25.3-py3-none-any
73+
patch -p1 -d unpacked_pip-25.3-py3-none-any < %{PATCH1000}
74+
echo "Manually Patching virtualenv-20.36.1/src/virtualenv/seed/wheels/embed/pip-25.3-py3-none-any.whl/pip/_internal/utils/unpacking.py"
75+
patch -p1 -d unpacked_pip-25.3-py3-none-any < %{PATCH1002}
76+
rm -f src/virtualenv/seed/wheels/embed/pip-25.3-py3-none-any.whl
77+
pushd unpacked_pip-25.3-py3-none-any
78+
zip -r ../src/virtualenv/seed/wheels/embed/pip-25.3-py3-none-any.whl *
79+
popd
80+
rm -rf unpacked_pip-25.3-py3-none-any
81+
82+
# Manual patching for CVE-2026-24049v0
83+
# For CVE-2026-24049, unpack.py file is located in 3 different places and each is of different version so the same patch cannot be applied to all of them.
84+
# Affected files are under src and archived inside a .whl file, so we need to unpack it, apply the patch, and then re-zip it.
85+
echo "Manually Patching virtualenv-20.36.1/src/virtualenv/seed/wheels/embed/setuptools-75.3.2-py3-none-any.whl/setuptools/_vendor/wheel/cli/unpack.py"
86+
mkdir -p unpacked_setuptools-75.3.2-py3-none-any
87+
unzip src/virtualenv/seed/wheels/embed/setuptools-75.3.2-py3-none-any.whl -d unpacked_setuptools-75.3.2-py3-none-any
88+
patch -p1 -d unpacked_setuptools-75.3.2-py3-none-any < %{PATCH1003}
89+
rm -f src/virtualenv/seed/wheels/embed/setuptools-75.3.2-py3-none-any.whl
90+
pushd unpacked_setuptools-75.3.2-py3-none-any
91+
zip -r ../src/virtualenv/seed/wheels/embed/setuptools-75.3.2-py3-none-any.whl *
92+
popd
93+
rm -rf unpacked_setuptools-75.3.2-py3-none-any
94+
95+
# Manual patching for CVE-2026-24049v0
96+
echo "Manually Patching virtualenv-20.36.1/src/virtualenv/seed/wheels/embed/setuptools-80.9.0-py3-none-any.whl/setuptools/_vendor/wheel/cli/unpack.py"
97+
mkdir -p unpacked_setuptools-80.9.0-py3-none-any
98+
unzip src/virtualenv/seed/wheels/embed/setuptools-80.9.0-py3-none-any.whl -d unpacked_setuptools-80.9.0-py3-none-any
99+
patch -p1 -d unpacked_setuptools-80.9.0-py3-none-any < %{PATCH1003}
100+
rm -f src/virtualenv/seed/wheels/embed/setuptools-80.9.0-py3-none-any.whl
101+
pushd unpacked_setuptools-80.9.0-py3-none-any
102+
zip -r ../src/virtualenv/seed/wheels/embed/setuptools-80.9.0-py3-none-any.whl *
103+
popd
104+
rm -rf unpacked_setuptools-80.9.0-py3-none-any
105+
106+
# Manual patching for CVE-2026-24049v1
107+
echo "Manually Patching virtualenv-20.36.1/src/virtualenv/seed/wheels/embed/unpacked_wheel-0.45.1-py3-none-any.whl/wheel/cli/unpack.py"
108+
mkdir -p unpacked_wheel-0.45.1-py3-none-any
109+
unzip src/virtualenv/seed/wheels/embed/wheel-0.45.1-py3-none-any.whl -d unpacked_wheel-0.45.1-py3-none-any
110+
patch -p1 -d unpacked_wheel-0.45.1-py3-none-any < %{PATCH1004}
111+
rm -f src/virtualenv/seed/wheels/embed/wheel-0.45.1-py3-none-any.whl
112+
pushd unpacked_wheel-0.45.1-py3-none-any
113+
zip -r ../src/virtualenv/seed/wheels/embed/unpacked_wheel-0.45.1-py3-none-any.whl *
114+
popd
115+
rm -rf unpacked_wheel-0.45.1-py3-none-any
116+
41117

42118
%generate_buildrequires
43119

@@ -60,6 +136,9 @@ tox -e py
60136
%{_bindir}/virtualenv
61137

62138
%changelog
139+
* Mon Feb 23 2026 BinduSri Adabala <v-badabala@microsoft.com> - 20.36.1-2
140+
- Patch for CVE-2025-50181, CVE-2026-24049 and CVE-2026-1703
141+
63142
* Wed Jan 14 2026 Archana Shettigar <v-shettigara@microsoft.com> - 20.36.1-1
64143
- Upgrade to 20.36.1 for CVE-2026-22702
65144

0 commit comments

Comments
 (0)