Skip to content

Commit bb7e16a

Browse files
gh-153605: Fix gettext.GNUTranslations raising struct.error on a malformed .mo file (#153606)
Co-authored-by: Stan Ulbrych <stan@python.org>
1 parent efbd165 commit bb7e16a

3 files changed

Lines changed: 44 additions & 1 deletion

File tree

‎Lib/gettext.py‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,8 @@ def _parse(self, fp):
362362
self.plural = lambda n: int(n != 1) # germanic plural by default
363363
buf = fp.read()
364364
buflen = len(buf)
365+
if buflen < 20:
366+
raise OSError(0, 'File is corrupt', filename)
365367
# Are we big endian or little endian?
366368
magic = unpack('<I', buf[:4])[0]
367369
if magic == self.LE_MAGIC:
@@ -378,6 +380,11 @@ def _parse(self, fp):
378380
if major_version not in self.VERSIONS:
379381
raise OSError(0, 'Bad version number ' + str(major_version), filename)
380382

383+
# The seek tables must lie within the file.
384+
if (masteridx + 8 * msgcount > buflen
385+
or transidx + 8 * msgcount > buflen):
386+
raise OSError(0, 'File is corrupt', filename)
387+
381388
# Now put all messages from the .mo file buffer into the catalog
382389
# dictionary.
383390
for i in range(0, msgcount):

‎Lib/test/test_gettext.py‎

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import os
22
import base64
33
import gettext
4+
import io
45
import unittest
56
import unittest.mock
67
from functools import partial
@@ -39,7 +40,7 @@
3940
'''
4041

4142
# .mo file with an invalid magic number
42-
GNU_MO_DATA_BAD_MAGIC_NUMBER = base64.b64encode(b'ABCD')
43+
GNU_MO_DATA_BAD_MAGIC_NUMBER = base64.b64encode(b'ABCD' + bytes(16))
4344

4445
# This data contains an invalid major version number (5)
4546
# An unexpected major version number should be treated as an error when
@@ -314,6 +315,39 @@ def test_corrupt_file(self):
314315
self.assertEqual(exception.strerror, "File is corrupt")
315316
self.assertEqual(exception.filename, MOFILE_CORRUPT)
316317

318+
def test_truncated_header(self):
319+
magic = b'\xde\x12\x04\x95'
320+
for buf in (b'', magic, magic + bytes(15)):
321+
with self.subTest(buflen=len(buf)):
322+
with self.assertRaises(OSError) as cm:
323+
gettext.GNUTranslations(io.BytesIO(buf))
324+
self.assertEqual(cm.exception.errno, 0)
325+
self.assertEqual(cm.exception.strerror, "File is corrupt")
326+
327+
def test_offsets_out_of_bounds(self):
328+
buf = bytes([
329+
0xde, 0x12, 0x04, 0x95, # Magic
330+
0x00, 0x00, 0x00, 0x00, # Version
331+
0x01, 0x00, 0x00, 0x00, # Message count
332+
0xe8, 0x03, 0x00, 0x00, # Message offset (past EOF)
333+
0xd0, 0x07, 0x00, 0x00, # Translation offset (past EOF)
334+
])
335+
with self.assertRaises(OSError) as cm:
336+
gettext.GNUTranslations(io.BytesIO(buf))
337+
self.assertEqual(cm.exception.errno, 0)
338+
self.assertEqual(cm.exception.strerror, "File is corrupt")
339+
340+
def test_empty_catalog(self):
341+
buf = bytes([
342+
0xde, 0x12, 0x04, 0x95, # Magic
343+
0x00, 0x00, 0x00, 0x00, # Version
344+
0x00, 0x00, 0x00, 0x00, # Message count
345+
0x14, 0x00, 0x00, 0x00, # Message offset
346+
0x14, 0x00, 0x00, 0x00, # Translation offset
347+
])
348+
t = gettext.GNUTranslations(io.BytesIO(buf))
349+
self.assertEqual(t.gettext('foo'), 'foo')
350+
317351
def test_big_endian_file(self):
318352
with open(MOFILE_BIG_ENDIAN, 'rb') as fp:
319353
t = gettext.GNUTranslations(fp)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix :class:`gettext.GNUTranslations` raising :exc:`struct.error` instead of
2+
:exc:`OSError` when reading a malformed ``.mo`` file. Patch by tonghuaroot.

0 commit comments

Comments
 (0)