Submitted By:            Joe Locash
Date:                    2026-04-13
Initial Package Version: 3.14.4
Upstream Status:         Applied
Origin:                  Upstream:
                           https://github.com/python/cpython/pull/148342
                           https://github.com/python/cpython/pull/148480
Description:             Upsream fixes for CVE-2026-1502 and CVE-2026-6100.

From afdd351544e8112d4070a31f2218f99256697472 Mon Sep 17 00:00:00 2001
From: Seth Larson <seth@python.org>
Date: Fri, 10 Apr 2026 10:21:42 -0500
Subject: [PATCH] gh-146211: Reject CR/LF in HTTP tunnel request headers
 (GH-146212) (cherry picked from commit
 05ed7ce7ae9e17c23a04085b2539fe6d6d3cef69)

Co-authored-by: Seth Larson <seth@python.org>
Co-authored-by: Illia Volochii <illia.volochii@gmail.com>
---
 Lib/http/client.py                            | 11 ++++-
 Lib/test/test_httplib.py                      | 45 +++++++++++++++++++
 ...-03-20-09-29-42.gh-issue-146211.PQVbs7.rst |  2 +
 3 files changed, 57 insertions(+), 1 deletion(-)
 create mode 100644 Misc/NEWS.d/next/Security/2026-03-20-09-29-42.gh-issue-146211.PQVbs7.rst

diff --git a/Lib/http/client.py b/Lib/http/client.py
index 77f8d26291dfc2..6fb7d254ea9c27 100644
--- a/Lib/http/client.py
+++ b/Lib/http/client.py
@@ -972,13 +972,22 @@ def _wrap_ipv6(self, ip):
         return ip
 
     def _tunnel(self):
+        if _contains_disallowed_url_pchar_re.search(self._tunnel_host):
+            raise ValueError('Tunnel host can\'t contain control characters %r'
+                             % (self._tunnel_host,))
         connect = b"CONNECT %s:%d %s\r\n" % (
             self._wrap_ipv6(self._tunnel_host.encode("idna")),
             self._tunnel_port,
             self._http_vsn_str.encode("ascii"))
         headers = [connect]
         for header, value in self._tunnel_headers.items():
-            headers.append(f"{header}: {value}\r\n".encode("latin-1"))
+            header_bytes = header.encode("latin-1")
+            value_bytes = value.encode("latin-1")
+            if not _is_legal_header_name(header_bytes):
+                raise ValueError('Invalid header name %r' % (header_bytes,))
+            if _is_illegal_header_value(value_bytes):
+                raise ValueError('Invalid header value %r' % (value_bytes,))
+            headers.append(b"%s: %s\r\n" % (header_bytes, value_bytes))
         headers.append(b"\r\n")
         # Making a single send() call instead of one per line encourages
         # the host OS to use a more optimal packet size instead of
diff --git a/Lib/test/test_httplib.py b/Lib/test/test_httplib.py
index bcb828edec7c39..6f3eac6b98a4de 100644
--- a/Lib/test/test_httplib.py
+++ b/Lib/test/test_httplib.py
@@ -369,6 +369,51 @@ def test_invalid_headers(self):
                 with self.assertRaisesRegex(ValueError, 'Invalid header'):
                     conn.putheader(name, value)
 
+    def test_invalid_tunnel_headers(self):
+        cases = (
+            ('Invalid\r\nName', 'ValidValue'),
+            ('Invalid\rName', 'ValidValue'),
+            ('Invalid\nName', 'ValidValue'),
+            ('\r\nInvalidName', 'ValidValue'),
+            ('\rInvalidName', 'ValidValue'),
+            ('\nInvalidName', 'ValidValue'),
+            (' InvalidName', 'ValidValue'),
+            ('\tInvalidName', 'ValidValue'),
+            ('Invalid:Name', 'ValidValue'),
+            (':InvalidName', 'ValidValue'),
+            ('ValidName', 'Invalid\r\nValue'),
+            ('ValidName', 'Invalid\rValue'),
+            ('ValidName', 'Invalid\nValue'),
+            ('ValidName', 'InvalidValue\r\n'),
+            ('ValidName', 'InvalidValue\r'),
+            ('ValidName', 'InvalidValue\n'),
+        )
+        for name, value in cases:
+            with self.subTest((name, value)):
+                conn = client.HTTPConnection('example.com')
+                conn.set_tunnel('tunnel', headers={
+                    name: value
+                })
+                conn.sock = FakeSocket('')
+                with self.assertRaisesRegex(ValueError, 'Invalid header'):
+                    conn._tunnel()  # Called in .connect()
+
+    def test_invalid_tunnel_host(self):
+        cases = (
+            'invalid\r.host',
+            '\ninvalid.host',
+            'invalid.host\r\n',
+            'invalid.host\x00',
+            'invalid host',
+        )
+        for tunnel_host in cases:
+            with self.subTest(tunnel_host):
+                conn = client.HTTPConnection('example.com')
+                conn.set_tunnel(tunnel_host)
+                conn.sock = FakeSocket('')
+                with self.assertRaisesRegex(ValueError, 'Tunnel host can\'t contain control characters'):
+                    conn._tunnel()  # Called in .connect()
+
     def test_headers_debuglevel(self):
         body = (
             b'HTTP/1.1 200 OK\r\n'
diff --git a/Misc/NEWS.d/next/Security/2026-03-20-09-29-42.gh-issue-146211.PQVbs7.rst b/Misc/NEWS.d/next/Security/2026-03-20-09-29-42.gh-issue-146211.PQVbs7.rst
new file mode 100644
index 00000000000000..4993633b8ebebb
--- /dev/null
+++ b/Misc/NEWS.d/next/Security/2026-03-20-09-29-42.gh-issue-146211.PQVbs7.rst
@@ -0,0 +1,2 @@
+Reject CR/LF characters in tunnel request headers for the
+HTTPConnection.set_tunnel() method.
From c8d8173c4b06d06902c99ec010ad785a30952880 Mon Sep 17 00:00:00 2001
From: Stan Ulbrych <stan@python.org>
Date: Mon, 13 Apr 2026 02:14:54 +0100
Subject: [PATCH] gh-148395: Fix a possible UAF in
 `{LZMA,BZ2,_Zlib}Decompressor` (GH-148396)

Fix dangling input pointer after `MemoryError` in _lzma/_bz2/_ZlibDecompressor.decompress
(cherry picked from commit 8fc66aef6d7b3ae58f43f5c66f9366cc8cbbfcd2)

Co-authored-by: Stan Ulbrych <stan@python.org>
---
 .../Security/2026-04-10-16-28-21.gh-issue-148395.kfzm0G.rst  | 5 +++++
 Modules/_bz2module.c                                         | 1 +
 Modules/_lzmamodule.c                                        | 1 +
 Modules/zlibmodule.c                                         | 1 +
 4 files changed, 8 insertions(+)
 create mode 100644 Misc/NEWS.d/next/Security/2026-04-10-16-28-21.gh-issue-148395.kfzm0G.rst

diff --git a/Misc/NEWS.d/next/Security/2026-04-10-16-28-21.gh-issue-148395.kfzm0G.rst b/Misc/NEWS.d/next/Security/2026-04-10-16-28-21.gh-issue-148395.kfzm0G.rst
new file mode 100644
index 00000000000000..9502189ab199c1
--- /dev/null
+++ b/Misc/NEWS.d/next/Security/2026-04-10-16-28-21.gh-issue-148395.kfzm0G.rst
@@ -0,0 +1,5 @@
+Fix a dangling input pointer in :class:`lzma.LZMADecompressor`,
+:class:`bz2.BZ2Decompressor`, and internal :class:`!zlib._ZlibDecompressor`
+when memory allocation fails with :exc:`MemoryError`, which could let a
+subsequent :meth:`!decompress` call read or write through a stale pointer to
+the already-released caller buffer.
diff --git a/Modules/_bz2module.c b/Modules/_bz2module.c
index 9e85e0de42cd8d..055ce82e7d2863 100644
--- a/Modules/_bz2module.c
+++ b/Modules/_bz2module.c
@@ -593,6 +593,7 @@ decompress(BZ2Decompressor *d, char *data, size_t len, Py_ssize_t max_length)
     return result;
 
 error:
+    bzs->next_in = NULL;
     Py_XDECREF(result);
     return NULL;
 }
diff --git a/Modules/_lzmamodule.c b/Modules/_lzmamodule.c
index 462c2181fa6036..6785dc56730c5c 100644
--- a/Modules/_lzmamodule.c
+++ b/Modules/_lzmamodule.c
@@ -1120,6 +1120,7 @@ decompress(Decompressor *d, uint8_t *data, size_t len, Py_ssize_t max_length)
     return result;
 
 error:
+    lzs->next_in = NULL;
     Py_XDECREF(result);
     return NULL;
 }
diff --git a/Modules/zlibmodule.c b/Modules/zlibmodule.c
index 5b6b0c5cac864a..a86aa5fdbb576c 100644
--- a/Modules/zlibmodule.c
+++ b/Modules/zlibmodule.c
@@ -1675,6 +1675,7 @@ decompress(ZlibDecompressor *self, uint8_t *data,
     return result;
 
 error:
+    self->zst.next_in = NULL;
     Py_XDECREF(result);
     return NULL;
 }
