diff --git a/CVE-2026-44431.patch b/CVE-2026-44431.patch new file mode 100644 index 0000000..1361818 --- /dev/null +++ b/CVE-2026-44431.patch @@ -0,0 +1,35 @@ +From 158c67280914613c4280ada8918bb5cbdbff4eca Mon Sep 17 00:00:00 2001 +From: Lumir Balhar +Date: Wed, 3 Jun 2026 11:47:42 +0200 +Subject: [PATCH] CVE-2026-44431: Remove sensitive headers in proxy pools too + +--- + src/urllib3/connectionpool.py | 12 ++++++++++++ + 1 file changed, 12 insertions(+) + +diff --git a/src/urllib3/connectionpool.py b/src/urllib3/connectionpool.py +index 402bf67..c28a83c 100644 +--- a/src/urllib3/connectionpool.py ++++ b/src/urllib3/connectionpool.py +@@ -852,6 +852,18 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): + body = None + headers = HTTPHeaderDict(headers)._prepare_for_method_change() + ++ # Strip headers marked as unsafe to forward to the redirected location. ++ # Check remove_headers_on_redirect to avoid a potential network call within ++ # self.is_same_host() which may use socket.gethostbyname() in the future. ++ if retries.remove_headers_on_redirect and not self.is_same_host( ++ redirect_location ++ ): ++ if not isinstance(headers, HTTPHeaderDict): ++ headers = HTTPHeaderDict(headers or {}) ++ for header in list(six.iterkeys(headers)): ++ if header.lower() in retries.remove_headers_on_redirect: ++ headers.pop(header, None) ++ + try: + retries = retries.increment(method, url, response=response, _pool=self) + except MaxRetryError: +-- +2.54.0 + diff --git a/CVE-2026-44432.patch b/CVE-2026-44432.patch new file mode 100644 index 0000000..c5ea288 --- /dev/null +++ b/CVE-2026-44432.patch @@ -0,0 +1,73 @@ +From 7f655554452d03b459c589db3459380a8579c10c Mon Sep 17 00:00:00 2001 +From: Lumir Balhar +Date: Wed, 3 Jun 2026 12:01:02 +0200 +Subject: [PATCH] CVE-2026-44432: drain_conn must not decompress after partial + read + +--- + src/urllib3/response.py | 12 +++++++----- + test/test_response.py | 25 +++++++++++++++++++++++++ + 2 files changed, 32 insertions(+), 5 deletions(-) + +diff --git a/src/urllib3/response.py b/src/urllib3/response.py +index 1357d65..c27947b 100644 +--- a/src/urllib3/response.py ++++ b/src/urllib3/response.py +@@ -480,11 +480,13 @@ 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. +- decode_content=self._has_decoded_content, +- ) ++ # Discard any decoder state so that we read and discard raw bytes ++ # without decompression, avoiding decompression-bomb resource ++ # exhaustion (CVE-2026-44432). ++ self._decoder = None ++ self._has_decoded_content = False ++ self._decoded_buffer = BytesQueueBuffer() ++ self.read(decode_content=False) + except (HTTPError, SocketError, BaseSSLError, HTTPException): + pass + +diff --git a/test/test_response.py b/test/test_response.py +index 597ce46..7b7c13d 100644 +--- a/test/test_response.py ++++ b/test/test_response.py +@@ -745,6 +745,31 @@ class TestResponse(object): + resp.read(1, decode_content=False) + assert resp.read(1, decode_content=True) == b"o" + ++ @mock.patch("urllib3.response.DeflateDecoder.decompress") ++ def test_drain_conn_after_partial_read_does_not_decompress( ++ self, deflate_decompress ++ ): ++ # CVE-2026-44432: drain_conn after a partial decoded read must not ++ # decompress the remainder of the body (decompression-bomb risk). ++ compress = zlib.compressobj(6, zlib.DEFLATED, -zlib.MAX_WBITS) ++ data = compress.compress(b"foobar") ++ data += compress.flush() ++ ++ fp = BytesIO(data) ++ resp = HTTPResponse( ++ fp, headers={"content-encoding": "deflate"}, preload_content=False ++ ) ++ ++ # Partially read with decoding; _has_decoded_content becomes True. ++ deflate_decompress.return_value = b"f" ++ assert resp.read(1) == b"f" ++ assert resp._has_decoded_content ++ ++ # drain_conn must not call the decoder (no decompression of remainder). ++ deflate_decompress.reset_mock() ++ resp.drain_conn() ++ deflate_decompress.assert_not_called() ++ + def test_streaming(self): + fp = BytesIO(b"foo") + resp = HTTPResponse(fp, preload_content=False) +-- +2.54.0 + diff --git a/python3.12-urllib3.spec b/python3.12-urllib3.spec index da55356..258d26a 100644 --- a/python3.12-urllib3.spec +++ b/python3.12-urllib3.spec @@ -6,7 +6,7 @@ Name: python%{python3_pkgversion}-urllib3 Version: 1.26.19 -Release: 2%{?dist} +Release: 3%{?dist} Summary: HTTP library with thread-safe connection pooling, file post, and more # SPDX @@ -17,6 +17,19 @@ Patch1: CVE-2025-66471.patch Patch2: CVE-2025-66418.patch Patch3: CVE-2026-21441.patch +# CVE-2026-44431 +# Sensitive headers were stripped on cross-host redirects by PoolManager but not +# by pools obtained via ProxyManager.connection_from_url(), allowing headers such +# as Authorization, Cookie and Proxy-Authorization to leak to the redirected host. +# Upstream fix: https://github.com/urllib3/urllib3/commit/5ec0de49 +Patch4: CVE-2026-44431.patch + +# CVE-2026-44432 +# drain_conn() after a partial decoded read would decompress all remaining +# response data, enabling a decompression-bomb DoS against the client. +# Upstream fix: https://github.com/urllib3/urllib3/security/advisories/GHSA-mf9v-mfxr-j63j +Patch5: CVE-2026-44432.patch + BuildArch: noarch BuildRequires: python%{python3_pkgversion}-devel @@ -133,6 +146,10 @@ ignore="${ignore-} --ignore=test/test_no_ssl.py" %changelog +* Wed Jun 03 2026 Lumír Balhar - 1.26.19-3 +- Security fixes for CVE-2026-44431 and CVE-2026-44432 +Resolves: RHEL-185125, RHEL-184900 + * Tue Dec 16 2025 Miro Hrončok - 1.26.19-2 - Security fix for CVE-2025-66471 - Security fix for CVE-2025-66418