From e59353e32b70fcccd65d829fe8145aa2575821ec Mon Sep 17 00:00:00 2001 From: Frederick Price Date: Mon, 8 Apr 2024 19:09:16 -0400 Subject: [PATCH 1/2] CVE-2017-18207 cherry-pick 3c0a5a7c7ba8fbbc95dd1fe76cd7a1c0ce167371 --- Lib/aifc.py | 4 ++ Lib/sunau.py | 2 + Lib/test/test_aifc.py | 63 ++++++++++++------ Lib/test/test_sunau.py | 39 +++++++++++ Lib/test/test_wave.py | 65 +++++++++++++++++++ Lib/wave.py | 16 ++++- .../2018-03-01-17-49-56.bpo-32056.IlpfgE.rst | 3 + 7 files changed, 170 insertions(+), 22 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2018-03-01-17-49-56.bpo-32056.IlpfgE.rst diff --git a/Lib/aifc.py b/Lib/aifc.py index 981f8010690e00..d0e5e02fa5ee30 100644 --- a/Lib/aifc.py +++ b/Lib/aifc.py @@ -465,6 +465,10 @@ def _read_comm_chunk(self, chunk): self._nframes = _read_long(chunk) self._sampwidth = (_read_short(chunk) + 7) // 8 self._framerate = int(_read_float(chunk)) + if self._sampwidth <= 0: + raise Error('bad sample width') + if self._nchannels <= 0: + raise Error('bad # of channels') self._framesize = self._nchannels * self._sampwidth if self._aifc: #DEBUG: SGI's soundeditor produces a bad size :-( diff --git a/Lib/sunau.py b/Lib/sunau.py index b53044d22b1648..b5d83ea4b464c6 100644 --- a/Lib/sunau.py +++ b/Lib/sunau.py @@ -194,6 +194,8 @@ def initfp(self, file): raise Error, 'unknown encoding' self._framerate = int(_read_u32(file)) self._nchannels = int(_read_u32(file)) + if not self._nchannels: + raise Error('bad # of channels') self._framesize = self._framesize * self._nchannels if self._hdr_size > 24: self._info = file.read(self._hdr_size - 24) diff --git a/Lib/test/test_aifc.py b/Lib/test/test_aifc.py index 92bbe7bc75da93..48c20c8b40d94a 100644 --- a/Lib/test/test_aifc.py +++ b/Lib/test/test_aifc.py @@ -216,45 +216,70 @@ def test_read_no_comm_chunk(self): def test_read_no_ssnd_chunk(self): b = b'FORM' + struct.pack('>L', 4) + b'AIFC' - b += b'COMM' + struct.pack('>LhlhhLL', 38, 0, 0, 0, 0, 0, 0) + b += b'COMM' + struct.pack('>LhlhhLL', 38, 1, 0, 8, + 0x4000 | 12, 11025<<18, 0) b += b'NONE' + struct.pack('B', 14) + b'not compressed' + b'\x00' with self.assertRaisesRegexp(aifc.Error, 'COMM chunk and/or SSND chunk' ' missing'): aifc.open(io.BytesIO(b)) def test_read_wrong_compression_type(self): - b = 'FORM' + struct.pack('>L', 4) + 'AIFC' - b += 'COMM' + struct.pack('>LhlhhLL', 23, 0, 0, 0, 0, 0, 0) - b += 'WRNG' + struct.pack('B', 0) + b = b'FORM' + struct.pack('>L', 4) + b'AIFC' + b += b'COMM' + struct.pack('>LhlhhLL', 23, 1, 0, 8, + 0x4000 | 12, 11025<<18, 0) + b += b'WRNG' + struct.pack('B', 0) self.assertRaises(aifc.Error, aifc.open, io.BytesIO(b)) + def test_read_wrong_number_of_channels(self): + for nchannels in 0, -1: + b = b'FORM' + struct.pack('>L', 4) + b'AIFC' + b += b'COMM' + struct.pack('>LhlhhLL', 38, nchannels, 0, 8, + 0x4000 | 12, 11025<<18, 0) + b += b'NONE' + struct.pack('B', 14) + b'not compressed' + b'\x00' + b += b'SSND' + struct.pack('>L', 8) + b'\x00' * 8 + with self.assertRaisesRegex(aifc.Error, 'bad # of channels'): + aifc.open(io.BytesIO(b)) + + def test_read_wrong_sample_width(self): + for sampwidth in 0, -1: + b = b'FORM' + struct.pack('>L', 4) + b'AIFC' + b += b'COMM' + struct.pack('>LhlhhLL', 38, 1, 0, sampwidth, + 0x4000 | 12, 11025<<18, 0) + b += b'NONE' + struct.pack('B', 14) + b'not compressed' + b'\x00' + b += b'SSND' + struct.pack('>L', 8) + b'\x00' * 8 + with self.assertRaisesRegex(aifc.Error, 'bad sample width'): + aifc.open(io.BytesIO(b)) + def test_read_wrong_marks(self): - b = 'FORM' + struct.pack('>L', 4) + 'AIFF' - b += 'COMM' + struct.pack('>LhlhhLL', 18, 0, 0, 0, 0, 0, 0) - b += 'SSND' + struct.pack('>L', 8) + '\x00' * 8 - b += 'MARK' + struct.pack('>LhB', 3, 1, 1) - with captured_stdout() as s: + b = b'FORM' + struct.pack('>L', 4) + b'AIFF' + b += b'COMM' + struct.pack('>LhlhhLL', 18, 1, 0, 8, + 0x4000 | 12, 11025<<18, 0) + b += b'SSND' + struct.pack('>L', 8) + b'\x00' * 8 + b += b'MARK' + struct.pack('>LhB', 3, 1, 1) + with self.assertWarns(UserWarning) as cm: f = aifc.open(io.BytesIO(b)) self.assertEqual(s.getvalue(), 'Warning: MARK chunk contains ' 'only 0 markers instead of 1\n') self.assertEqual(f.getmarkers(), None) def test_read_comm_kludge_compname_even(self): - b = 'FORM' + struct.pack('>L', 4) + 'AIFC' - b += 'COMM' + struct.pack('>LhlhhLL', 18, 0, 0, 0, 0, 0, 0) - b += 'NONE' + struct.pack('B', 4) + 'even' + '\x00' - b += 'SSND' + struct.pack('>L', 8) + '\x00' * 8 - with captured_stdout() as s: + b = b'FORM' + struct.pack('>L', 4) + b'AIFC' + b += b'COMM' + struct.pack('>LhlhhLL', 18, 1, 0, 8, + 0x4000 | 12, 11025<<18, 0) + b += b'NONE' + struct.pack('B', 4) + b'even' + b'\x00' + b += b'SSND' + struct.pack('>L', 8) + b'\x00' * 8 + with self.assertWarns(UserWarning) as cm: f = aifc.open(io.BytesIO(b)) self.assertEqual(s.getvalue(), 'Warning: bad COMM chunk size\n') self.assertEqual(f.getcompname(), 'even') def test_read_comm_kludge_compname_odd(self): - b = 'FORM' + struct.pack('>L', 4) + 'AIFC' - b += 'COMM' + struct.pack('>LhlhhLL', 18, 0, 0, 0, 0, 0, 0) - b += 'NONE' + struct.pack('B', 3) + 'odd' - b += 'SSND' + struct.pack('>L', 8) + '\x00' * 8 - with captured_stdout() as s: + b = b'FORM' + struct.pack('>L', 4) + b'AIFC' + b += b'COMM' + struct.pack('>LhlhhLL', 18, 1, 0, 8, + 0x4000 | 12, 11025<<18, 0) + b += b'NONE' + struct.pack('B', 3) + b'odd' + b += b'SSND' + struct.pack('>L', 8) + b'\x00' * 8 + with self.assertWarns(UserWarning) as cm: f = aifc.open(io.BytesIO(b)) self.assertEqual(s.getvalue(), 'Warning: bad COMM chunk size\n') self.assertEqual(f.getcompname(), 'odd') diff --git a/Lib/test/test_sunau.py b/Lib/test/test_sunau.py index f6828683521df1..332868c25ff00d 100644 --- a/Lib/test/test_sunau.py +++ b/Lib/test/test_sunau.py @@ -1,6 +1,9 @@ from test.test_support import TESTFN, run_unittest import unittest from test import audiotests +from audioop import byteswap +import io +import struct import sys import sunau @@ -96,5 +99,41 @@ def test_main(): run_unittest(SunauPCM8Test, SunauPCM16Test, SunauPCM16Test, SunauPCM32Test, SunauULAWTest) + +class SunauLowLevelTest(unittest.TestCase): + + def test_read_bad_magic_number(self): + b = b'SPA' + with self.assertRaises(EOFError): + sunau.open(io.BytesIO(b)) + b = b'SPAM' + with self.assertRaisesRegex(sunau.Error, 'bad magic number'): + sunau.open(io.BytesIO(b)) + + def test_read_too_small_header(self): + b = struct.pack('>LLLLL', sunau.AUDIO_FILE_MAGIC, 20, 0, + sunau.AUDIO_FILE_ENCODING_LINEAR_8, 11025) + with self.assertRaisesRegex(sunau.Error, 'header size too small'): + sunau.open(io.BytesIO(b)) + + def test_read_too_large_header(self): + b = struct.pack('>LLLLLL', sunau.AUDIO_FILE_MAGIC, 124, 0, + sunau.AUDIO_FILE_ENCODING_LINEAR_8, 11025, 1) + b += b'\0' * 100 + with self.assertRaisesRegex(sunau.Error, 'header size ridiculously large'): + sunau.open(io.BytesIO(b)) + + def test_read_wrong_encoding(self): + b = struct.pack('>LLLLLL', sunau.AUDIO_FILE_MAGIC, 24, 0, 0, 11025, 1) + with self.assertRaisesRegex(sunau.Error, r'encoding not \(yet\) supported'): + sunau.open(io.BytesIO(b)) + + def test_read_wrong_number_of_channels(self): + b = struct.pack('>LLLLLL', sunau.AUDIO_FILE_MAGIC, 24, 0, + sunau.AUDIO_FILE_ENCODING_LINEAR_8, 11025, 0) + with self.assertRaisesRegex(sunau.Error, 'bad # of channels'): + sunau.open(io.BytesIO(b)) + + if __name__ == "__main__": test_main() diff --git a/Lib/test/test_wave.py b/Lib/test/test_wave.py index 9513df459386b2..ab911d70a08163 100644 --- a/Lib/test/test_wave.py +++ b/Lib/test/test_wave.py @@ -1,6 +1,10 @@ from test.test_support import TESTFN, run_unittest import unittest from test import audiotests +from test import support +from audioop import byteswap +import io +import struct import sys import wave @@ -119,5 +123,66 @@ def test_unseekable_incompleted_write(self): def test_main(): run_unittest(WavePCM8Test, WavePCM16Test, WavePCM24Test, WavePCM32Test) + +class WaveLowLevelTest(unittest.TestCase): + + def test_read_no_chunks(self): + b = b'SPAM' + with self.assertRaises(EOFError): + wave.open(io.BytesIO(b)) + + def test_read_no_riff_chunk(self): + b = b'SPAM' + struct.pack(' Date: Tue, 9 Apr 2024 13:21:39 -0400 Subject: [PATCH 2/2] CVE-2017-18207 Fix up code for Python2 --- Lib/aifc.py | 28 ++++++++++++++++------------ Lib/test/test_aifc.py | 17 +++++++---------- Lib/test/test_sunau.py | 1 - Lib/test/test_wave.py | 1 - Lib/wave.py | 4 ++-- 5 files changed, 25 insertions(+), 26 deletions(-) diff --git a/Lib/aifc.py b/Lib/aifc.py index d0e5e02fa5ee30..880ad50a48da67 100644 --- a/Lib/aifc.py +++ b/Lib/aifc.py @@ -135,6 +135,7 @@ """ import struct +import warnings import __builtin__ __all__ = ["Error","open","openfp"] @@ -316,16 +317,16 @@ def initfp(self, file): except EOFError: break chunkname = chunk.getname() - if chunkname == 'COMM': + if chunkname == b'COMM': self._read_comm_chunk(chunk) self._comm_chunk_read = 1 - elif chunkname == 'SSND': + elif chunkname == b'SSND': self._ssnd_chunk = chunk dummy = chunk.read(8) self._ssnd_seek_needed = 0 - elif chunkname == 'FVER': + elif chunkname == b'FVER': self._version = _read_ulong(chunk) - elif chunkname == 'MARK': + elif chunkname == b'MARK': self._readmark(chunk) chunk.skip() if not self._comm_chunk_read or not self._ssnd_chunk: @@ -466,16 +467,17 @@ def _read_comm_chunk(self, chunk): self._sampwidth = (_read_short(chunk) + 7) // 8 self._framerate = int(_read_float(chunk)) if self._sampwidth <= 0: - raise Error('bad sample width') + raise Error, 'bad sample width' if self._nchannels <= 0: - raise Error('bad # of channels') + raise Error, 'bad # of channels' + self._framesize = self._nchannels * self._sampwidth if self._aifc: #DEBUG: SGI's soundeditor produces a bad size :-( kludge = 0 if chunk.chunksize == 18: kludge = 1 - print 'Warning: bad COMM chunk size' + warnings.warn("bad COMM chunk size") chunk.chunksize = 23 #DEBUG end self._comptype = chunk.read(4) @@ -539,11 +541,13 @@ def _readmark(self, chunk): # a position 0 and name '' self._markers.append((id, pos, name)) except EOFError: - print 'Warning: MARK chunk contains only', - print len(self._markers), - if len(self._markers) == 1: print 'marker', - else: print 'markers', - print 'instead of', nmarkers + warning_message = 'MARK chunk contains only ' + str(len(self._markers)) + if len(self._markers) == 1: + warning_message += ' marker ' + else: + warning_message += ' markers ' + warning_message += 'instead of ' + str(nmarkers) + warnings.warn(warning_message) class Aifc_write: # Variables used in this class: diff --git a/Lib/test/test_aifc.py b/Lib/test/test_aifc.py index 48c20c8b40d94a..f21798dfe4bc7c 100644 --- a/Lib/test/test_aifc.py +++ b/Lib/test/test_aifc.py @@ -7,6 +7,7 @@ import sys import struct import aifc +from test.test_support import check_warnings class AifcTest(audiotests.AudioWriteTests, @@ -237,7 +238,7 @@ def test_read_wrong_number_of_channels(self): 0x4000 | 12, 11025<<18, 0) b += b'NONE' + struct.pack('B', 14) + b'not compressed' + b'\x00' b += b'SSND' + struct.pack('>L', 8) + b'\x00' * 8 - with self.assertRaisesRegex(aifc.Error, 'bad # of channels'): + with self.assertRaisesRegexp(aifc.Error, 'bad # of channels'): aifc.open(io.BytesIO(b)) def test_read_wrong_sample_width(self): @@ -247,7 +248,7 @@ def test_read_wrong_sample_width(self): 0x4000 | 12, 11025<<18, 0) b += b'NONE' + struct.pack('B', 14) + b'not compressed' + b'\x00' b += b'SSND' + struct.pack('>L', 8) + b'\x00' * 8 - with self.assertRaisesRegex(aifc.Error, 'bad sample width'): + with self.assertRaisesRegexp(aifc.Error, 'bad sample width'): aifc.open(io.BytesIO(b)) def test_read_wrong_marks(self): @@ -256,11 +257,9 @@ def test_read_wrong_marks(self): 0x4000 | 12, 11025<<18, 0) b += b'SSND' + struct.pack('>L', 8) + b'\x00' * 8 b += b'MARK' + struct.pack('>LhB', 3, 1, 1) - with self.assertWarns(UserWarning) as cm: + with check_warnings(('MARK chunk contains only 0 markers instead of 1', UserWarning)): f = aifc.open(io.BytesIO(b)) - self.assertEqual(s.getvalue(), 'Warning: MARK chunk contains ' - 'only 0 markers instead of 1\n') - self.assertEqual(f.getmarkers(), None) + self.assertEqual(f.getmarkers(), None) def test_read_comm_kludge_compname_even(self): b = b'FORM' + struct.pack('>L', 4) + b'AIFC' @@ -268,9 +267,8 @@ def test_read_comm_kludge_compname_even(self): 0x4000 | 12, 11025<<18, 0) b += b'NONE' + struct.pack('B', 4) + b'even' + b'\x00' b += b'SSND' + struct.pack('>L', 8) + b'\x00' * 8 - with self.assertWarns(UserWarning) as cm: + with check_warnings(('bad COMM chunk size', UserWarning)): f = aifc.open(io.BytesIO(b)) - self.assertEqual(s.getvalue(), 'Warning: bad COMM chunk size\n') self.assertEqual(f.getcompname(), 'even') def test_read_comm_kludge_compname_odd(self): @@ -279,9 +277,8 @@ def test_read_comm_kludge_compname_odd(self): 0x4000 | 12, 11025<<18, 0) b += b'NONE' + struct.pack('B', 3) + b'odd' b += b'SSND' + struct.pack('>L', 8) + b'\x00' * 8 - with self.assertWarns(UserWarning) as cm: + with check_warnings(('bad COMM chunk size', UserWarning)): f = aifc.open(io.BytesIO(b)) - self.assertEqual(s.getvalue(), 'Warning: bad COMM chunk size\n') self.assertEqual(f.getcompname(), 'odd') def test_write_params_raises(self): diff --git a/Lib/test/test_sunau.py b/Lib/test/test_sunau.py index 332868c25ff00d..1550600495fa78 100644 --- a/Lib/test/test_sunau.py +++ b/Lib/test/test_sunau.py @@ -1,7 +1,6 @@ from test.test_support import TESTFN, run_unittest import unittest from test import audiotests -from audioop import byteswap import io import struct import sys diff --git a/Lib/test/test_wave.py b/Lib/test/test_wave.py index ab911d70a08163..9d01eacc539a43 100644 --- a/Lib/test/test_wave.py +++ b/Lib/test/test_wave.py @@ -2,7 +2,6 @@ import unittest from test import audiotests from test import support -from audioop import byteswap import io import struct import sys diff --git a/Lib/wave.py b/Lib/wave.py index 189c18e1747f5d..2d34ae756270db 100644 --- a/Lib/wave.py +++ b/Lib/wave.py @@ -269,12 +269,12 @@ def _read_fmt_chunk(self, chunk): try: wFormatTag, self._nchannels, self._framerate, dwAvgBytesPerSec, wBlockAlign = struct.unpack_from('