diff --git a/Lib/test/test_pyexpat.py b/Lib/test/test_pyexpat.py
index aaa91aca36e3c4..0b95547b63edcf 100644
--- a/Lib/test/test_pyexpat.py
+++ b/Lib/test/test_pyexpat.py
@@ -712,6 +712,19 @@ def test_change_size_2(self):
parser.Parse(xml2, True)
self.assertEqual(self.n, 4)
+ @support.requires_resource('cpu')
+ @support.requires_resource('walltime')
+ def test_large_character_data_no_buffer_overflow(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"" + b"A" * N + b""
+ 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..f51fbdcd99ac9b
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-04-23-12-50-15.gh-issue-148441.zvpCkR.rst
@@ -0,0 +1,4 @@
+:mod:`xml.parsers.expat`: Fix a heap buffer overflow in
+:meth:`~xml.parsers.expat.xmlparser.CharacterDataHandler`
+when the character data size exceeds the parser's
+:attr:`buffer size `.
diff --git a/Modules/pyexpat.c b/Modules/pyexpat.c
index 0f0afe17513ef1..c01f7babe74527 100644
--- a/Modules/pyexpat.c
+++ b/Modules/pyexpat.c
@@ -393,7 +393,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