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

Reply via email to