diff mbox series

[wrynose,3/3] python3-urllib3: Fix CVE-2026-97688

Message ID 20261105073137.381446-3-yurade@cisco.com
State New
Headers show
Series [wrynose,1/3] python3-urllib3: Fix CVE-2026-44432 | expand

Commit Message

Yogita Urade Nov. 5, 2026, 7:31 a.m. UTC
This patch applies the upstream fix as referenced in [3],
using the commit shown in [1] and regression commit from [2].

Regression test-only follow-up skips the Brotli case when urllib3 uses
brotlicffi, because that trailing-data case is unsupported by that
backend. The skip is limited to Brotli; the Deflate regression case
remains covered.

[1] https://github.com/urllib3/urllib3/commit/ea2ad7b21a80da3632f80016526a18864586077f
[2] https://github.com/urllib3/urllib3/commit/9016d7e8afc68185496ef07f3c3a4a743d04922e
[3] https://security-tracker.debian.org/tracker/CVE-2026-97688
[4] https://github.com/urllib3/urllib3/pull/5258

Signed-off-by: Yogita Urade <yurade@cisco.com>
---
 .../CVE-2026-97688-regression.patch           |  29 +++++
 .../python3-urllib3/CVE-2026-97688.patch      | 107 ++++++++++++++++++
 .../python/python3-urllib3_2.6.3.bb           |   2 +
 3 files changed, 138 insertions(+)
 create mode 100644 meta/recipes-devtools/python/python3-urllib3/CVE-2026-97688-regression.patch
 create mode 100644 meta/recipes-devtools/python/python3-urllib3/CVE-2026-97688.patch
diff mbox series

Patch

