Skip to content

Commit 8f15a00

Browse files
[Medium] Patch python-urllib3 for CVE-2025-50181 (microsoft#14094)
1 parent 4281707 commit 8f15a00

File tree

2 files changed

+199
-1
lines changed

2 files changed

+199
-1
lines changed
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
From ae6bf09c81cb7d415983ae7a08d805dd47149318 Mon Sep 17 00:00:00 2001
2+
From: dj_palli <[email protected]>
3+
Date: Tue, 24 Jun 2025 20:58:33 +0000
4+
Subject: [PATCH] Address CVE-2025-50181
5+
6+
Upstream patch reference: https://github.com/urllib3/urllib3/commit/f05b1329126d5be6de501f9d1e3e36738bc08857
7+
8+
---
9+
CHANGES.rst | 2 +
10+
src/urllib3/poolmanager.py | 16 ++++
11+
test/test_poolmanager.py | 5 +-
12+
test/with_dummyserver/test_poolmanager.py | 102 ++++++++++++++++++++++
13+
4 files changed, 123 insertions(+), 2 deletions(-)
14+
15+
diff --git a/CHANGES.rst b/CHANGES.rst
16+
index 6c37aeb..e6db1de 100644
17+
--- a/CHANGES.rst
18+
+++ b/CHANGES.rst
19+
@@ -2,6 +2,8 @@
20+
==================
21+
22+
* Made body stripped from HTTP requests changing the request method to GET after HTTP 303 "See Other" redirect responses.
23+
+- Fixed a security issue where restricting the maximum number of followed redirects at the urllib3.PoolManager level via the retries parameter did not work.
24+
+- TODO: add other entries in the release PR.
25+
26+
2.0.6 (2023-10-02)
27+
==================
28+
diff --git a/src/urllib3/poolmanager.py b/src/urllib3/poolmanager.py
29+
index 3c92a14..e2b3a12 100644
30+
--- a/src/urllib3/poolmanager.py
31+
+++ b/src/urllib3/poolmanager.py
32+
@@ -203,6 +203,22 @@ class PoolManager(RequestMethods):
33+
**connection_pool_kw: typing.Any,
34+
) -> None:
35+
super().__init__(headers)
36+
+ if "retries" in connection_pool_kw:
37+
+ retries = connection_pool_kw["retries"]
38+
+ if not isinstance(retries, Retry):
39+
+ # When Retry is initialized, raise_on_redirect is based
40+
+ # on a redirect boolean value.
41+
+ # But requests made via a pool manager always set
42+
+ # redirect to False, and raise_on_redirect always ends
43+
+ # up being False consequently.
44+
+ # Here we fix the issue by setting raise_on_redirect to
45+
+ # a value needed by the pool manager without considering
46+
+ # the redirect boolean.
47+
+ raise_on_redirect = retries is not False
48+
+ retries = Retry.from_int(retries, redirect=False)
49+
+ retries.raise_on_redirect = raise_on_redirect
50+
+ connection_pool_kw = connection_pool_kw.copy()
51+
+ connection_pool_kw["retries"] = retries
52+
self.connection_pool_kw = connection_pool_kw
53+
54+
self.pools: RecentlyUsedContainer[PoolKey, HTTPConnectionPool]
55+
diff --git a/test/test_poolmanager.py b/test/test_poolmanager.py
56+
index 821e218..6693159 100644
57+
--- a/test/test_poolmanager.py
58+
+++ b/test/test_poolmanager.py
59+
@@ -375,9 +375,10 @@ class TestPoolManager:
60+
61+
def test_merge_pool_kwargs(self) -> None:
62+
"""Assert _merge_pool_kwargs works in the happy case"""
63+
- p = PoolManager(retries=100)
64+
+ retries = retry.Retry(total=100)
65+
+ p = PoolManager(retries=retries)
66+
merged = p._merge_pool_kwargs({"new_key": "value"})
67+
- assert {"retries": 100, "new_key": "value"} == merged
68+
+ assert {"retries": retries, "new_key": "value"} == merged
69+
70+
def test_merge_pool_kwargs_none(self) -> None:
71+
"""Assert false-y values to _merge_pool_kwargs result in defaults"""
72+
diff --git a/test/with_dummyserver/test_poolmanager.py b/test/with_dummyserver/test_poolmanager.py
73+
index cd842c1..15532bb 100644
74+
--- a/test/with_dummyserver/test_poolmanager.py
75+
+++ b/test/with_dummyserver/test_poolmanager.py
76+
@@ -81,6 +81,90 @@ class TestPoolManager(HTTPDummyServerTestCase):
77+
assert r.status == 200
78+
assert r.data == b"Dummy server!"
79+
80+
+
81+
+ @pytest.mark.parametrize(
82+
+ "retries",
83+
+ (0, Retry(total=0), Retry(redirect=0), Retry(total=0, redirect=0)),
84+
+ )
85+
+ def test_redirects_disabled_for_pool_manager_with_0(
86+
+ self, retries: typing.Literal[0] | Retry
87+
+ ) -> None:
88+
+ """
89+
+ Check handling redirects when retries is set to 0 on the pool
90+
+ manager.
91+
+ """
92+
+ with PoolManager(retries=retries) as http:
93+
+ with pytest.raises(MaxRetryError):
94+
+ http.request("GET", f"{self.base_url}/redirect")
95+
+
96+
+ # Setting redirect=True should not change the behavior.
97+
+ with pytest.raises(MaxRetryError):
98+
+ http.request("GET", f"{self.base_url}/redirect", redirect=True)
99+
+
100+
+ # Setting redirect=False should not make it follow the redirect,
101+
+ # but MaxRetryError should not be raised.
102+
+ response = http.request("GET", f"{self.base_url}/redirect", redirect=False)
103+
+ assert response.status == 303
104+
+
105+
+ @pytest.mark.parametrize(
106+
+ "retries",
107+
+ (
108+
+ False,
109+
+ Retry(total=False),
110+
+ Retry(redirect=False),
111+
+ Retry(total=False, redirect=False),
112+
+ ),
113+
+ )
114+
+ def test_redirects_disabled_for_pool_manager_with_false(
115+
+ self, retries: typing.Literal[False] | Retry
116+
+ ) -> None:
117+
+ """
118+
+ Check that setting retries set to False on the pool manager disables
119+
+ raising MaxRetryError and redirect=True does not change the
120+
+ behavior.
121+
+ """
122+
+ with PoolManager(retries=retries) as http:
123+
+ response = http.request("GET", f"{self.base_url}/redirect")
124+
+ assert response.status == 303
125+
+
126+
+ response = http.request("GET", f"{self.base_url}/redirect", redirect=True)
127+
+ assert response.status == 303
128+
+
129+
+ response = http.request("GET", f"{self.base_url}/redirect", redirect=False)
130+
+ assert response.status == 303
131+
+
132+
+ def test_redirects_disabled_for_individual_request(self) -> None:
133+
+ """
134+
+ Check handling redirects when they are meant to be disabled
135+
+ on the request level.
136+
+ """
137+
+ with PoolManager() as http:
138+
+ # Check when redirect is not passed.
139+
+ with pytest.raises(MaxRetryError):
140+
+ http.request("GET", f"{self.base_url}/redirect", retries=0)
141+
+ response = http.request("GET", f"{self.base_url}/redirect", retries=False)
142+
+ assert response.status == 303
143+
+
144+
+ # Check when redirect=True.
145+
+ with pytest.raises(MaxRetryError):
146+
+ http.request(
147+
+ "GET", f"{self.base_url}/redirect", retries=0, redirect=True
148+
+ )
149+
+ response = http.request(
150+
+ "GET", f"{self.base_url}/redirect", retries=False, redirect=True
151+
+ )
152+
+ assert response.status == 303
153+
+
154+
+ # Check when redirect=False.
155+
+ response = http.request(
156+
+ "GET", f"{self.base_url}/redirect", retries=0, redirect=False
157+
+ )
158+
+ assert response.status == 303
159+
+ response = http.request(
160+
+ "GET", f"{self.base_url}/redirect", retries=False, redirect=False
161+
+ )
162+
+ assert response.status == 303
163+
+
164+
def test_cross_host_redirect(self) -> None:
165+
with PoolManager() as http:
166+
cross_host_location = f"{self.base_url_alt}/echo?a=b"
167+
@@ -135,6 +219,24 @@ class TestPoolManager(HTTPDummyServerTestCase):
168+
pool = http.connection_from_host(self.host, self.port)
169+
assert pool.num_connections == 1
170+
171+
+ # Check when retries are configured for the pool manager.
172+
+ with PoolManager(retries=1) as http:
173+
+ with pytest.raises(MaxRetryError):
174+
+ http.request(
175+
+ "GET",
176+
+ f"{self.base_url}/redirect",
177+
+ fields={"target": f"/redirect?target={self.base_url}/"},
178+
+ )
179+
+
180+
+ # Here we allow more retries for the request.
181+
+ response = http.request(
182+
+ "GET",
183+
+ f"{self.base_url}/redirect",
184+
+ fields={"target": f"/redirect?target={self.base_url}/"},
185+
+ retries=2,
186+
+ )
187+
+ assert response.status == 200
188+
+
189+
def test_redirect_cross_host_remove_headers(self) -> None:
190+
with PoolManager() as http:
191+
r = http.request(
192+
--
193+
2.45.2
194+

SPECS/python-urllib3/python-urllib3.spec

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Summary: A powerful, sanity-friendly HTTP client for Python.
22
Name: python-urllib3
33
Version: 2.0.7
4-
Release: 1%{?dist}
4+
Release: 2%{?dist}
55
License: MIT
66
Vendor: Microsoft Corporation
77
Distribution: Azure Linux
@@ -12,6 +12,7 @@ BuildArch: noarch
1212
Patch0: urllib3_test_recent_date.patch
1313
Patch1: change-backend-to-flit_core.patch
1414
Patch2: CVE-2024-37891.patch
15+
Patch3: CVE-2025-50181.patch
1516

1617
%description
1718
A powerful, sanity-friendly HTTP client for Python.
@@ -83,6 +84,9 @@ skiplist+=" or test_respect_retry_after_header_sleep"
8384
%{python3_sitelib}/*
8485

8586
%changelog
87+
* Tue Jun 24 2025 Durga Jagadeesh Palli <[email protected]> - 2.0.7-2
88+
- add patch for CVE-2025-50181
89+
8690
* Wed Jul 10 2024 Sumedh Sharma <[email protected]> - 2.0.7-1
8791
- Bump version to fix CVE-2023-43804 & CVE-2023-45803.
8892
- Add patch file to fix CVE-2024-37891.

0 commit comments

Comments
 (0)