Skip to content

Commit 406e074

Browse files
azurelinux-securityakhila-gurujujslobodzian
authored
[AutoPR- Security] Patch python-urllib3 for CVE-2025-66418, CVE-2026-21441 [HIGH] (#15472)
Co-authored-by: akhila-guruju <v-guakhila@microsoft.com> Co-authored-by: jslobodzian <joslobo@microsoft.com>
1 parent 5408d20 commit 406e074

4 files changed

Lines changed: 190 additions & 6 deletions

File tree

SPECS/python-urllib3/CVE-2025-50181.patch

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ index 61715e9..ded7b38 100644
7474
+ p = PoolManager(retries=retries)
7575
merged = p._merge_pool_kwargs({"new_key": "value"})
7676
- assert {"strict": True, "new_key": "value"} == merged
77-
+ assert {"strict": retries, "new_key": "value"} == merged
77+
+ assert {"retries": retries, "new_key": "value"} == merged
7878

7979
def test_merge_pool_kwargs_none(self):
8080
"""Assert false-y values to _merge_pool_kwargs result in defaults"""
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
From 4b4af90890a415a347e28cb21f20dbe5db243412 Mon Sep 17 00:00:00 2001
2+
From: Illia Volochii <illia.volochii@gmail.com>
3+
Date: Fri, 5 Dec 2025 16:41:33 +0200
4+
Subject: [PATCH] Merge commit from fork
5+
6+
* Add a hard-coded limit for the decompression chain
7+
8+
* Reuse new list
9+
10+
Upstream Patch Reference: https://github.com/urllib3/urllib3/commit/24d7b67eac89f94e11003424bcf0d8f7b72222a8.patch
11+
12+
Signed-off-by: Azure Linux Security Servicing Account <azurelinux-security@microsoft.com>
13+
Origin: https://github.com/urllib3/urllib3/commit/24d7b67eac89f94e11003424bcf0d8f7b72222a8.patch
14+
Upstream-reference: https://raw.githubusercontent.com/azurelinux-security/azurelinux/22b34ffefbca93d9b67a608c9e9ea2c25ca89555/SPECS/python-urllib3/CVE-2025-66418.patch
15+
---
16+
changelog/GHSA-gm62-xv2j-4w53.security.rst | 4 ++++
17+
src/urllib3/response.py | 13 ++++++++++++-
18+
test/test_response.py | 10 ++++++++++
19+
3 files changed, 26 insertions(+), 1 deletion(-)
20+
create mode 100644 changelog/GHSA-gm62-xv2j-4w53.security.rst
21+
22+
diff --git a/changelog/GHSA-gm62-xv2j-4w53.security.rst b/changelog/GHSA-gm62-xv2j-4w53.security.rst
23+
new file mode 100644
24+
index 0000000..6646eaa
25+
--- /dev/null
26+
+++ b/changelog/GHSA-gm62-xv2j-4w53.security.rst
27+
@@ -0,0 +1,4 @@
28+
+Fixed a security issue where an attacker could compose an HTTP response with
29+
+virtually unlimited links in the ``Content-Encoding`` header, potentially
30+
+leading to a denial of service (DoS) attack by exhausting system resources
31+
+during decoding. The number of allowed chained encodings is now limited to 5.
32+
diff --git a/src/urllib3/response.py b/src/urllib3/response.py
33+
index 0bd13d4..0f8adbd 100644
34+
--- a/src/urllib3/response.py
35+
+++ b/src/urllib3/response.py
36+
@@ -135,8 +135,19 @@ class MultiDecoder(object):
37+
they were applied.
38+
"""
39+
40+
+
41+
+ # Maximum allowed number of chained HTTP encodings in the
42+
+ # Content-Encoding header.
43+
+ max_decode_links = 5
44+
+
45+
def __init__(self, modes):
46+
- self._decoders = [_get_decoder(m.strip()) for m in modes.split(",")]
47+
+ encodings = [m.strip() for m in modes.split(",")]
48+
+ if len(encodings) > self.max_decode_links:
49+
+ raise DecodeError(
50+
+ "Too many content encodings in the chain: "
51+
+ f"{len(encodings)} > {self.max_decode_links}"
52+
+ )
53+
+ self._decoders = [_get_decoder(e) for e in encodings]
54+
55+
def flush(self):
56+
return self._decoders[0].flush()
57+
diff --git a/test/test_response.py b/test/test_response.py
58+
index e09e385..4bfa8af 100644
59+
--- a/test/test_response.py
60+
+++ b/test/test_response.py
61+
@@ -295,6 +295,16 @@ class TestResponse(object):
62+
63+
assert r.data == b"foo"
64+
65+
+ def test_read_multi_decoding_too_many_links(self) -> None:
66+
+ fp = BytesIO(b"foo")
67+
+ with pytest.raises(
68+
+ DecodeError, match="Too many content encodings in the chain: 6 > 5"
69+
+ ):
70+
+ HTTPResponse(
71+
+ fp,
72+
+ headers={"content-encoding": "gzip, deflate, br, zstd, gzip, deflate"},
73+
+ )
74+
+
75+
def test_body_blob(self):
76+
resp = HTTPResponse(b"foo")
77+
assert resp.data == b"foo"
78+
--
79+
2.45.4
80+
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
From 8864ac407bba8607950025e0979c4c69bc7abc7b Mon Sep 17 00:00:00 2001
2+
From: Illia Volochii <illia.volochii@gmail.com>
3+
Date: Wed, 7 Jan 2026 18:07:30 +0200
4+
Subject: [PATCH] Merge commit from fork
5+
6+
* Stop decoding response content during redirects needlessly
7+
8+
* Rename the new query parameter
9+
10+
* Add a changelog entry
11+
12+
Upstream Patch reference: https://github.com/urllib3/urllib3/commit/8864ac407bba8607950025e0979c4c69bc7abc7b.patch
13+
---
14+
dummyserver/handlers.py | 9 ++++++++-
15+
src/urllib3/response.py | 4 +++-
16+
test/with_dummyserver/test_connectionpool.py | 19 +++++++++++++++++++
17+
3 files changed, 30 insertions(+), 2 deletions(-)
18+
19+
diff --git a/dummyserver/handlers.py b/dummyserver/handlers.py
20+
index acd181d..db38a39 100644
21+
--- a/dummyserver/handlers.py
22+
+++ b/dummyserver/handlers.py
23+
@@ -190,7 +190,14 @@ class TestingApp(RequestHandler):
24+
status = status.decode("latin-1")
25+
26+
headers = [("Location", target)]
27+
- return Response(status=status, headers=headers)
28+
+ compressed = request.params.get("compressed") == b"true"
29+
+ if compressed:
30+
+ headers.append(("Content-Encoding", "gzip"))
31+
+ data = gzip.compress(b"foo")
32+
+ else:
33+
+ data = b""
34+
+
35+
+ return Response(data, status=status, headers=headers)
36+
37+
def not_found(self, request):
38+
return Response("Not found", status="404 Not Found")
39+
diff --git a/src/urllib3/response.py b/src/urllib3/response.py
40+
index 0f8adbd..428b8ec 100644
41+
--- a/src/urllib3/response.py
42+
+++ b/src/urllib3/response.py
43+
@@ -303,7 +303,9 @@ class HTTPResponse(io.IOBase):
44+
Unread data in the HTTPResponse connection blocks the connection from being released back to the pool.
45+
"""
46+
try:
47+
- self.read()
48+
+ # Do not spend resources decoding the content unless decoding has already been initiated.
49+
+ # In this backport we don't track that state, so we avoid decoding here.
50+
+ self.read(decode_content=False)
51+
except (HTTPError, SocketError, BaseSSLError, HTTPException):
52+
pass
53+
54+
diff --git a/test/with_dummyserver/test_connectionpool.py b/test/with_dummyserver/test_connectionpool.py
55+
index cde027b..c80a16d 100644
56+
--- a/test/with_dummyserver/test_connectionpool.py
57+
+++ b/test/with_dummyserver/test_connectionpool.py
58+
@@ -464,6 +464,25 @@ class TestConnectionPool(HTTPDummyServerTestCase):
59+
assert r.status == 200
60+
assert r.data == b"Dummy server!"
61+
62+
+ @mock.patch("urllib3.response.GzipDecoder.decompress")
63+
+ def test_no_decoding_with_redirect_when_preload_disabled(
64+
+ self, gzip_decompress: mock.MagicMock
65+
+ ) -> None:
66+
+ """
67+
+ Test that urllib3 does not attempt to decode a gzipped redirect
68+
+ response when `preload_content` is set to `False`.
69+
+ """
70+
+ with HTTPConnectionPool(self.host, self.port) as pool:
71+
+ # Three requests are expected: two redirects and one final / 200 OK.
72+
+ response = pool.request(
73+
+ "GET",
74+
+ "/redirect",
75+
+ fields={"target": "/redirect?compressed=true", "compressed": "true"},
76+
+ preload_content=False,
77+
+ )
78+
+ assert response.status == 200
79+
+ gzip_decompress.assert_not_called()
80+
+
81+
def test_303_redirect_makes_request_lose_body(self):
82+
with HTTPConnectionPool(self.host, self.port) as pool:
83+
response = pool.request(
84+
--
85+
2.43.0
86+

SPECS/python-urllib3/python-urllib3.spec

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Summary: A powerful, sanity-friendly HTTP client for Python.
22
Name: python-urllib3
33
Version: 1.26.19
4-
Release: 2%{?dist}
4+
Release: 3%{?dist}
55
License: MIT
66
Vendor: Microsoft Corporation
77
Distribution: Mariner
@@ -22,10 +22,14 @@ BuildRequires: python3-setuptools
2222
BuildRequires: python3-xml
2323
%if %{with_check}
2424
BuildRequires: python3-pip
25+
BuildRequires: python3-pytest
26+
BuildRequires: python3-mock
2527
%endif
2628
Requires: python3
2729

28-
Patch0: CVE-2025-50181.patch
30+
Patch0: CVE-2025-50181.patch
31+
Patch1: CVE-2025-66418.patch
32+
Patch2: CVE-2026-21441.patch
2933

3034
%description -n python3-urllib3
3135
urllib3 is a powerful, sanity-friendly HTTP client for Python. Much of the Python ecosystem already uses urllib3 and you should too.
@@ -43,16 +47,30 @@ rm -rf test/contrib/
4347
%py3_install
4448

4549
%check
46-
pip3 install --user --upgrade nox
47-
PATH="$PATH:/root/.local/bin/"
48-
nox --reuse-existing-virtualenvs --sessions test-%{python3_version}
50+
# Install nox to handle test environment setup
51+
pip3 install nox
52+
53+
# Patch dev-requirements.txt to use compatible versions with setuptools 69.x:
54+
# - pytest 7.x+ is compatible with newer setuptools
55+
# - flaky 3.8.0+ is compatible with pytest 7.x+
56+
sed -i 's/pytest==4.6.9.*/pytest>=7.0.0/' dev-requirements.txt
57+
sed -i 's/pytest==6.2.4.*/pytest>=7.0.0/' dev-requirements.txt
58+
sed -i 's/flaky==3.7.0/flaky>=3.8.0/' dev-requirements.txt
59+
60+
# Run the test session for Python 3.9
61+
# Skip test_recent_date which uses hardcoded date that is now in the past
62+
# Note: test/with_dummyserver and test/contrib are removed in %prep
63+
nox -s test-3.9 -- -k "not test_recent_date"
4964

5065
%files -n python3-urllib3
5166
%defattr(-,root,root,-)
5267
%license LICENSE.txt
5368
%{python3_sitelib}/*
5469

5570
%changelog
71+
* Fri Jan 09 2026 Azure Linux Security Servicing Account <azurelinux-security@microsoft.com> - 1.26.19-3
72+
- Patch for CVE-2025-66418, CVE-2026-21441
73+
5674
* Thu Jun 26 2025 Durga Jagadeesh Palli <v-dpalli@microsoft.com> - 1.26.19-2
5775
- Patch CVE-2025-50181
5876

0 commit comments

Comments
 (0)