Hi Slavko, On Tue, Jan 20, 2026 at 01:08:58PM +0100, Slavko wrote: > Package: python3-urllib3 > Severity: serious > Version: 1.26.12-1+deb12u2 > > Hi, > > recent security upgrade breaks httplib3 usage and throws exception: > > AttributeError: 'HTTPResponse' object has no attribute > '_has_decoded_content'. Did you mean: 'decode_content'? > > I call it from requests, but IMO it doesn't matter. As i found, that > particular problem is introduced by CVE-2026-21441.patch, which > contains: > > - self.read() > + self.read( > + # Do not spend resources decoding the content unless > + # decoding has already been initiated. > + decode_content=self._has_decoded_content, > + ) > > But that is only place, where "_has_decoded_content" is in this > version of urllib. I can only guess, that this is patch is either > incomplete, or this part is not appropriate.
Can you please test the test packages from https://deb.debusine.debian.net/debian/developers-carnil-urllib3-debian-bug-1126002/ ? Testworkflow is documented in: https://debusine.debian.net/debian/developers-carnil-urllib3-debian-bug-1126002/work-request/349642/ Attached is the debdiff in case you prefer to build a own package. Regards, Salvatore
diff -Nru python-urllib3-1.26.12/debian/changelog python-urllib3-1.26.12/debian/changelog --- python-urllib3-1.26.12/debian/changelog 2026-01-12 22:53:55.000000000 +0100 +++ python-urllib3-1.26.12/debian/changelog 2026-01-20 22:34:53.000000000 +0100 @@ -1,3 +1,14 @@ +python-urllib3 (1.26.12-1+deb12u3) bookworm-security; urgency=high + + * Non-maintainer upload by the Security Team. + * Prevent issue in HTTPResponse().read() when decoded_content is True and then + False Provided it has initialized eligible decoder(decompressor) and did + decode once (Closes: #1126002) + * fix missed coverage when calling read() having amt=None + * tests: Change expectations as the initial payload changed + + -- Salvatore Bonaccorso <[email protected]> Tue, 20 Jan 2026 22:34:53 +0100 + python-urllib3 (1.26.12-1+deb12u2) bookworm-security; urgency=high * Non-maintainer upload by the Security Team. diff -Nru python-urllib3-1.26.12/debian/patches/Prevent-issue-in-HTTPResponse-.read-when-decoded_con.patch python-urllib3-1.26.12/debian/patches/Prevent-issue-in-HTTPResponse-.read-when-decoded_con.patch --- python-urllib3-1.26.12/debian/patches/Prevent-issue-in-HTTPResponse-.read-when-decoded_con.patch 1970-01-01 01:00:00.000000000 +0100 +++ python-urllib3-1.26.12/debian/patches/Prevent-issue-in-HTTPResponse-.read-when-decoded_con.patch 2026-01-20 22:27:39.000000000 +0100 @@ -0,0 +1,89 @@ +From: Ousret <[email protected]> +Date: Thu, 17 Nov 2022 01:40:19 +0100 +Subject: Prevent issue in HTTPResponse().read() when decoded_content is True + and then False Provided it has initialized eligible decoder(decompressor) and + did decode once +Origin: https://github.com/urllib3/urllib3/commit/cefd1dbba6a20ea4f017e6e472f9ada3a8a743e0 + +[Salvatore Bonaccorso: Backport for code before c35033f6cc54 ("Standardize +HTTPResponse.read(X) behavior regardless of compression").] +--- + src/urllib3/response.py | 12 ++++++++++++ + test/test_response.py | 35 +++++++++++++++++++++++++++++++++++ + 3 files changed, 48 insertions(+) + create mode 100644 changelog/2800.bugfix.rst + +--- a/src/urllib3/response.py ++++ b/src/urllib3/response.py +@@ -237,6 +237,7 @@ class HTTPResponse(io.IOBase): + self.reason = reason + self.strict = strict + self.decode_content = decode_content ++ self._has_decoded_content = False + self.retries = retries + self.enforce_content_length = enforce_content_length + self.auto_close = auto_close +@@ -413,11 +414,17 @@ class HTTPResponse(io.IOBase): + Decode the data passed in and potentially flush the decoder. + """ + if not decode_content: ++ if self._has_decoded_content: ++ raise RuntimeError( ++ "Calling read(decode_content=False) is not supported after " ++ "read(decode_content=True) was called." ++ ) + return data + + try: + if self._decoder: + data = self._decoder.decompress(data) ++ self._has_decoded_content = True + except self.DECODER_ERROR_CLASSES as e: + content_encoding = self.headers.get("content-encoding", "").lower() + raise DecodeError( +--- a/test/test_response.py ++++ b/test/test_response.py +@@ -453,6 +453,43 @@ class TestResponse(object): + next(reader) + assert re.match("I/O operation on closed file.?", str(ctx.value)) + ++ def test_read_with_illegal_mix_decode_toggle(self): ++ compress = zlib.compressobj(6, zlib.DEFLATED, -zlib.MAX_WBITS) ++ data = compress.compress(b"foo") ++ data += compress.flush() ++ ++ fp = BytesIO(data) ++ ++ resp = HTTPResponse( ++ fp, headers={"content-encoding": "deflate"}, preload_content=False ++ ) ++ ++ assert resp.read(1) == b"" ++ assert resp.read(1) == b"f" ++ ++ with pytest.raises( ++ RuntimeError, ++ match=( ++ r"Calling read\(decode_content=False\) is not supported after " ++ r"read\(decode_content=True\) was called" ++ ), ++ ): ++ resp.read(1, decode_content=False) ++ ++ def test_read_with_mix_decode_toggle(self): ++ compress = zlib.compressobj(6, zlib.DEFLATED, -zlib.MAX_WBITS) ++ data = compress.compress(b"foo") ++ data += compress.flush() ++ ++ fp = BytesIO(data) ++ ++ resp = HTTPResponse( ++ fp, headers={"content-encoding": "deflate"}, preload_content=False ++ ) ++ resp.read(1, decode_content=False) ++ assert resp.read(1, decode_content=True) == b"" ++ assert resp.read(1, decode_content=True) == b"o" ++ + def test_streaming(self): + fp = BytesIO(b"foo") + resp = HTTPResponse(fp, preload_content=False) diff -Nru python-urllib3-1.26.12/debian/patches/apply-suggestion-from-pquentin.patch python-urllib3-1.26.12/debian/patches/apply-suggestion-from-pquentin.patch --- python-urllib3-1.26.12/debian/patches/apply-suggestion-from-pquentin.patch 1970-01-01 01:00:00.000000000 +0100 +++ python-urllib3-1.26.12/debian/patches/apply-suggestion-from-pquentin.patch 2026-01-20 22:33:56.000000000 +0100 @@ -0,0 +1,56 @@ +From: Ousret <[email protected]> +Date: Sun, 20 Nov 2022 13:56:21 +0100 +Subject: apply suggestion from @pquentin + had to change expectations as the + initial payload changed +Origin: https://github.com/urllib3/urllib3/commit/698df9ef7e88354e8ec9392471189a168fb31521 + +[Salvatore Bonaccorso: Backport for code before c35033f6cc54 ("Standardize +HTTPResponse.read(X) behavior regardless of compression").] +--- + test/test_response.py | 12 ++++-------- + 1 file changed, 4 insertions(+), 8 deletions(-) + +--- a/test/test_response.py ++++ b/test/test_response.py +@@ -454,9 +454,7 @@ class TestResponse(object): + assert re.match("I/O operation on closed file.?", str(ctx.value)) + + def test_read_with_illegal_mix_decode_toggle(self): +- compress = zlib.compressobj(6, zlib.DEFLATED, -zlib.MAX_WBITS) +- data = compress.compress(b"foo") +- data += compress.flush() ++ data = zlib.compress(b"foo") + + fp = BytesIO(data) + +@@ -464,7 +462,7 @@ class TestResponse(object): + fp, headers={"content-encoding": "deflate"}, preload_content=False + ) + +- assert resp.read(1) == b"" ++ assert resp.read(3) == b"" + assert resp.read(1) == b"f" + + with pytest.raises( +@@ -486,18 +484,16 @@ class TestResponse(object): + resp.read(decode_content=False) + + def test_read_with_mix_decode_toggle(self): +- compress = zlib.compressobj(6, zlib.DEFLATED, -zlib.MAX_WBITS) +- data = compress.compress(b"foo") +- data += compress.flush() ++ data = zlib.compress(b"foo") + + fp = BytesIO(data) + + resp = HTTPResponse( + fp, headers={"content-encoding": "deflate"}, preload_content=False + ) +- resp.read(1, decode_content=False) ++ assert resp.read(2, decode_content=False) is not None + assert resp.read(1, decode_content=True) == b"" +- assert resp.read(1, decode_content=True) == b"o" ++ assert resp.read(1, decode_content=True) == b"f" + + def test_streaming(self): + fp = BytesIO(b"foo") diff -Nru python-urllib3-1.26.12/debian/patches/fix-missed-coverage-when-calling-read-having-amt-Non.patch python-urllib3-1.26.12/debian/patches/fix-missed-coverage-when-calling-read-having-amt-Non.patch --- python-urllib3-1.26.12/debian/patches/fix-missed-coverage-when-calling-read-having-amt-Non.patch 1970-01-01 01:00:00.000000000 +0100 +++ python-urllib3-1.26.12/debian/patches/fix-missed-coverage-when-calling-read-having-amt-Non.patch 2026-01-20 22:27:46.000000000 +0100 @@ -0,0 +1,32 @@ +From: Ousret <[email protected]> +Date: Thu, 17 Nov 2022 01:58:12 +0100 +Subject: fix missed coverage when calling read() having amt=None +Origin: https://github.com/urllib3/urllib3/commit/4acccf76c2892e80aebb5840f7de1460a4c64a61 + +--- + test/test_response.py | 9 +++++++++ + 1 file changed, 9 insertions(+) + +diff --git a/test/test_response.py b/test/test_response.py +index 454ed60c7813..85aa6986771d 100644 +--- a/test/test_response.py ++++ b/test/test_response.py +@@ -587,6 +587,15 @@ class TestResponse: + ): + resp.read(1, decode_content=False) + ++ with pytest.raises( ++ RuntimeError, ++ match=( ++ r"Calling read\(decode_content=False\) is not supported after " ++ r"read\(decode_content=True\) was called" ++ ), ++ ): ++ resp.read(decode_content=False) ++ + def test_read_with_mix_decode_toggle(self): + compress = zlib.compressobj(6, zlib.DEFLATED, -zlib.MAX_WBITS) + data = compress.compress(b"foo") +-- +2.51.0 + diff -Nru python-urllib3-1.26.12/debian/patches/series python-urllib3-1.26.12/debian/patches/series --- python-urllib3-1.26.12/debian/patches/series 2026-01-12 22:53:55.000000000 +0100 +++ python-urllib3-1.26.12/debian/patches/series 2026-01-20 22:27:46.000000000 +0100 @@ -6,3 +6,6 @@ CVE-2025-50181.patch CVE-2025-66418.patch CVE-2026-21441.patch +Prevent-issue-in-HTTPResponse-.read-when-decoded_con.patch +fix-missed-coverage-when-calling-read-having-amt-Non.patch +apply-suggestion-from-pquentin.patch

