summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--meta/recipes-devtools/python/python3-urllib3/CVE-2025-50181.patch283
-rw-r--r--meta/recipes-devtools/python/python3-urllib3_2.2.2.bb4
2 files changed, 287 insertions, 0 deletions
diff --git a/meta/recipes-devtools/python/python3-urllib3/CVE-2025-50181.patch b/meta/recipes-devtools/python/python3-urllib3/CVE-2025-50181.patch
new file mode 100644
index 0000000000..d4f6e98cc1
--- /dev/null
+++ b/meta/recipes-devtools/python/python3-urllib3/CVE-2025-50181.patch
@@ -0,0 +1,283 @@
1From f05b1329126d5be6de501f9d1e3e36738bc08857 Mon Sep 17 00:00:00 2001
2From: Illia Volochii <illia.volochii@gmail.com>
3Date: Wed, 18 Jun 2025 16:25:01 +0300
4Subject: [PATCH] Merge commit from fork
5
6* Apply Quentin's suggestion
7
8Co-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
36Co-authored-by: Seth Michael Larson <sethmichaellarson@gmail.com>
37
38* Use a minor release instead of the patch one
39
40---------
41
42Co-authored-by: Quentin Pradet <quentin.pradet@gmail.com>
43Co-authored-by: Seth Michael Larson <sethmichaellarson@gmail.com>
44
45CVE: CVE-2025-50181
46Upstream-Status: Backport [https://github.com/urllib3/urllib3/commit/f05b1329126d5be6de501f9d1e3e36738bc08857]
47
48Signed-off-by: Yogita Urade <yogita.urade@windriver.com>
49---
50 docs/reference/contrib/emscripten.rst | 2 +-
51 dummyserver/app.py | 1 +
52 src/urllib3/poolmanager.py | 18 +++-
53 test/contrib/emscripten/test_emscripten.py | 16 ++++
54 test/test_poolmanager.py | 5 +-
55 test/with_dummyserver/test_poolmanager.py | 101 +++++++++++++++++++++
56 6 files changed, 139 insertions(+), 4 deletions(-)
57
58diff --git a/docs/reference/contrib/emscripten.rst b/docs/reference/contrib/emscripten.rst
59index 9e85629..c88e422 100644
60--- a/docs/reference/contrib/emscripten.rst
61+++ b/docs/reference/contrib/emscripten.rst
62@@ -68,7 +68,7 @@ Features which are usable with Emscripten support are:
63 * Timeouts
64 * Retries
65 * Streaming (with Web Workers and Cross-Origin Isolation)
66-* Redirects
67+* Redirects (determined by browser/runtime, not restrictable with urllib3)
68 * Decompressing response bodies
69
70 Features which don't work with Emscripten:
71diff --git a/dummyserver/app.py b/dummyserver/app.py
72index 9fc9d1b..96e0dab 100644
73--- a/dummyserver/app.py
74+++ b/dummyserver/app.py
75@@ -228,6 +228,7 @@ async def encodingrequest() -> ResponseReturnValue:
76
77
78 @hypercorn_app.route("/redirect", methods=["GET", "POST", "PUT"])
79+@pyodide_testing_app.route("/redirect", methods=["GET", "POST", "PUT"])
80 async def redirect() -> ResponseReturnValue:
81 "Perform a redirect to ``target``"
82 values = await request.values
83diff --git a/src/urllib3/poolmanager.py b/src/urllib3/poolmanager.py
84index 085d1db..5763fea 100644
85--- a/src/urllib3/poolmanager.py
86+++ b/src/urllib3/poolmanager.py
87@@ -203,6 +203,22 @@ class PoolManager(RequestMethods):
88 **connection_pool_kw: typing.Any,
89 ) -> None:
90 super().__init__(headers)
91+ if "retries" in connection_pool_kw:
92+ retries = connection_pool_kw["retries"]
93+ if not isinstance(retries, Retry):
94+ # When Retry is initialized, raise_on_redirect is based
95+ # on a redirect boolean value.
96+ # But requests made via a pool manager always set
97+ # redirect to False, and raise_on_redirect always ends
98+ # up being False consequently.
99+ # Here we fix the issue by setting raise_on_redirect to
100+ # a value needed by the pool manager without considering
101+ # the redirect boolean.
102+ raise_on_redirect = retries is not False
103+ retries = Retry.from_int(retries, redirect=False)
104+ retries.raise_on_redirect = raise_on_redirect
105+ connection_pool_kw = connection_pool_kw.copy()
106+ connection_pool_kw["retries"] = retries
107 self.connection_pool_kw = connection_pool_kw
108
109 self.pools: RecentlyUsedContainer[PoolKey, HTTPConnectionPool]
110@@ -456,7 +472,7 @@ class PoolManager(RequestMethods):
111 kw["body"] = None
112 kw["headers"] = HTTPHeaderDict(kw["headers"])._prepare_for_method_change()
113
114- retries = kw.get("retries")
115+ retries = kw.get("retries", response.retries)
116 if not isinstance(retries, Retry):
117 retries = Retry.from_int(retries, redirect=redirect)
118
119diff --git a/test/contrib/emscripten/test_emscripten.py b/test/contrib/emscripten/test_emscripten.py
120index 17264d8..0e107fa 100644
121--- a/test/contrib/emscripten/test_emscripten.py
122+++ b/test/contrib/emscripten/test_emscripten.py
123@@ -949,6 +949,22 @@ def test_retries(
124 pyodide_test(selenium_coverage, testserver_http.http_host, find_unused_port())
125
126
127+def test_redirects(
128+ selenium_coverage: typing.Any, testserver_http: PyodideServerInfo
129+) -> None:
130+ @run_in_pyodide # type: ignore[misc]
131+ def pyodide_test(selenium_coverage: typing.Any, host: str, port: int) -> None:
132+ from urllib3 import request
133+
134+ redirect_url = f"http://{host}:{port}/redirect"
135+ response = request("GET", redirect_url)
136+ assert response.status == 200
137+
138+ pyodide_test(
139+ selenium_coverage, testserver_http.http_host, testserver_http.http_port
140+ )
141+
142+
143 @install_urllib3_wheel()
144 def test_insecure_requests_warning(
145 selenium_coverage: typing.Any, testserver_http: PyodideServerInfo
146diff --git a/test/test_poolmanager.py b/test/test_poolmanager.py
147index ab5f203..b481a19 100644
148--- a/test/test_poolmanager.py
149+++ b/test/test_poolmanager.py
150@@ -379,9 +379,10 @@ class TestPoolManager:
151
152 def test_merge_pool_kwargs(self) -> None:
153 """Assert _merge_pool_kwargs works in the happy case"""
154- p = PoolManager(retries=100)
155+ retries = retry.Retry(total=100)
156+ p = PoolManager(retries=retries)
157 merged = p._merge_pool_kwargs({"new_key": "value"})
158- assert {"retries": 100, "new_key": "value"} == merged
159+ assert {"retries": retries, "new_key": "value"} == merged
160
161 def test_merge_pool_kwargs_none(self) -> None:
162 """Assert false-y values to _merge_pool_kwargs result in defaults"""
163diff --git a/test/with_dummyserver/test_poolmanager.py b/test/with_dummyserver/test_poolmanager.py
164index af77241..7f163ab 100644
165--- a/test/with_dummyserver/test_poolmanager.py
166+++ b/test/with_dummyserver/test_poolmanager.py
167@@ -84,6 +84,89 @@ class TestPoolManager(HypercornDummyServerTestCase):
168 assert r.status == 200
169 assert r.data == b"Dummy server!"
170
171+ @pytest.mark.parametrize(
172+ "retries",
173+ (0, Retry(total=0), Retry(redirect=0), Retry(total=0, redirect=0)),
174+ )
175+ def test_redirects_disabled_for_pool_manager_with_0(
176+ self, retries: typing.Literal[0] | Retry
177+ ) -> None:
178+ """
179+ Check handling redirects when retries is set to 0 on the pool
180+ manager.
181+ """
182+ with PoolManager(retries=retries) as http:
183+ with pytest.raises(MaxRetryError):
184+ http.request("GET", f"{self.base_url}/redirect")
185+
186+ # Setting redirect=True should not change the behavior.
187+ with pytest.raises(MaxRetryError):
188+ http.request("GET", f"{self.base_url}/redirect", redirect=True)
189+
190+ # Setting redirect=False should not make it follow the redirect,
191+ # but MaxRetryError should not be raised.
192+ response = http.request("GET", f"{self.base_url}/redirect", redirect=False)
193+ assert response.status == 303
194+
195+ @pytest.mark.parametrize(
196+ "retries",
197+ (
198+ False,
199+ Retry(total=False),
200+ Retry(redirect=False),
201+ Retry(total=False, redirect=False),
202+ ),
203+ )
204+ def test_redirects_disabled_for_pool_manager_with_false(
205+ self, retries: typing.Literal[False] | Retry
206+ ) -> None:
207+ """
208+ Check that setting retries set to False on the pool manager disables
209+ raising MaxRetryError and redirect=True does not change the
210+ behavior.
211+ """
212+ with PoolManager(retries=retries) as http:
213+ response = http.request("GET", f"{self.base_url}/redirect")
214+ assert response.status == 303
215+
216+ response = http.request("GET", f"{self.base_url}/redirect", redirect=True)
217+ assert response.status == 303
218+
219+ response = http.request("GET", f"{self.base_url}/redirect", redirect=False)
220+ assert response.status == 303
221+
222+ def test_redirects_disabled_for_individual_request(self) -> None:
223+ """
224+ Check handling redirects when they are meant to be disabled
225+ on the request level.
226+ """
227+ with PoolManager() as http:
228+ # Check when redirect is not passed.
229+ with pytest.raises(MaxRetryError):
230+ http.request("GET", f"{self.base_url}/redirect", retries=0)
231+ response = http.request("GET", f"{self.base_url}/redirect", retries=False)
232+ assert response.status == 303
233+
234+ # Check when redirect=True.
235+ with pytest.raises(MaxRetryError):
236+ http.request(
237+ "GET", f"{self.base_url}/redirect", retries=0, redirect=True
238+ )
239+ response = http.request(
240+ "GET", f"{self.base_url}/redirect", retries=False, redirect=True
241+ )
242+ assert response.status == 303
243+
244+ # Check when redirect=False.
245+ response = http.request(
246+ "GET", f"{self.base_url}/redirect", retries=0, redirect=False
247+ )
248+ assert response.status == 303
249+ response = http.request(
250+ "GET", f"{self.base_url}/redirect", retries=False, redirect=False
251+ )
252+ assert response.status == 303
253+
254 def test_cross_host_redirect(self) -> None:
255 with PoolManager() as http:
256 cross_host_location = f"{self.base_url_alt}/echo?a=b"
257@@ -138,6 +221,24 @@ class TestPoolManager(HypercornDummyServerTestCase):
258 pool = http.connection_from_host(self.host, self.port)
259 assert pool.num_connections == 1
260
261+ # Check when retries are configured for the pool manager.
262+ with PoolManager(retries=1) as http:
263+ with pytest.raises(MaxRetryError):
264+ http.request(
265+ "GET",
266+ f"{self.base_url}/redirect",
267+ fields={"target": f"/redirect?target={self.base_url}/"},
268+ )
269+
270+ # Here we allow more retries for the request.
271+ response = http.request(
272+ "GET",
273+ f"{self.base_url}/redirect",
274+ fields={"target": f"/redirect?target={self.base_url}/"},
275+ retries=2,
276+ )
277+ assert response.status == 200
278+
279 def test_redirect_cross_host_remove_headers(self) -> None:
280 with PoolManager() as http:
281 r = http.request(
282--
2832.40.0
diff --git a/meta/recipes-devtools/python/python3-urllib3_2.2.2.bb b/meta/recipes-devtools/python/python3-urllib3_2.2.2.bb
index 31a03a60b3..bdb1c7ca8d 100644
--- a/meta/recipes-devtools/python/python3-urllib3_2.2.2.bb
+++ b/meta/recipes-devtools/python/python3-urllib3_2.2.2.bb
@@ -7,6 +7,10 @@ SRC_URI[sha256sum] = "dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f
7 7
8inherit pypi python_hatchling 8inherit pypi python_hatchling
9 9
10SRC_URI += " \
11 file://CVE-2025-50181.patch \
12"
13
10RDEPENDS:${PN} += "\ 14RDEPENDS:${PN} += "\
11 python3-certifi \ 15 python3-certifi \
12 python3-cryptography \ 16 python3-cryptography \