Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions SPECS/python-urllib3/CVE-2025-66418.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
From 4b4af90890a415a347e28cb21f20dbe5db243412 Mon Sep 17 00:00:00 2001
From: Illia Volochii <[email protected]>
Date: Fri, 5 Dec 2025 16:41:33 +0200
Subject: [PATCH] Merge commit from fork

* Add a hard-coded limit for the decompression chain

* Reuse new list

Upstream Patch Reference: https://github.com/urllib3/urllib3/commit/24d7b67eac89f94e11003424bcf0d8f7b72222a8.patch

Signed-off-by: Azure Linux Security Servicing Account <[email protected]>
Origin: https://github.com/urllib3/urllib3/commit/24d7b67eac89f94e11003424bcf0d8f7b72222a8.patch
Upstream-reference: https://github.com/___raw___/azurelinux-security/azurelinux/22b34ffefbca93d9b67a608c9e9ea2c25ca89555/SPECS/python-urllib3/CVE-2025-66418.patch
---
changelog/GHSA-gm62-xv2j-4w53.security.rst | 4 ++++
src/urllib3/response.py | 13 ++++++++++++-
test/test_response.py | 10 ++++++++++
3 files changed, 26 insertions(+), 1 deletion(-)
create mode 100644 changelog/GHSA-gm62-xv2j-4w53.security.rst

diff --git a/changelog/GHSA-gm62-xv2j-4w53.security.rst b/changelog/GHSA-gm62-xv2j-4w53.security.rst
new file mode 100644
index 0000000..6646eaa
--- /dev/null
+++ b/changelog/GHSA-gm62-xv2j-4w53.security.rst
@@ -0,0 +1,4 @@
+Fixed a security issue where an attacker could compose an HTTP response with
+virtually unlimited links in the ``Content-Encoding`` header, potentially
+leading to a denial of service (DoS) attack by exhausting system resources
+during decoding. The number of allowed chained encodings is now limited to 5.
diff --git a/src/urllib3/response.py b/src/urllib3/response.py
index 0bd13d4..0f8adbd 100644
--- a/src/urllib3/response.py
+++ b/src/urllib3/response.py
@@ -135,8 +135,19 @@ class MultiDecoder(object):
they were applied.
"""

+
+ # Maximum allowed number of chained HTTP encodings in the
+ # Content-Encoding header.
+ max_decode_links = 5
+
def __init__(self, modes):
- self._decoders = [_get_decoder(m.strip()) for m in modes.split(",")]
+ encodings = [m.strip() for m in modes.split(",")]
+ if len(encodings) > self.max_decode_links:
+ raise DecodeError(
+ "Too many content encodings in the chain: "
+ f"{len(encodings)} > {self.max_decode_links}"
+ )
+ self._decoders = [_get_decoder(e) for e in encodings]

def flush(self):
return self._decoders[0].flush()
diff --git a/test/test_response.py b/test/test_response.py
index e09e385..4bfa8af 100644
--- a/test/test_response.py
+++ b/test/test_response.py
@@ -295,6 +295,16 @@ class TestResponse(object):

assert r.data == b"foo"

+ def test_read_multi_decoding_too_many_links(self) -> None:
+ fp = BytesIO(b"foo")
+ with pytest.raises(
+ DecodeError, match="Too many content encodings in the chain: 6 > 5"
+ ):
+ HTTPResponse(
+ fp,
+ headers={"content-encoding": "gzip, deflate, br, zstd, gzip, deflate"},
+ )
+
def test_body_blob(self):
resp = HTTPResponse(b"foo")
assert resp.data == b"foo"
--
2.45.4

86 changes: 86 additions & 0 deletions SPECS/python-urllib3/CVE-2026-21441.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
From 8864ac407bba8607950025e0979c4c69bc7abc7b Mon Sep 17 00:00:00 2001
From: Illia Volochii <[email protected]>
Date: Wed, 7 Jan 2026 18:07:30 +0200
Subject: [PATCH] Merge commit from fork

* Stop decoding response content during redirects needlessly

* Rename the new query parameter

* Add a changelog entry

Upstream Patch reference: https://github.com/urllib3/urllib3/commit/8864ac407bba8607950025e0979c4c69bc7abc7b.patch
---
dummyserver/handlers.py | 9 ++++++++-
src/urllib3/response.py | 4 +++-
test/with_dummyserver/test_connectionpool.py | 19 +++++++++++++++++++
3 files changed, 30 insertions(+), 2 deletions(-)

diff --git a/dummyserver/handlers.py b/dummyserver/handlers.py
index acd181d..db38a39 100644
--- a/dummyserver/handlers.py
+++ b/dummyserver/handlers.py
@@ -190,7 +190,14 @@ class TestingApp(RequestHandler):
status = status.decode("latin-1")

headers = [("Location", target)]
- return Response(status=status, headers=headers)
+ compressed = request.params.get("compressed") == b"true"
+ if compressed:
+ headers.append(("Content-Encoding", "gzip"))
+ data = gzip.compress(b"foo")
+ else:
+ data = b""
+
+ return Response(data, status=status, headers=headers)

def not_found(self, request):
return Response("Not found", status="404 Not Found")
diff --git a/src/urllib3/response.py b/src/urllib3/response.py
index 0f8adbd..428b8ec 100644
--- a/src/urllib3/response.py
+++ b/src/urllib3/response.py
@@ -303,7 +303,9 @@ class HTTPResponse(io.IOBase):
Unread data in the HTTPResponse connection blocks the connection from being released back to the pool.
"""
try:
- self.read()
+ # Do not spend resources decoding the content unless decoding has already been initiated.
+ # In this backport we don't track that state, so we avoid decoding here.
+ self.read(decode_content=False)
except (HTTPError, SocketError, BaseSSLError, HTTPException):
pass

diff --git a/test/with_dummyserver/test_connectionpool.py b/test/with_dummyserver/test_connectionpool.py
index cde027b..c80a16d 100644
--- a/test/with_dummyserver/test_connectionpool.py
+++ b/test/with_dummyserver/test_connectionpool.py
@@ -464,6 +464,25 @@ class TestConnectionPool(HTTPDummyServerTestCase):
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):
with HTTPConnectionPool(self.host, self.port) as pool:
response = pool.request(
--
2.43.0

7 changes: 6 additions & 1 deletion SPECS/python-urllib3/python-urllib3.spec
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Summary: A powerful, sanity-friendly HTTP client for Python.
Name: python-urllib3
Version: 1.26.19
Release: 2%{?dist}
Release: 3%{?dist}
License: MIT
Vendor: Microsoft Corporation
Distribution: Mariner
Expand All @@ -26,6 +26,8 @@ BuildRequires: python3-pip
Requires: python3

Patch0: CVE-2025-50181.patch
Patch1: CVE-2025-66418.patch
Patch2: CVE-2026-21441.patch

%description -n python3-urllib3
urllib3 is a powerful, sanity-friendly HTTP client for Python. Much of the Python ecosystem already uses urllib3 and you should too.
Expand Down Expand Up @@ -53,6 +55,9 @@ nox --reuse-existing-virtualenvs --sessions test-%{python3_version}
%{python3_sitelib}/*

%changelog
* Fri Jan 09 2026 Azure Linux Security Servicing Account <[email protected]> - 1.26.19-3
- Patch for CVE-2025-66418, CVE-2026-21441

* Thu Jun 26 2025 Durga Jagadeesh Palli <[email protected]> - 1.26.19-2
- Patch CVE-2025-50181

Expand Down
Loading