git: 7bec71c4243b - 2026Q2 - lang/python314: Update to 3.14.5
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Fri, 12 Jun 2026 05:58:53 UTC
The branch 2026Q2 has been updated by vishwin:
URL: https://cgit.FreeBSD.org/ports/commit/?id=7bec71c4243bdec53bb63a38d561ad41b89e09de
commit 7bec71c4243bdec53bb63a38d561ad41b89e09de
Author: Matthias Andree <mandree@FreeBSD.org>
AuthorDate: 2026-05-11 16:00:29 +0000
Commit: Charlie Li <vishwin@FreeBSD.org>
CommitDate: 2026-06-12 05:53:18 +0000
lang/python314: Update to 3.14.5
This release swaps out the incremental garbage collector for the
generational one used in 3.13 due to reports of memory pressure.
Backport the post-release upstream fix for:
Heap Buffer Overflow in pyexpat Character Data Buffering #148441
which is believed to be only triggered in rare circumstances.
Changelog: https://www.python.org/downloads/release/python-3145/
PR: 295200
MFH: 2026Q2
(cherry picked from commit ecdfdb02e3cdae8b5d61d074175ee0424f90bd85)
---
lang/python314/Makefile | 2 +-
lang/python314/Makefile.version | 2 +-
lang/python314/distinfo | 6 +-
...211-reject-CR_LF-in-HTTP-tunnel-request-headers | 108 ---------------------
...ix-quadratic-regex-backtracking-in-configparser | 83 ----------------
...action_substitution-bypass-of-dash-prefix-check | 66 -------------
...tch-gh-148395-fix-possible-uaf-in-decompressors | 65 -------------
...integer-overflow-in-Expats-CharacterDataHandler | 70 +++++++++++++
lang/python314/pkg-plist | 7 +-
9 files changed, 77 insertions(+), 332 deletions(-)
diff --git a/lang/python314/Makefile b/lang/python314/Makefile
index d3513d47ad73..55ea7849a9cf 100644
--- a/lang/python314/Makefile
+++ b/lang/python314/Makefile
@@ -1,6 +1,6 @@
PORTNAME= python
DISTVERSION= ${PYTHON_DISTVERSION} # see Makefile.version
-PORTREVISION= 3
+PORTREVISION= 0
CATEGORIES= lang python
MASTER_SITES= PYTHON/ftp/python/${DISTVERSION:C/[a-z].*//}
PKGNAMESUFFIX= ${PYTHON_SUFFIX}
diff --git a/lang/python314/Makefile.version b/lang/python314/Makefile.version
index c8ccfa3963b0..01917fa79e66 100644
--- a/lang/python314/Makefile.version
+++ b/lang/python314/Makefile.version
@@ -4,4 +4,4 @@
# 1. Update python documentation (lang/python-doc-*)
# Run "make -C lang/python-doc-html makesum"
# 2. Remove PORTREVISION in Makefile
-PYTHON_DISTVERSION= 3.14.4
+PYTHON_DISTVERSION= 3.14.5
diff --git a/lang/python314/distinfo b/lang/python314/distinfo
index 7c5dead58955..762b3cb841c8 100644
--- a/lang/python314/distinfo
+++ b/lang/python314/distinfo
@@ -1,3 +1,3 @@
-TIMESTAMP = 1775640582
-SHA256 (python/Python-3.14.4.tar.xz) = d923c51303e38e249136fc1bdf3568d56ecb03214efdef48516176d3d7faaef8
-SIZE (python/Python-3.14.4.tar.xz) = 23855332
+TIMESTAMP = 1778513616
+SHA256 (python/Python-3.14.5.tar.xz) = 7e32597b99e5d9a39abed35de4693fa169df3e5850d4c334337ffd6a19a36db6
+SIZE (python/Python-3.14.5.tar.xz) = 23903332
diff --git a/lang/python314/files/patch-gh-146211-reject-CR_LF-in-HTTP-tunnel-request-headers b/lang/python314/files/patch-gh-146211-reject-CR_LF-in-HTTP-tunnel-request-headers
deleted file mode 100644
index 989f22a0529b..000000000000
--- a/lang/python314/files/patch-gh-146211-reject-CR_LF-in-HTTP-tunnel-request-headers
+++ /dev/null
@@ -1,108 +0,0 @@
-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
---- ./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
---- ./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
-+++ ./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.
diff --git a/lang/python314/files/patch-gh-146333-Fix-quadratic-regex-backtracking-in-configparser b/lang/python314/files/patch-gh-146333-Fix-quadratic-regex-backtracking-in-configparser
deleted file mode 100644
index 7dffa8ff1cfe..000000000000
--- a/lang/python314/files/patch-gh-146333-Fix-quadratic-regex-backtracking-in-configparser
+++ /dev/null
@@ -1,83 +0,0 @@
-From ab8704a8e05e2f926c10f994e4085e8726048fa4 Mon Sep 17 00:00:00 2001
-From: Joshua Swanson <22283299+joshuaswanson@users.noreply.github.com>
-Date: Tue, 7 Apr 2026 16:10:34 +0200
-Subject: [PATCH] gh-146333: Fix quadratic regex backtracking in configparser
- option parsing (GH-146399)
-
-Use negative lookahead in option regex to prevent backtracking, and to avoid changing logic outside the regexes (since people could use the regex directly).
-(cherry picked from commit 7e0a0be4097f9d29d66fe23f5af86f18a34ed7dd)
-
-Co-authored-by: Joshua Swanson <22283299+joshuaswanson@users.noreply.github.com>
----
- Lib/configparser.py | 8 ++++++--
- Lib/test/test_configparser.py | 20 +++++++++++++++++++
- ...3-25-00-51-03.gh-issue-146333.LqdL__bn.rst | 3 +++
- 3 files changed, 29 insertions(+), 2 deletions(-)
- create mode 100644 Misc/NEWS.d/next/Security/2026-03-25-00-51-03.gh-issue-146333.LqdL__bn.rst
-
-diff --git a/Lib/configparser.py b/Lib/configparser.py
-index d435a5c2fe0da2..e76647d339e913 100644
---- ./Lib/configparser.py
-+++ b/Lib/configparser.py
-@@ -613,7 +613,9 @@ class RawConfigParser(MutableMapping):
- \] # ]
- """
- _OPT_TMPL = r"""
-- (?P<option>.*?) # very permissive!
-+ (?P<option> # very permissive!
-+ (?:(?!{delim})\S)* # non-delimiter non-whitespace
-+ (?:\s+(?:(?!{delim})\S)+)*) # optionally more words
- \s*(?P<vi>{delim})\s* # any number of space/tab,
- # followed by any of the
- # allowed delimiters,
-@@ -621,7 +623,9 @@ class RawConfigParser(MutableMapping):
- (?P<value>.*)$ # everything up to eol
- """
- _OPT_NV_TMPL = r"""
-- (?P<option>.*?) # very permissive!
-+ (?P<option> # very permissive!
-+ (?:(?!{delim})\S)* # non-delimiter non-whitespace
-+ (?:\s+(?:(?!{delim})\S)+)*) # optionally more words
- \s*(?: # any number of space/tab,
- (?P<vi>{delim})\s* # optionally followed by
- # any of the allowed
-diff --git a/Lib/test/test_configparser.py b/Lib/test/test_configparser.py
-index 1bfb53ccbb1398..d7c4f19c1a5ef0 100644
---- ./Lib/test/test_configparser.py
-+++ b/Lib/test/test_configparser.py
-@@ -2270,6 +2270,26 @@ def test_section_bracket_in_key(self):
- output.close()
-
-
-+class ReDoSTestCase(unittest.TestCase):
-+ """Regression tests for quadratic regex backtracking (gh-146333)."""
-+
-+ def test_option_regex_does_not_backtrack(self):
-+ # A line with many spaces between non-delimiter characters
-+ # should be parsed in linear time, not quadratic.
-+ parser = configparser.RawConfigParser()
-+ content = "[section]\n" + "x" + " " * 40000 + "y" + "\n"
-+ # This should complete almost instantly. Before the fix,
-+ # it would take over a minute due to catastrophic backtracking.
-+ with self.assertRaises(configparser.ParsingError):
-+ parser.read_string(content)
-+
-+ def test_option_regex_no_value_does_not_backtrack(self):
-+ parser = configparser.RawConfigParser(allow_no_value=True)
-+ content = "[section]\n" + "x" + " " * 40000 + "y" + "\n"
-+ parser.read_string(content)
-+ self.assertTrue(parser.has_option("section", "x" + " " * 40000 + "y"))
-+
-+
- class MiscTestCase(unittest.TestCase):
- def test__all__(self):
- support.check__all__(self, configparser, not_exported={"Error"})
-diff --git a/Misc/NEWS.d/next/Security/2026-03-25-00-51-03.gh-issue-146333.LqdL__bn.rst b/Misc/NEWS.d/next/Security/2026-03-25-00-51-03.gh-issue-146333.LqdL__bn.rst
-new file mode 100644
-index 00000000000000..96d86ecc0a0fb3
---- /dev/null
-+++ ./Misc/NEWS.d/next/Security/2026-03-25-00-51-03.gh-issue-146333.LqdL__bn.rst
-@@ -0,0 +1,3 @@
-+Fix quadratic backtracking in :class:`configparser.RawConfigParser` option
-+parsing regexes (``OPTCRE`` and ``OPTCRE_NV``). A crafted configuration line
-+with many whitespace characters could cause excessive CPU usage.
diff --git a/lang/python314/files/patch-gh-148169-fix-webbrowser-_action_substitution-bypass-of-dash-prefix-check b/lang/python314/files/patch-gh-148169-fix-webbrowser-_action_substitution-bypass-of-dash-prefix-check
deleted file mode 100644
index 5407326b750a..000000000000
--- a/lang/python314/files/patch-gh-148169-fix-webbrowser-_action_substitution-bypass-of-dash-prefix-check
+++ /dev/null
@@ -1,66 +0,0 @@
-From f529b9470752c28ab69c96f31b0dbc10db69b404 Mon Sep 17 00:00:00 2001
-From: Stan Ulbrych <stan@python.org>
-Date: Mon, 13 Apr 2026 20:02:52 +0100
-Subject: [PATCH] gh-148169: Fix webbrowser `%action` substitution bypass of
- dash-prefix check (GH-148170) (cherry picked from commit
- d22922c8a7958353689dc4763dd72da2dea03fff)
-
-Co-authored-by: Stan Ulbrych <stan@python.org>
----
- Lib/test/test_webbrowser.py | 9 +++++++++
- Lib/webbrowser.py | 5 +++--
- .../2026-03-31-09-15-51.gh-issue-148169.EZJzz2.rst | 2 ++
- 3 files changed, 14 insertions(+), 2 deletions(-)
- create mode 100644 Misc/NEWS.d/next/Security/2026-03-31-09-15-51.gh-issue-148169.EZJzz2.rst
-
-diff --git a/Lib/test/test_webbrowser.py b/Lib/test/test_webbrowser.py
-index 404b3a31a5d2c9..bfbcf112b0b085 100644
---- ./Lib/test/test_webbrowser.py
-+++ b/Lib/test/test_webbrowser.py
-@@ -119,6 +119,15 @@ def test_open_bad_new_parameter(self):
- arguments=[URL],
- kw=dict(new=999))
-
-+ def test_reject_action_dash_prefixes(self):
-+ browser = self.browser_class(name=CMD_NAME)
-+ with self.assertRaises(ValueError):
-+ browser.open('%action--incognito')
-+ # new=1: action is "--new-window", so "%action" itself expands to
-+ # a dash-prefixed flag even with no dash in the original URL.
-+ with self.assertRaises(ValueError):
-+ browser.open('%action', new=1)
-+
-
- class EdgeCommandTest(CommandTestMixin, unittest.TestCase):
-
-diff --git a/Lib/webbrowser.py b/Lib/webbrowser.py
-index 0e0b5034e5f53d..97aad6eea509eb 100644
---- ./Lib/webbrowser.py
-+++ b/Lib/webbrowser.py
-@@ -274,7 +274,6 @@ def _invoke(self, args, remote, autoraise, url=None):
-
- def open(self, url, new=0, autoraise=True):
- sys.audit("webbrowser.open", url)
-- self._check_url(url)
- if new == 0:
- action = self.remote_action
- elif new == 1:
-@@ -288,7 +287,9 @@ def open(self, url, new=0, autoraise=True):
- raise Error("Bad 'new' parameter to open(); "
- f"expected 0, 1, or 2, got {new}")
-
-- args = [arg.replace("%s", url).replace("%action", action)
-+ self._check_url(url.replace("%action", action))
-+
-+ args = [arg.replace("%action", action).replace("%s", url)
- for arg in self.remote_args]
- args = [arg for arg in args if arg]
- success = self._invoke(args, True, autoraise, url)
-diff --git a/Misc/NEWS.d/next/Security/2026-03-31-09-15-51.gh-issue-148169.EZJzz2.rst b/Misc/NEWS.d/next/Security/2026-03-31-09-15-51.gh-issue-148169.EZJzz2.rst
-new file mode 100644
-index 00000000000000..45cdeebe1b6d64
---- /dev/null
-+++ ./Misc/NEWS.d/next/Security/2026-03-31-09-15-51.gh-issue-148169.EZJzz2.rst
-@@ -0,0 +1,2 @@
-+A bypass in :mod:`webbrowser` allowed URLs prefixed with ``%action`` to pass
-+the dash-prefix safety check.
diff --git a/lang/python314/files/patch-gh-148395-fix-possible-uaf-in-decompressors b/lang/python314/files/patch-gh-148395-fix-possible-uaf-in-decompressors
deleted file mode 100644
index d5532033752e..000000000000
--- a/lang/python314/files/patch-gh-148395-fix-possible-uaf-in-decompressors
+++ /dev/null
@@ -1,65 +0,0 @@
-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
-+++ ./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
---- ./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
---- ./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
---- ./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;
- }
diff --git a/lang/python314/files/patch-gh-148441-Avoid-integer-overflow-in-Expats-CharacterDataHandler b/lang/python314/files/patch-gh-148441-Avoid-integer-overflow-in-Expats-CharacterDataHandler
new file mode 100644
index 000000000000..1625b93d088f
--- /dev/null
+++ b/lang/python314/files/patch-gh-148441-Avoid-integer-overflow-in-Expats-CharacterDataHandler
@@ -0,0 +1,70 @@
+From 6588ca5b642a0f878197fc31afb6bfa424fd7219 Mon Sep 17 00:00:00 2001
+From: "Miss Islington (bot)"
+ <31488909+miss-islington@users.noreply.github.com>
+Date: Sun, 10 May 2026 16:08:59 +0200
+Subject: [PATCH] [3.14] gh-148441: Avoid integer overflow in Expat's
+ CharacterDataHandler (GH-148904) (#149638)
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+gh-148441: Avoid integer overflow in Expat's CharacterDataHandler (GH-148904)
+(cherry picked from commit bc1be4f6174086b4a46e3fe656552f5bb4e6e7b2)
+
+Co-authored-by: ByteFlow <fakeshadow1337@gmail.com>
+Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com>
+---
+ Lib/test/test_pyexpat.py | 14 ++++++++++++++
+ .../2026-04-23-12-50-15.gh-issue-148441.zvpCkR.rst | 4 ++++
+ Modules/pyexpat.c | 2 +-
+ 3 files changed, 19 insertions(+), 1 deletion(-)
+ create mode 100644 Misc/NEWS.d/next/Library/2026-04-23-12-50-15.gh-issue-148441.zvpCkR.rst
+
+diff --git ./Lib/test/test_pyexpat.py b/Lib/test/test_pyexpat.py
+index 465f65a03b9e15..6eda89ad2cdf2a 100644
+--- ./Lib/test/test_pyexpat.py
++++ b/Lib/test/test_pyexpat.py
+@@ -672,6 +672,20 @@ def test_change_size_2(self):
+ parser.Parse(xml2, True)
+ self.assertEqual(self.n, 4)
+
++ @support.requires_resource('cpu')
++ @support.requires_resource('walltime')
++ @support.bigmemtest(size=2**31, memuse=4, dry_run=False)
++ def test_large_character_data_does_not_crash(self):
++ # See https://github.com/python/cpython/issues/148441
++ parser = expat.ParserCreate()
++ parser.buffer_text = True
++ parser.buffer_size = 2**31 - 1 # INT_MAX
++ N = 2049 * (1 << 20) - 3 # Character data greater than INT_MAX
++ self.assertGreater(N, parser.buffer_size)
++ parser.CharacterDataHandler = lambda text: None
++ xml_data = b"<r>" + b"A" * N + b"</r>"
++ self.assertEqual(parser.Parse(xml_data, True), 1)
++
+ class ElementDeclHandlerTest(unittest.TestCase):
+ def test_trigger_leak(self):
+ # Unfixed, this test would leak the memory of the so-called
+diff --git a/Misc/NEWS.d/next/Library/2026-04-23-12-50-15.gh-issue-148441.zvpCkR.rst b/Misc/NEWS.d/next/Library/2026-04-23-12-50-15.gh-issue-148441.zvpCkR.rst
+new file mode 100644
+index 00000000000000..762815270e4d40
+--- /dev/null
++++ ./Misc/NEWS.d/next/Library/2026-04-23-12-50-15.gh-issue-148441.zvpCkR.rst
+@@ -0,0 +1,4 @@
++:mod:`xml.parsers.expat`: prevent a crash in
++:meth:`~xml.parsers.expat.xmlparser.CharacterDataHandler`
++when the character data size exceeds the parser's
++:attr:`buffer size <xml.parsers.expat.xmlparser.buffer_size>`.
+diff --git a/Modules/pyexpat.c b/Modules/pyexpat.c
+index f82f8456e489eb..c9dc5e2211ecd5 100644
+--- ./Modules/pyexpat.c
++++ b/Modules/pyexpat.c
+@@ -389,7 +389,7 @@ my_CharacterDataHandler(void *userData, const XML_Char *data, int len)
+ if (self->buffer == NULL)
+ call_character_handler(self, data, len);
+ else {
+- if ((self->buffer_used + len) > self->buffer_size) {
++ if (len > (self->buffer_size - self->buffer_used)) {
+ if (flush_character_buffer(self) < 0)
+ return;
+ /* handler might have changed; drop the rest on the floor
diff --git a/lang/python314/pkg-plist b/lang/python314/pkg-plist
index b5fe9727f492..3090f13e1c5d 100644
--- a/lang/python314/pkg-plist
+++ b/lang/python314/pkg-plist
@@ -1847,7 +1847,7 @@ lib/python%%XYDOT%%/ensurepip/__pycache__/__main__.cpython-%%XY%%.pyc
lib/python%%XYDOT%%/ensurepip/__pycache__/_uninstall.cpython-%%XY%%.opt-1.pyc
lib/python%%XYDOT%%/ensurepip/__pycache__/_uninstall.cpython-%%XY%%.opt-2.pyc
lib/python%%XYDOT%%/ensurepip/__pycache__/_uninstall.cpython-%%XY%%.pyc
-lib/python%%XYDOT%%/ensurepip/_bundled/pip-26.0.1-py3-none-any.whl
+lib/python%%XYDOT%%/ensurepip/_bundled/pip-26.1.1-py3-none-any.whl
lib/python%%XYDOT%%/ensurepip/_uninstall.py
lib/python%%XYDOT%%/enum.py
lib/python%%XYDOT%%/filecmp.py
@@ -2908,9 +2908,6 @@ lib/python%%XYDOT%%/test/__pycache__/_test_eintr.cpython-%%XY%%.pyc
lib/python%%XYDOT%%/test/__pycache__/_test_embed_structseq.cpython-%%XY%%.opt-1.pyc
lib/python%%XYDOT%%/test/__pycache__/_test_embed_structseq.cpython-%%XY%%.opt-2.pyc
lib/python%%XYDOT%%/test/__pycache__/_test_embed_structseq.cpython-%%XY%%.pyc
-lib/python%%XYDOT%%/test/__pycache__/_test_gc_fast_cycles.cpython-%%XY%%.opt-1.pyc
-lib/python%%XYDOT%%/test/__pycache__/_test_gc_fast_cycles.cpython-%%XY%%.opt-2.pyc
-lib/python%%XYDOT%%/test/__pycache__/_test_gc_fast_cycles.cpython-%%XY%%.pyc
lib/python%%XYDOT%%/test/__pycache__/_test_monitoring_shutdown.cpython-%%XY%%.opt-1.pyc
lib/python%%XYDOT%%/test/__pycache__/_test_monitoring_shutdown.cpython-%%XY%%.opt-2.pyc
lib/python%%XYDOT%%/test/__pycache__/_test_monitoring_shutdown.cpython-%%XY%%.pyc
@@ -4233,7 +4230,6 @@ lib/python%%XYDOT%%/test/_crossinterp_definitions.py
lib/python%%XYDOT%%/test/_test_atexit.py
lib/python%%XYDOT%%/test/_test_eintr.py
lib/python%%XYDOT%%/test/_test_embed_structseq.py
-lib/python%%XYDOT%%/test/_test_gc_fast_cycles.py
lib/python%%XYDOT%%/test/_test_monitoring_shutdown.py
lib/python%%XYDOT%%/test/_test_multiprocessing.py
lib/python%%XYDOT%%/test/_test_venv_multiprocessing.py
@@ -6679,6 +6675,7 @@ lib/python%%XYDOT%%/test/test_json/__pycache__/test_tool.cpython-%%XY%%.pyc
lib/python%%XYDOT%%/test/test_json/__pycache__/test_unicode.cpython-%%XY%%.opt-1.pyc
lib/python%%XYDOT%%/test/test_json/__pycache__/test_unicode.cpython-%%XY%%.opt-2.pyc
lib/python%%XYDOT%%/test/test_json/__pycache__/test_unicode.cpython-%%XY%%.pyc
+lib/python%%XYDOT%%/test/test_json/json_lines.jsonl
lib/python%%XYDOT%%/test/test_json/test_decode.py
lib/python%%XYDOT%%/test/test_json/test_default.py
lib/python%%XYDOT%%/test/test_json/test_dump.py