https://github.com/python/cpython/commit/bb7e16ace95c8998e23f2d643f9b7968c9c120cd
commit: bb7e16ace95c8998e23f2d643f9b7968c9c120cd
branch: main
author: tonghuaroot (童话) <[email protected]>
committer: StanFromIreland <[email protected]>
date: 2026-10-25T18:06:47Z
summary:

gh-153605: Fix `gettext.GNUTranslations` raising `struct.error` on a malformed 
`.mo` file (#153606)

Co-authored-by: Stan Ulbrych <[email protected]>

files:
A Misc/NEWS.d/next/Library/2026-07-11-16-00-00.gh-issue-153605.Rk7Xz3.rst
M Lib/gettext.py
M Lib/test/test_gettext.py

diff --git a/Lib/gettext.py b/Lib/gettext.py
index f681f69b9827cb..91ae3bdc82465f 100644
--- a/Lib/gettext.py
+++ b/Lib/gettext.py
@@ -362,6 +362,8 @@ def _parse(self, fp):
         self.plural = lambda n: int(n != 1) # germanic plural by default
         buf = fp.read()
         buflen = len(buf)
+        if buflen < 20:
+            raise OSError(0, 'File is corrupt', filename)
         # Are we big endian or little endian?
         magic = unpack('<I', buf[:4])[0]
         if magic == self.LE_MAGIC:
@@ -378,6 +380,11 @@ def _parse(self, fp):
         if major_version not in self.VERSIONS:
             raise OSError(0, 'Bad version number ' + str(major_version), 
filename)
 
+        # The seek tables must lie within the file.
+        if (masteridx + 8 * msgcount > buflen
+                or transidx + 8 * msgcount > buflen):
+            raise OSError(0, 'File is corrupt', filename)
+
         # Now put all messages from the .mo file buffer into the catalog
         # dictionary.
         for i in range(0, msgcount):
diff --git a/Lib/test/test_gettext.py b/Lib/test/test_gettext.py
index b18489f34ae480..a1830d4c39c08e 100644
--- a/Lib/test/test_gettext.py
+++ b/Lib/test/test_gettext.py
@@ -1,6 +1,7 @@
 import os
 import base64
 import gettext
+import io
 import unittest
 import unittest.mock
 from functools import partial
@@ -39,7 +40,7 @@
 '''
 
 # .mo file with an invalid magic number
-GNU_MO_DATA_BAD_MAGIC_NUMBER = base64.b64encode(b'ABCD')
+GNU_MO_DATA_BAD_MAGIC_NUMBER = base64.b64encode(b'ABCD' + bytes(16))
 
 # This data contains an invalid major version number (5)
 # An unexpected major version number should be treated as an error when
@@ -314,6 +315,39 @@ def test_corrupt_file(self):
             self.assertEqual(exception.strerror, "File is corrupt")
             self.assertEqual(exception.filename, MOFILE_CORRUPT)
 
+    def test_truncated_header(self):
+        magic = b'\xde\x12\x04\x95'
+        for buf in (b'', magic, magic + bytes(15)):
+            with self.subTest(buflen=len(buf)):
+                with self.assertRaises(OSError) as cm:
+                    gettext.GNUTranslations(io.BytesIO(buf))
+                self.assertEqual(cm.exception.errno, 0)
+                self.assertEqual(cm.exception.strerror, "File is corrupt")
+
+    def test_offsets_out_of_bounds(self):
+        buf = bytes([
+            0xde, 0x12, 0x04, 0x95,  # Magic
+            0x00, 0x00, 0x00, 0x00,  # Version
+            0x01, 0x00, 0x00, 0x00,  # Message count
+            0xe8, 0x03, 0x00, 0x00,  # Message offset (past EOF)
+            0xd0, 0x07, 0x00, 0x00,  # Translation offset (past EOF)
+        ])
+        with self.assertRaises(OSError) as cm:
+            gettext.GNUTranslations(io.BytesIO(buf))
+        self.assertEqual(cm.exception.errno, 0)
+        self.assertEqual(cm.exception.strerror, "File is corrupt")
+
+    def test_empty_catalog(self):
+        buf = bytes([
+            0xde, 0x12, 0x04, 0x95,  # Magic
+            0x00, 0x00, 0x00, 0x00,  # Version
+            0x00, 0x00, 0x00, 0x00,  # Message count
+            0x14, 0x00, 0x00, 0x00,  # Message offset
+            0x14, 0x00, 0x00, 0x00,  # Translation offset
+        ])
+        t = gettext.GNUTranslations(io.BytesIO(buf))
+        self.assertEqual(t.gettext('foo'), 'foo')
+
     def test_big_endian_file(self):
         with open(MOFILE_BIG_ENDIAN, 'rb') as fp:
             t = gettext.GNUTranslations(fp)
diff --git 
a/Misc/NEWS.d/next/Library/2026-07-11-16-00-00.gh-issue-153605.Rk7Xz3.rst 
b/Misc/NEWS.d/next/Library/2026-07-11-16-00-00.gh-issue-153605.Rk7Xz3.rst
new file mode 100644
index 00000000000000..a08fde3045c077
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-07-11-16-00-00.gh-issue-153605.Rk7Xz3.rst
@@ -0,0 +1,2 @@
+Fix :class:`gettext.GNUTranslations` raising :exc:`struct.error` instead of
+:exc:`OSError` when reading a malformed ``.mo`` file. Patch by tonghuaroot.

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to