Control: tags 1125062 + patch
Control: tags 1125062 + pending

Dear maintainer,

I've prepared an NMU for python-urllib3 (versioned as 2.5.0-1.2) and
uploaded it to DELAYED/5. Please feel free to tell me if I
should cancel it.

Regards,
Salvatore
diffstat for python-urllib3-2.5.0 python-urllib3-2.5.0

 changelog                    |    8 +++
 patches/CVE-2026-21441.patch |   91 +++++++++++++++++++++++++++++++++++++++++++
 patches/series               |    1 
 3 files changed, 100 insertions(+)

diff -Nru python-urllib3-2.5.0/debian/changelog python-urllib3-2.5.0/debian/changelog
--- python-urllib3-2.5.0/debian/changelog	2026-01-03 20:00:44.000000000 +0100
+++ python-urllib3-2.5.0/debian/changelog	2026-01-10 15:16:31.000000000 +0100
@@ -1,3 +1,11 @@
+python-urllib3 (2.5.0-1.2) unstable; urgency=medium
+
+  * Non-maintainer upload.
+  * Decompression-bomb safeguards bypassed when following HTTP redirects
+    (streaming API) (CVE-2026-21441) (Closes: #1125062)
+
+ -- Salvatore Bonaccorso <[email protected]>  Sat, 10 Jan 2026 15:16:31 +0100
+
 python-urllib3 (2.5.0-1.1) unstable; urgency=medium
 
   * Non-maintainer upload.
diff -Nru python-urllib3-2.5.0/debian/patches/CVE-2026-21441.patch python-urllib3-2.5.0/debian/patches/CVE-2026-21441.patch
--- python-urllib3-2.5.0/debian/patches/CVE-2026-21441.patch	1970-01-01 01:00:00.000000000 +0100
+++ python-urllib3-2.5.0/debian/patches/CVE-2026-21441.patch	2026-01-10 15:16:02.000000000 +0100
@@ -0,0 +1,91 @@
+From: Illia Volochii <[email protected]>
+Date: Wed, 7 Jan 2026 18:07:30 +0200
+Subject: Merge commit from fork
+Origin: https://github.com/urllib3/urllib3/commit/8864ac407bba8607950025e0979c4c69bc7abc7b
+Bug-Debian: https://bugs.debian.org/1125062
+Bug-Debian-Security: https://security-tracker.debian.org/tracker/CVE-2026-21441
+
+* Stop decoding response content during redirects needlessly
+
+* Rename the new query parameter
+
+* Add a changelog entry
+---
+ CHANGES.rst                                  | 13 +++++++++++++
+ dummyserver/app.py                           |  8 +++++++-
+ src/urllib3/response.py                      |  6 +++++-
+ test/with_dummyserver/test_connectionpool.py | 19 +++++++++++++++++++
+ 4 files changed, 44 insertions(+), 2 deletions(-)
+
+diff --git a/dummyserver/app.py b/dummyserver/app.py
+index 0eeb93f7396e..5b82e9329d88 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 f6266f1a938f..ff6d1f4911c2 100644
+--- a/src/urllib3/response.py
++++ b/src/urllib3/response.py
+@@ -797,7 +797,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 ce165e24a1ce..8d6107aea039 100644
+--- a/test/with_dummyserver/test_connectionpool.py
++++ b/test/with_dummyserver/test_connectionpool.py
+@@ -508,6 +508,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.51.0
+
diff -Nru python-urllib3-2.5.0/debian/patches/series python-urllib3-2.5.0/debian/patches/series
--- python-urllib3-2.5.0/debian/patches/series	2026-01-03 19:56:35.000000000 +0100
+++ python-urllib3-2.5.0/debian/patches/series	2026-01-10 15:14:58.000000000 +0100
@@ -1,2 +1,3 @@
 test_http2_probe_blocked_per_thread-requires_network.patch
 CVE-2025-66418.patch
+CVE-2026-21441.patch

Reply via email to