Skip to content

Commit 9aca93b

Browse files
authored
[MEDIUM] Patch for python-virtualenv CVE-2025-50181 (microsoft#14239)
1 parent f0d3b57 commit 9aca93b

File tree

5 files changed

+272
-2
lines changed

5 files changed

+272
-2
lines changed
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
From e942dd93a09e2eb8a52097c76075d0a42be20f39 Mon Sep 17 00:00:00 2001
2+
From: Aninda <[email protected]>
3+
Date: Tue, 8 Jul 2025 10:53:14 -0400
4+
Subject: [PATCH] Address CVE-2025-50181-1
5+
Upstream Patch Reference: https://github.com/urllib3/urllib3/commit/f05b1329126d5be6de501f9d1e3e36738bc08857
6+
---
7+
pip/_vendor/urllib3/poolmanager.py | 18 +++++++++++++++++-
8+
1 file changed, 17 insertions(+), 1 deletion(-)
9+
10+
diff --git a/pip/_vendor/urllib3/poolmanager.py b/pip/_vendor/urllib3/poolmanager.py
11+
index 14b10da..574b7de 100644
12+
--- a/pip/_vendor/urllib3/poolmanager.py
13+
+++ b/pip/_vendor/urllib3/poolmanager.py
14+
@@ -170,6 +170,22 @@ class PoolManager(RequestMethods):
15+
16+
def __init__(self, num_pools=10, headers=None, **connection_pool_kw):
17+
RequestMethods.__init__(self, headers)
18+
+ if "retries" in connection_pool_kw:
19+
+ retries = connection_pool_kw["retries"]
20+
+ if not isinstance(retries, Retry):
21+
+ # When Retry is initialized, raise_on_redirect is based
22+
+ # on a redirect boolean value.
23+
+ # But requests made via a pool manager always set
24+
+ # redirect to False, and raise_on_redirect always ends
25+
+ # up being False consequently.
26+
+ # Here we fix the issue by setting raise_on_redirect to
27+
+ # a value needed by the pool manager without considering
28+
+ # the redirect boolean.
29+
+ raise_on_redirect = retries is not False
30+
+ retries = Retry.from_int(retries, redirect=False)
31+
+ retries.raise_on_redirect = raise_on_redirect
32+
+ connection_pool_kw = connection_pool_kw.copy()
33+
+ connection_pool_kw["retries"] = retries
34+
self.connection_pool_kw = connection_pool_kw
35+
self.pools = RecentlyUsedContainer(num_pools)
36+
37+
@@ -386,7 +402,7 @@ class PoolManager(RequestMethods):
38+
if response.status == 303:
39+
method = "GET"
40+
41+
- retries = kw.get("retries")
42+
+ retries = kw.get("retries", response.retries)
43+
if not isinstance(retries, Retry):
44+
retries = Retry.from_int(retries, redirect=redirect)
45+
46+
--
47+
2.34.1
48+
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
From 8275bde7d461c4d6cd44397529fcb75847eee97c Mon Sep 17 00:00:00 2001
2+
From: Aninda <[email protected]>
3+
Date: Wed, 9 Jul 2025 22:12:03 -0400
4+
Subject: [PATCH] Address CVE-xxxx-yyyy
5+
Upstream Patch Reference: https://github.com/urllib3/urllib3/commit/f05b1329126d5be6de501f9d1e3e36738bc08857
6+
---
7+
pip/_vendor/urllib3/poolmanager.py | 18 +++++++++++++++++-
8+
1 file changed, 17 insertions(+), 1 deletion(-)
9+
10+
diff --git a/pip/_vendor/urllib3/poolmanager.py b/pip/_vendor/urllib3/poolmanager.py
11+
index fb51bf7..a8de7c6 100644
12+
--- a/pip/_vendor/urllib3/poolmanager.py
13+
+++ b/pip/_vendor/urllib3/poolmanager.py
14+
@@ -170,6 +170,22 @@ class PoolManager(RequestMethods):
15+
16+
def __init__(self, num_pools=10, headers=None, **connection_pool_kw):
17+
RequestMethods.__init__(self, headers)
18+
+ if "retries" in connection_pool_kw:
19+
+ retries = connection_pool_kw["retries"]
20+
+ if not isinstance(retries, Retry):
21+
+ # When Retry is initialized, raise_on_redirect is based
22+
+ # on a redirect boolean value.
23+
+ # But requests made via a pool manager always set
24+
+ # redirect to False, and raise_on_redirect always ends
25+
+ # up being False consequently.
26+
+ # Here we fix the issue by setting raise_on_redirect to
27+
+ # a value needed by the pool manager without considering
28+
+ # the redirect boolean.
29+
+ raise_on_redirect = retries is not False
30+
+ retries = Retry.from_int(retries, redirect=False)
31+
+ retries.raise_on_redirect = raise_on_redirect
32+
+ connection_pool_kw = connection_pool_kw.copy()
33+
+ connection_pool_kw["retries"] = retries
34+
self.connection_pool_kw = connection_pool_kw
35+
self.pools = RecentlyUsedContainer(num_pools)
36+
37+
@@ -389,7 +405,7 @@ class PoolManager(RequestMethods):
38+
kw["body"] = None
39+
kw["headers"] = HTTPHeaderDict(kw["headers"])._prepare_for_method_change()
40+
41+
- retries = kw.get("retries")
42+
+ retries = kw.get("retries", response.retries)
43+
if not isinstance(retries, Retry):
44+
retries = Retry.from_int(retries, redirect=redirect)
45+
46+
--
47+
2.34.1
48+
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
From b9f02b9d46967b99beab18718c79e79e08304c89 Mon Sep 17 00:00:00 2001
2+
From: Aninda <[email protected]>
3+
Date: Tue, 8 Jul 2025 22:33:17 -0400
4+
Subject: [PATCH] Address CVE-2025-50181v2
5+
Upstream Patch Reference: https://github.com/urllib3/urllib3/commit/f05b1329126d5be6de501f9d1e3e36738bc08857
6+
---
7+
pip/_vendor/urllib3/poolmanager.py | 18 +++++++++++++++++-
8+
1 file changed, 17 insertions(+), 1 deletion(-)
9+
10+
diff --git a/pip/_vendor/urllib3/poolmanager.py b/pip/_vendor/urllib3/poolmanager.py
11+
index fe5491c..d03b343 100644
12+
--- a/pip/_vendor/urllib3/poolmanager.py
13+
+++ b/pip/_vendor/urllib3/poolmanager.py
14+
@@ -151,6 +151,22 @@ class PoolManager(RequestMethods):
15+
16+
def __init__(self, num_pools=10, headers=None, **connection_pool_kw):
17+
RequestMethods.__init__(self, headers)
18+
+ if "retries" in connection_pool_kw:
19+
+ retries = connection_pool_kw["retries"]
20+
+ if not isinstance(retries, Retry):
21+
+ # When Retry is initialized, raise_on_redirect is based
22+
+ # on a redirect boolean value.
23+
+ # But requests made via a pool manager always set
24+
+ # redirect to False, and raise_on_redirect always ends
25+
+ # up being False consequently.
26+
+ # Here we fix the issue by setting raise_on_redirect to
27+
+ # a value needed by the pool manager without considering
28+
+ # the redirect boolean.
29+
+ raise_on_redirect = retries is not False
30+
+ retries = Retry.from_int(retries, redirect=False)
31+
+ retries.raise_on_redirect = raise_on_redirect
32+
+ connection_pool_kw = connection_pool_kw.copy()
33+
+ connection_pool_kw["retries"] = retries
34+
self.connection_pool_kw = connection_pool_kw
35+
self.pools = RecentlyUsedContainer(num_pools,
36+
dispose_func=lambda p: p.close())
37+
@@ -333,7 +349,7 @@ class PoolManager(RequestMethods):
38+
if response.status == 303:
39+
method = 'GET'
40+
41+
- retries = kw.get('retries')
42+
+ retries = kw.get("retries", response.retries)
43+
if not isinstance(retries, Retry):
44+
retries = Retry.from_int(retries, redirect=redirect)
45+
46+
--
47+
2.34.1
48+
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
From 3d5efc9facfee6c0136bbe14e02000de2cfcef23 Mon Sep 17 00:00:00 2001
2+
From: Aninda <[email protected]>
3+
Date: Wed, 9 Jul 2025 07:33:05 -0400
4+
Subject: [PATCH] Address CVE-2025-50181v3
5+
Upstream Patch Reference: https://github.com/urllib3/urllib3/commit/f05b1329126d5be6de501f9d1e3e36738bc08857
6+
---
7+
pip/_vendor/urllib3/poolmanager.py | 18 +++++++++++++++++-
8+
1 file changed, 17 insertions(+), 1 deletion(-)
9+
10+
diff --git a/pip/_vendor/urllib3/poolmanager.py b/pip/_vendor/urllib3/poolmanager.py
11+
index 242a2f8..c542cea 100644
12+
--- a/pip/_vendor/urllib3/poolmanager.py
13+
+++ b/pip/_vendor/urllib3/poolmanager.py
14+
@@ -158,6 +158,22 @@ class PoolManager(RequestMethods):
15+
16+
def __init__(self, num_pools=10, headers=None, **connection_pool_kw):
17+
RequestMethods.__init__(self, headers)
18+
+ if "retries" in connection_pool_kw:
19+
+ retries = connection_pool_kw["retries"]
20+
+ if not isinstance(retries, Retry):
21+
+ # When Retry is initialized, raise_on_redirect is based
22+
+ # on a redirect boolean value.
23+
+ # But requests made via a pool manager always set
24+
+ # redirect to False, and raise_on_redirect always ends
25+
+ # up being False consequently.
26+
+ # Here we fix the issue by setting raise_on_redirect to
27+
+ # a value needed by the pool manager without considering
28+
+ # the redirect boolean.
29+
+ raise_on_redirect = retries is not False
30+
+ retries = Retry.from_int(retries, redirect=False)
31+
+ retries.raise_on_redirect = raise_on_redirect
32+
+ connection_pool_kw = connection_pool_kw.copy()
33+
+ connection_pool_kw["retries"] = retries
34+
self.connection_pool_kw = connection_pool_kw
35+
self.pools = RecentlyUsedContainer(num_pools, dispose_func=lambda p: p.close())
36+
37+
@@ -340,7 +356,7 @@ class PoolManager(RequestMethods):
38+
if response.status == 303:
39+
method = "GET"
40+
41+
- retries = kw.get("retries")
42+
+ retries = kw.get("retries", response.retries)
43+
if not isinstance(retries, Retry):
44+
retries = Retry.from_int(retries, redirect=redirect)
45+
46+
--
47+
2.34.1
48+

SPECS/python-virtualenv/python-virtualenv.spec

Lines changed: 80 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
Summary: Virtual Python Environment builder
22
Name: python-virtualenv
33
Version: 20.26.6
4-
Release: 1%{?dist}
4+
Release: 2%{?dist}
55
License: MIT
66
Vendor: Microsoft Corporation
77
Distribution: Mariner
88
Group: Development/Languages/Python
99
URL: https://pypi.python.org/pypi/virtualenv
1010
Source0: https://files.pythonhosted.org/packages/3f/40/abc5a766da6b0b2457f819feab8e9203cbeae29327bd241359f866a3da9d/virtualenv-20.26.6.tar.gz#/%{name}-%{version}.tar.gz
1111
Patch0: 0001-replace-to-flit.patch
12+
Patch1000: CVE-2025-50181v0.patch
13+
Patch1001: CVE-2025-50181v1.patch
14+
Patch1002: CVE-2025-50181v2.patch
15+
Patch1003: CVE-2025-50181v3.patch
1216
BuildArch: noarch
1317

1418
%description
@@ -20,6 +24,7 @@ BuildRequires: python3-devel
2024
BuildRequires: python3-setuptools_scm
2125
BuildRequires: python3-xml
2226
BuildRequires: python3-wheel
27+
BuildRequires: zip
2328

2429
%if 0%{?with_check}
2530
BuildRequires: python3-pip
@@ -38,7 +43,77 @@ Provides: %{name}-doc = %{version}-%{release}
3843
virtualenv is a tool to create isolated Python environment.
3944

4045
%prep
41-
%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+
%patch0 -p1
49+
50+
# Manual patching for CVE-2025-50181
51+
# This patch is needed to fix the issue with urllib3 poolmanager.py
52+
# The poolmanager.py file is located in 4 different places and each is of different version so the same patch cannot be applied to all of them.
53+
# For the poolmanager.py under src, it is archived inside a .whl file, so we need to unpack it, apply the patch, and then re-zip it.
54+
# For the poolmanager.py under tests, it is archived inside a .whl file, which in turn is archived inside another .whl file,
55+
# so, we need to unpack the outer .whl, then unpack the inner .whl, apply the patch, and then re-zip both levels.
56+
57+
echo "Manually Patching virtualenv-20.26.6/src/virtualenv/seed/wheels/embed/pip-24.0-py3-none-any.whl/pip/_vendor/urllib3/poolmanager.py"
58+
mkdir -p unpacked_pip-24.0-py3-none-any
59+
unzip src/virtualenv/seed/wheels/embed/pip-24.0-py3-none-any.whl -d unpacked_pip-24.0-py3-none-any
60+
patch -p1 -d unpacked_pip-24.0-py3-none-any < %{PATCH1000}
61+
# Remove the original file
62+
rm -f src/virtualenv/seed/wheels/embed/pip-24.0-py3-none-any.whl
63+
# After patching, re-zip the contents back into a .whl
64+
pushd unpacked_pip-24.0-py3-none-any
65+
zip -r ../src/virtualenv/seed/wheels/embed/pip-24.0-py3-none-any.whl *
66+
popd
67+
rm -rf unpacked_pip-24.0-py3-none-any
68+
69+
echo "Manually Patching virtualenv-20.26.6/src/virtualenv/seed/wheels/embed/pip-24.2-py3-none-any.whl/pip/_vendor/urllib3/poolmanager.py"
70+
mkdir -p unpacked_pip-24.2-py3-none-any
71+
unzip src/virtualenv/seed/wheels/embed/pip-24.2-py3-none-any.whl -d unpacked_pip-24.2-py3-none-any
72+
patch -p1 -d unpacked_pip-24.2-py3-none-any < %{PATCH1001}
73+
# Remove the original file
74+
rm -f src/virtualenv/seed/wheels/embed/pip-24.2-py3-none-any.whl
75+
# After patching, re-zip the contents back into a .whl
76+
pushd unpacked_pip-24.2-py3-none-any
77+
zip -r ../src/virtualenv/seed/wheels/embed/pip-24.2-py3-none-any.whl *
78+
popd
79+
rm -rf unpacked_pip-24.2-py3-none-any
80+
81+
echo "Manually Patching the poolmanager.py under tests, it needs to be unpacked from a .whl file, which is inside another .whl file"
82+
# unpack the outer wheel
83+
mkdir -p unpacked_virtualenv-16.7.9-py2.py3-none-any
84+
unzip tests/unit/create/virtualenv-16.7.9-py2.py3-none-any.whl -d unpacked_virtualenv-16.7.9-py2.py3-none-any
85+
86+
# This is the pip-19.1.1 wheel that is archived inside the virtualenv_support directory of the outer wheel
87+
# We need to unpack it, apply the patch, and then re-zip it
88+
echo "Manually Patching virtualenv-16.7.9-py2.py3-none-any/virtualenv_support/pip-19.1.1-py2.py3-none-any.whl/pip/_vendor/urllib3/poolmanager.py"
89+
# unpack the inner wheel
90+
mkdir -p unpacked_pip-19.1.1-py2.py3-none-any
91+
unzip unpacked_virtualenv-16.7.9-py2.py3-none-any/virtualenv_support/pip-19.1.1-py2.py3-none-any.whl -d unpacked_pip-19.1.1-py2.py3-none-any
92+
patch -p1 -d unpacked_pip-19.1.1-py2.py3-none-any < %{PATCH1002}
93+
rm -f unpacked_virtualenv-16.7.9-py2.py3-none-any/virtualenv_support/pip-19.1.1-py2.py3-none-any.whl
94+
pushd unpacked_pip-19.1.1-py2.py3-none-any
95+
zip -r ../unpacked_virtualenv-16.7.9-py2.py3-none-any/virtualenv_support/pip-19.1.1-py2.py3-none-any.whl *
96+
popd
97+
rm -rf unpacked_pip-19.1.1-py2.py3-none-any
98+
99+
# Now, we need to patch the pip-19.3.1 wheel that is archived inside the virtualenv_support directory of the outer wheel
100+
# We need to unpack it, apply the patch, and then re-zip it
101+
echo "Manually Patching virtualenv-16.7.9-py2.py3-none-any/virtualenv_support/pip-19.3.1-py2.py3-none-any.whl/pip/_vendor/urllib3/poolmanager.py"
102+
mkdir -p unpacked_pip-19.3.1-py2.py3-none-any
103+
unzip unpacked_virtualenv-16.7.9-py2.py3-none-any/virtualenv_support/pip-19.3.1-py2.py3-none-any.whl -d unpacked_pip-19.3.1-py2.py3-none-any
104+
patch -p1 -d unpacked_pip-19.3.1-py2.py3-none-any < %{PATCH1003}
105+
# Repack the inner wheel
106+
rm -f unpacked_virtualenv-16.7.9-py2.py3-none-any/virtualenv_support/pip-19.3.1-py2.py3-none-any.whl
107+
pushd unpacked_pip-19.3.1-py2.py3-none-any
108+
zip -r ../unpacked_virtualenv-16.7.9-py2.py3-none-any/virtualenv_support/pip-19.3.1-py2.py3-none-any.whl *
109+
popd
110+
rm -rf unpacked_pip-19.3.1-py2.py3-none-any
111+
112+
# Repack the outer wheel
113+
rm -f tests/unit/create/virtualenv-16.7.9-py2.py3-none-any.whl
114+
pushd unpacked_virtualenv-16.7.9-py2.py3-none-any
115+
zip -r ../tests/unit/create/unpacked_virtualenv-16.7.9-py2.py3-none-any *
116+
popd
42117

43118
%generate_buildrequires
44119

@@ -61,6 +136,9 @@ tox -e py
61136
%{_bindir}/virtualenv
62137

63138
%changelog
139+
* Wed Jul 09 2025 Aninda Pradhan <[email protected]> - 20.26.6-2
140+
- Add patch to fix CVE-2025-50181 in urllib3 poolmanager.py
141+
64142
* Wed Feb 26 2025 CBL-Mariner Servicing Account <[email protected]> - 20.26.6-1
65143
- Auto-upgrade to 20.26.6 - for CVE-2024-53899 [High]
66144
- Remove previously applied patches

0 commit comments

Comments
 (0)