diff options
| -rw-r--r-- | meta/recipes-devtools/python/python3-urllib3/CVE-2025-50181.patch | 283 | ||||
| -rw-r--r-- | meta/recipes-devtools/python/python3-urllib3_2.2.2.bb | 4 |
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 @@ | |||
| 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 | |||
| 45 | CVE: CVE-2025-50181 | ||
| 46 | Upstream-Status: Backport [https://github.com/urllib3/urllib3/commit/f05b1329126d5be6de501f9d1e3e36738bc08857] | ||
| 47 | |||
| 48 | Signed-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 | |||
| 58 | diff --git a/docs/reference/contrib/emscripten.rst b/docs/reference/contrib/emscripten.rst | ||
| 59 | index 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: | ||
| 71 | diff --git a/dummyserver/app.py b/dummyserver/app.py | ||
| 72 | index 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 | ||
| 83 | diff --git a/src/urllib3/poolmanager.py b/src/urllib3/poolmanager.py | ||
| 84 | index 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 | |||
| 119 | diff --git a/test/contrib/emscripten/test_emscripten.py b/test/contrib/emscripten/test_emscripten.py | ||
| 120 | index 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 | ||
| 146 | diff --git a/test/test_poolmanager.py b/test/test_poolmanager.py | ||
| 147 | index 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""" | ||
| 163 | diff --git a/test/with_dummyserver/test_poolmanager.py b/test/with_dummyserver/test_poolmanager.py | ||
| 164 | index 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 | -- | ||
| 283 | 2.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 | ||
| 8 | inherit pypi python_hatchling | 8 | inherit pypi python_hatchling |
| 9 | 9 | ||
| 10 | SRC_URI += " \ | ||
| 11 | file://CVE-2025-50181.patch \ | ||
| 12 | " | ||
| 13 | |||
| 10 | RDEPENDS:${PN} += "\ | 14 | RDEPENDS:${PN} += "\ |
| 11 | python3-certifi \ | 15 | python3-certifi \ |
| 12 | python3-cryptography \ | 16 | python3-cryptography \ |