diff --git a/meta/recipes-devtools/python/python3-urllib3/CVE-2026-97688-regression.patch b/meta/recipes-devtools/python/python3-urllib3/CVE-2026-97688-regression.patch
new file mode 100644
index 0000000000..220b024e1e
--- /dev/null
+++ b/meta/recipes-devtools/python/python3-urllib3/CVE-2026-97688-regression.patch
@@ -0,0 +1,29 @@ 
+From 9016d7e8afc68185496ef07f3c3a4a743d04922e Mon Sep 17 00:00:00 2001
+From: Illia Volochii <illia.volochii@gmail.com>
+Date: Tue, 15 Sep 2026 22:04:41 +0300
+Subject: [PATCH] Skip `test_read_chunked_with_trailing_data_does_not_hang` for
+ brotlicffi (#5258)
+
+CVE: CVE-2026-97688
+Upstream-Status: Backport [https://github.com/urllib3/urllib3/commit/9016d7e8afc68185496ef07f3c3a4a743d04922e]
+
+Signed-off-by: Yogita Urade <yurade@cisco.com>
+---
+ test/test_response.py | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/test/test_response.py b/test/test_response.py
+index 5ecdbd9..24118f6 100644
+--- a/test/test_response.py
++++ b/test/test_response.py
+@@ -831,6 +831,8 @@ class TestResponse:
+         # while trailing data remains in the decoder's input buffer.
+         original_data = b"A" * 100
+         content_encoding, compress_func = data
++        if content_encoding == "br" and brotli.__name__ == "brotlicffi":
++            pytest.skip("This case is not supported by brotlicffi")
+         compressed_data = compress_func(original_data) + b"tail"
+         httplib_r = httplib.HTTPResponse(MockSock)  # type: ignore[arg-type]
+         httplib_r.fp = MockChunkedEncodingResponse([compressed_data])  # type: ignore[assignment]
+--
+2.35.6
diff --git a/meta/recipes-devtools/python/python3-urllib3/CVE-2026-97688.patch b/meta/recipes-devtools/python/python3-urllib3/CVE-2026-97688.patch
new file mode 100644
index 0000000000..4fcdce68fc
--- /dev/null
+++ b/meta/recipes-devtools/python/python3-urllib3/CVE-2026-97688.patch
@@ -0,0 +1,107 @@ 
+From ea2ad7b21a80da3632f80016526a18864586077f Mon Sep 17 00:00:00 2001
+From: Illia Volochii <illia.volochii@gmail.com>
+Date: Tue, 15 Sep 2026 21:32:04 +0300
+Subject: [PATCH] Merge commit from fork
+
+* Prevent an infinite loop when decoding Deflate responses
+
+* Move the new test to a better place
+
+* Add a changelog entry
+
+CVE: CVE-2026-97688
+Upstream-Status: Backport [https://github.com/urllib3/urllib3/commit/ea2ad7b21a80da3632f80016526a18864586077f]
+
+Backport Changes:
+- Omitted changelog/GHSA-gh4c-6fx4-qh6g.bugfix.rst because the OE recipe
+  backport applies the runtime fix and tests; the package does not ship it.
+
+Signed-off-by: Yogita Urade <yurade@cisco.com>
+---
+ src/urllib3/response.py |  8 +++++++-
+ test/test_response.py   | 38 +++++++++++++++++++++++++++++++++++++-
+ 2 files changed, 44 insertions(+), 2 deletions(-)
+
+diff --git a/src/urllib3/response.py b/src/urllib3/response.py
+index e9e0cee75e..dccdb12936 100644
+--- a/src/urllib3/response.py
++++ b/src/urllib3/response.py
+@@ -76,6 +76,10 @@ def __init__(self) -> None:
+     def decompress(self, data: bytes, max_length: int = -1) -> bytes:
+         data = self._unfed_data + data
+         self._unfed_data = b""
++        # Data received after EOF cannot produce more output from this
++        # Deflate stream.
++        if self._obj.eof:
++            return b""
+         if not data and not self._obj.unconsumed_tail:
+             return data
+         original_max_length = max_length
+@@ -118,7 +122,9 @@ def decompress(self, data: bytes, max_length: int = -1) -> bytes:
+     @property
+     def has_unconsumed_tail(self) -> bool:
+         return bool(self._unfed_data) or (
+-            bool(self._obj.unconsumed_tail) and not self._first_try
++            bool(self._obj.unconsumed_tail)
++            and not self._first_try
++            and not self._obj.eof
+         )
+ 
+     def flush(self) -> bytes:
+
+diff --git a/test/test_response.py b/test/test_response.py
+index 799325b58c..4675646232 100644
+--- a/test/test_response.py
++++ b/test/test_response.py
+@@ -11,7 +11,7 @@
+ from base64 import b64decode
+ from http.client import IncompleteRead as httplib_IncompleteRead
+ from io import BufferedReader, BytesIO, TextIOWrapper
+-from test import onlyBrotli, onlyZstd
++from test import LONG_TIMEOUT, onlyBrotli, onlyZstd
+ from unittest import mock
+ 
+ import pytest
+@@ -813,6 +813,42 @@ def test_read_chunked_with_all_data_already_in_decompressor(
+             assert len(result[-1]) == len(original_data) % 10240
+         assert b"".join(result) == original_data
+ 
++    @pytest.mark.timeout(LONG_TIMEOUT)
++    @pytest.mark.parametrize(
++        "data",
++        [d[1] for d in _test_compressor_params],
++        ids=[d[0] for d in _test_compressor_params],
++    )
++    def test_read_chunked_with_trailing_data_does_not_hang(
++        self,
++        request: pytest.FixtureRequest,
++        data: tuple[str, typing.Callable[[bytes], bytes]] | None,
++    ) -> None:
++        if data is None:
++            pytest.skip(f"Proper {request.node.callspec.id} decoder is not available")
++        # The decoded body must fill multiple bounded reads so EOF is reached
++        # while trailing data remains in the decoder's input buffer.
++        original_data = b"A" * 100
++        content_encoding, compress_func = data
++        compressed_data = compress_func(original_data) + b"tail"
++        httplib_r = httplib.HTTPResponse(MockSock)  # type: ignore[arg-type]
++        httplib_r.fp = MockChunkedEncodingResponse([compressed_data])  # type: ignore[assignment]
++        r = HTTPResponse(
++            httplib_r,
++            preload_content=False,
++            headers={
++                "transfer-encoding": "chunked",
++                "content-encoding": content_encoding,
++            },
++        )
++
++        stream = r.stream(len(original_data) // 2, decode_content=True)
++        if content_encoding in ("br", "zstd"):
++            with pytest.raises(DecodeError):
++                list(stream)
++        else:
++            assert b"".join(stream) == original_data
++
+     @pytest.mark.parametrize(
+         "delta",
+         (
diff --git a/meta/recipes-devtools/python/python3-urllib3_2.6.3.bb b/meta/recipes-devtools/python/python3-urllib3_2.6.3.bb
index ad5dd190d8..73bfd36974 100644
--- a/meta/recipes-devtools/python/python3-urllib3_2.6.3.bb
+++ b/meta/recipes-devtools/python/python3-urllib3_2.6.3.bb
@@ -8,6 +8,8 @@  SRC_URI += "file://CVE-2026-44431.patch \
             file://CVE-2026-44432.patch \
             file://CVE-2026-97687_p1.patch \
             file://CVE-2026-97687_p2.patch \
+            file://CVE-2026-97688.patch \
+            file://CVE-2026-97688-regression.patch \
            "
 SRC_URI[sha256sum] = "1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed"