Ticket #5910: Python-3.14.4-security_fixes-1.patch

File Python-3.14.4-security_fixes-1.patch, 7.7 KB (added by Joe Locash, 14 hours ago)

I'm attaching a patch that addresses this, and CVE-2026-1502 which was also fixed after the release of 3.14.4.

  • Lib/http/client.py

    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 b def _wrap_ipv6(self, ip):  
    972972        return ip
    973973
    974974    def _tunnel(self):
     975        if _contains_disallowed_url_pchar_re.search(self._tunnel_host):
     976            raise ValueError('Tunnel host can\'t contain control characters %r'
     977                             % (self._tunnel_host,))
    975978        connect = b"CONNECT %s:%d %s\r\n" % (
    976979            self._wrap_ipv6(self._tunnel_host.encode("idna")),
    977980            self._tunnel_port,
    978981            self._http_vsn_str.encode("ascii"))
    979982        headers = [connect]
    980983        for header, value in self._tunnel_headers.items():
    981             headers.append(f"{header}: {value}\r\n".encode("latin-1"))
     984            header_bytes = header.encode("latin-1")
     985            value_bytes = value.encode("latin-1")
     986            if not _is_legal_header_name(header_bytes):
     987                raise ValueError('Invalid header name %r' % (header_bytes,))
     988            if _is_illegal_header_value(value_bytes):
     989                raise ValueError('Invalid header value %r' % (value_bytes,))
     990            headers.append(b"%s: %s\r\n" % (header_bytes, value_bytes))
    982991        headers.append(b"\r\n")
    983992        # Making a single send() call instead of one per line encourages
    984993        # the host OS to use a more optimal packet size instead of
  • Lib/test/test_httplib.py

    diff --git a/Lib/test/test_httplib.py b/Lib/test/test_httplib.py
    index bcb828edec7c39..6f3eac6b98a4de 100644
    a b def test_invalid_headers(self):  
    369369                with self.assertRaisesRegex(ValueError, 'Invalid header'):
    370370                    conn.putheader(name, value)
    371371
     372    def test_invalid_tunnel_headers(self):
     373        cases = (
     374            ('Invalid\r\nName', 'ValidValue'),
     375            ('Invalid\rName', 'ValidValue'),
     376            ('Invalid\nName', 'ValidValue'),
     377            ('\r\nInvalidName', 'ValidValue'),
     378            ('\rInvalidName', 'ValidValue'),
     379            ('\nInvalidName', 'ValidValue'),
     380            (' InvalidName', 'ValidValue'),
     381            ('\tInvalidName', 'ValidValue'),
     382            ('Invalid:Name', 'ValidValue'),
     383            (':InvalidName', 'ValidValue'),
     384            ('ValidName', 'Invalid\r\nValue'),
     385            ('ValidName', 'Invalid\rValue'),
     386            ('ValidName', 'Invalid\nValue'),
     387            ('ValidName', 'InvalidValue\r\n'),
     388            ('ValidName', 'InvalidValue\r'),
     389            ('ValidName', 'InvalidValue\n'),
     390        )
     391        for name, value in cases:
     392            with self.subTest((name, value)):
     393                conn = client.HTTPConnection('example.com')
     394                conn.set_tunnel('tunnel', headers={
     395                    name: value
     396                })
     397                conn.sock = FakeSocket('')
     398                with self.assertRaisesRegex(ValueError, 'Invalid header'):
     399                    conn._tunnel()  # Called in .connect()
     400
     401    def test_invalid_tunnel_host(self):
     402        cases = (
     403            'invalid\r.host',
     404            '\ninvalid.host',
     405            'invalid.host\r\n',
     406            'invalid.host\x00',
     407            'invalid host',
     408        )
     409        for tunnel_host in cases:
     410            with self.subTest(tunnel_host):
     411                conn = client.HTTPConnection('example.com')
     412                conn.set_tunnel(tunnel_host)
     413                conn.sock = FakeSocket('')
     414                with self.assertRaisesRegex(ValueError, 'Tunnel host can\'t contain control characters'):
     415                    conn._tunnel()  # Called in .connect()
     416
    372417    def test_headers_debuglevel(self):
    373418        body = (
    374419            b'HTTP/1.1 200 OK\r\n'
  • new file Misc/NEWS.d/next/Security/2026-03-20-09-29-42.gh-issue-146211.PQVbs7.rst

    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
    - +  
     1Reject CR/LF characters in tunnel request headers for the
     2HTTPConnection.set_tunnel() method.
  • new file Misc/NEWS.d/next/Security/2026-04-10-16-28-21.gh-issue-148395.kfzm0G.rst

    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
    - +  
     1Fix a dangling input pointer in :class:`lzma.LZMADecompressor`,
     2:class:`bz2.BZ2Decompressor`, and internal :class:`!zlib._ZlibDecompressor`
     3when memory allocation fails with :exc:`MemoryError`, which could let a
     4subsequent :meth:`!decompress` call read or write through a stale pointer to
     5the already-released caller buffer.
  • Modules/_bz2module.c

    diff --git a/Modules/_bz2module.c b/Modules/_bz2module.c
    index 9e85e0de42cd8d..055ce82e7d2863 100644
    a b decompress(BZ2Decompressor *d, char *data, size_t len, Py_ssize_t max_length)  
    593593    return result;
    594594
    595595error:
     596    bzs->next_in = NULL;
    596597    Py_XDECREF(result);
    597598    return NULL;
    598599}
  • Modules/_lzmamodule.c

    diff --git a/Modules/_lzmamodule.c b/Modules/_lzmamodule.c
    index 462c2181fa6036..6785dc56730c5c 100644
    a b decompress(Decompressor *d, uint8_t *data, size_t len, Py_ssize_t max_length)  
    11201120    return result;
    11211121
    11221122error:
     1123    lzs->next_in = NULL;
    11231124    Py_XDECREF(result);
    11241125    return NULL;
    11251126}
  • Modules/zlibmodule.c

    diff --git a/Modules/zlibmodule.c b/Modules/zlibmodule.c
    index 5b6b0c5cac864a..a86aa5fdbb576c 100644
    a b decompress(ZlibDecompressor *self, uint8_t *data,  
    16751675    return result;
    16761676
    16771677error:
     1678    self->zst.next_in = NULL;
    16781679    Py_XDECREF(result);
    16791680    return NULL;
    16801681}