summaryrefslogtreecommitdiffstats
path: root/meta/recipes-devtools/python/python3-urllib3/CVE-2026-21441.patch
blob: 16af67af312f84c228c50222f80d023379c32619 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
From 686d2bdd4affd3c86e605f54a72afe53c920f72f Mon Sep 17 00:00:00 2001
From: Illia Volochii <illia.volochii@gmail.com>
Date: Wed, 7 Jan 2026 18:07:30 +0200
Subject: [PATCH] Backport fix CVE-2026-21441 python urllib3

Original commit: 8864ac407bba8607950025e0979c4c69bc7abc7b
Original-author: Illia Volochii <illia.volochii@gmail.com>

Bugfixes
--------

- Fixed a high-severity security issue where decompression-bomb safeguards of
  the streaming API were bypassed when HTTP redirects were followed.
  (`GHSA-38jv-5279-wg99 <https://github.com/urllib3/urllib3/security/advisories/GHSA-38jv-5279-wg99>`__)

* Stop decoding response content during redirects needlessly

* Rename the new query parameter

* Add a changelog entry

Fixes CVE-2026-21441
CVE: CVE-2026-21441

Upstream-Status: Backport [https://github.com/urllib3/urllib3/commit/8864ac407bba8607950025e0979c4c69bc7abc7b]

Signed-off-by: Adarsh Jagadish Kamini <adarsh.jagadish.kamini@est.tech>
---
 dummyserver/app.py                           |  8 +++++++-
 src/urllib3/response.py                      |  6 +++++-
 test/with_dummyserver/test_connectionpool.py | 19 +++++++++++++++++++
 3 files changed, 31 insertions(+), 2 deletions(-)

diff --git a/dummyserver/app.py b/dummyserver/app.py
index 9fc9d1b7..c4978152 100644
--- a/dummyserver/app.py
+++ b/dummyserver/app.py
@@ -233,10 +233,16 @@ async def redirect() -> ResponseReturnValue:
     values = await request.values
     target = values.get("target", "/")
     status = values.get("status", "303 See Other")
+    compressed = values.get("compressed") == "true"
     status_code = status.split(" ")[0]
 
     headers = [("Location", target)]
-    return await make_response("", status_code, headers)
+    if compressed:
+        headers.append(("Content-Encoding", "gzip"))
+        data = gzip.compress(b"foo")
+    else:
+        data = b""
+    return await make_response(data, status_code, headers)
 
 
 @hypercorn_app.route("/redirect_after")
diff --git a/src/urllib3/response.py b/src/urllib3/response.py
index a0273d65..909da62b 100644
--- a/src/urllib3/response.py
+++ b/src/urllib3/response.py
@@ -646,7 +646,11 @@ class HTTPResponse(BaseHTTPResponse):
         Unread data in the HTTPResponse connection blocks the connection from being released back to the pool.
         """
         try:
-            self.read()
+            self.read(
+                # Do not spend resources decoding the content unless
+                # decoding has already been initiated.
+                decode_content=self._has_decoded_content,
+            )
         except (HTTPError, OSError, BaseSSLError, HTTPException):
             pass
 
diff --git a/test/with_dummyserver/test_connectionpool.py b/test/with_dummyserver/test_connectionpool.py
index 4fbe6a4f..ebcdf9bf 100644
--- a/test/with_dummyserver/test_connectionpool.py
+++ b/test/with_dummyserver/test_connectionpool.py
@@ -480,6 +480,25 @@ class TestConnectionPool(HypercornDummyServerTestCase):
             assert r.status == 200
             assert r.data == b"Dummy server!"
 
+    @mock.patch("urllib3.response.GzipDecoder.decompress")
+    def test_no_decoding_with_redirect_when_preload_disabled(
+        self, gzip_decompress: mock.MagicMock
+    ) -> None:
+        """
+        Test that urllib3 does not attempt to decode a gzipped redirect
+        response when `preload_content` is set to `False`.
+        """
+        with HTTPConnectionPool(self.host, self.port) as pool:
+            # Three requests are expected: two redirects and one final / 200 OK.
+            response = pool.request(
+                "GET",
+                "/redirect",
+                fields={"target": "/redirect?compressed=true", "compressed": "true"},
+                preload_content=False,
+            )
+        assert response.status == 200
+        gzip_decompress.assert_not_called()
+
     def test_303_redirect_makes_request_lose_body(self) -> None:
         with HTTPConnectionPool(self.host, self.port) as pool:
             response = pool.request(
-- 
2.44.0