[CVE-2017-18207]Improve exceptions in aifc, wave and sunau. 41/256741/1 submit/tizen_6.0_base/20210412.113806
authorJinWang An <jinwang.an@samsung.com>
Mon, 12 Apr 2021 07:10:22 +0000 (16:10 +0900)
committerJinWang An <jinwang.an@samsung.com>
Mon, 12 Apr 2021 07:10:22 +0000 (16:10 +0900)
** DISPUTED ** The Wave_read._read_fmt_chunk function
in Lib/wave.py in Python through 3.6.4 does not ensure
a nonzero channel value, which allows attackers to cause
a denial of service (divide-by-zero and exception) via
a crafted wav format audio file. NOTE: the vendor disputes
this issue because Python applications "need
to be prepared to handle a wide variety of exceptions."

Change-Id: Ia7b958c4d95596552802eda52f257fcc3fcc7469
Signed-off-by: JinWang An <jinwang.an@samsung.com>
Lib/aifc.py
Lib/sunau.py
Lib/test/test_aifc.py
Lib/test/test_sunau.py
Lib/wave.py

index 981f801..d0e5e02 100644 (file)
@@ -465,6 +465,10 @@ class Aifc_read:
         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 :-(
index b53044d..b5d83ea 100644 (file)
@@ -194,6 +194,8 @@ class Au_read:
             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)
index 92bbe7b..e8e2830 100644 (file)
@@ -216,7 +216,8 @@ class AIFCLowLevelTest(unittest.TestCase):
 
     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'):
@@ -224,13 +225,35 @@ class AIFCLowLevelTest(unittest.TestCase):
 
     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 += 'COMM' + struct.pack('>LhlhhLL', 23, 1, 0, 8,
+                                   0x4000 | 12, 11025<<18, 0)
         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 += 'COMM' + struct.pack('>LhlhhLL', 18, 1, 0, 8,
+                                   0x4000 | 12, 11025<<18, 0)
         b += 'SSND' + struct.pack('>L', 8) + '\x00' * 8
         b += 'MARK' + struct.pack('>LhB', 3, 1, 1)
         with captured_stdout() as s:
@@ -241,7 +264,8 @@ class AIFCLowLevelTest(unittest.TestCase):
 
     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 += 'COMM' + struct.pack('>LhlhhLL', 18, 1, 0, 8,
+                                   0x4000 | 12, 11025<<18, 0)
         b += 'NONE' + struct.pack('B', 4) + 'even' + '\x00'
         b += 'SSND' + struct.pack('>L', 8) + '\x00' * 8
         with captured_stdout() as s:
@@ -251,7 +275,8 @@ class AIFCLowLevelTest(unittest.TestCase):
 
     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 += 'COMM' + struct.pack('>LhlhhLL', 18, 1, 0, 8,
+                                   0x4000 | 12, 11025<<18, 0)
         b += 'NONE' + struct.pack('B', 3) + 'odd'
         b += 'SSND' + struct.pack('>L', 8) + '\x00' * 8
         with captured_stdout() as s:
index f682868..146c8d0 100644 (file)
@@ -1,6 +1,8 @@
 from test.test_support import TESTFN, run_unittest
 import unittest
 from test import audiotests
+import io
+import struct
 import sys
 import sunau
 
@@ -96,5 +98,64 @@ def test_main():
     run_unittest(SunauPCM8Test, SunauPCM16Test, SunauPCM16Test,
                  SunauPCM32Test, SunauULAWTest)
 
+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('<L', 0)
+        with self.assertRaisesRegex(wave.Error,
+                                    'file does not start with RIFF id'):
+            wave.open(io.BytesIO(b))
+
+    def test_read_not_wave(self):
+        b = b'RIFF' + struct.pack('<L', 4) + b'SPAM'
+        with self.assertRaisesRegex(wave.Error,
+                                    'not a WAVE file'):
+            wave.open(io.BytesIO(b))
+
+    def test_read_no_fmt_no_data_chunk(self):
+        b = b'RIFF' + struct.pack('<L', 4) + b'WAVE'
+        with self.assertRaisesRegex(wave.Error,
+                                    'fmt chunk and/or data chunk missing'):
+            wave.open(io.BytesIO(b))
+
+    def test_read_no_data_chunk(self):
+        b = b'RIFF' + struct.pack('<L', 28) + b'WAVE'
+        b += b'fmt ' + struct.pack('<LHHLLHH', 16, 1, 1, 11025, 11025, 1, 8)
+        with self.assertRaisesRegex(wave.Error,
+                                    'fmt chunk and/or data chunk missing'):
+            wave.open(io.BytesIO(b))
+
+    def test_read_no_fmt_chunk(self):
+        b = b'RIFF' + struct.pack('<L', 12) + b'WAVE'
+        b += b'data' + struct.pack('<L', 0)
+        with self.assertRaisesRegex(wave.Error, 'data chunk before fmt chunk'):
+            wave.open(io.BytesIO(b))
+
+    def test_read_wrong_form(self):
+        b = b'RIFF' + struct.pack('<L', 36) + b'WAVE'
+        b += b'fmt ' + struct.pack('<LHHLLHH', 16, 2, 1, 11025, 11025, 1, 1)
+        b += b'data' + struct.pack('<L', 0)
+        with self.assertRaisesRegex(wave.Error, 'unknown format: 2'):
+            wave.open(io.BytesIO(b))
+
+    def test_read_wrong_number_of_channels(self):
+        b = b'RIFF' + struct.pack('<L', 36) + b'WAVE'
+        b += b'fmt ' + struct.pack('<LHHLLHH', 16, 1, 0, 11025, 11025, 1, 8)
+        b += b'data' + struct.pack('<L', 0)
+        with self.assertRaisesRegex(wave.Error, 'bad # of channels'):
+            wave.open(io.BytesIO(b))
+
+    def test_read_wrong_sample_width(self):
+        b = b'RIFF' + struct.pack('<L', 36) + b'WAVE'
+        b += b'fmt ' + struct.pack('<LHHLLHH', 16, 1, 1, 11025, 11025, 1, 0)
+        b += b'data' + struct.pack('<L', 0)
+        with self.assertRaisesRegex(wave.Error, 'bad sample width'):
+            wave.open(io.BytesIO(b))
+
 if __name__ == "__main__":
     test_main()
index 28acaa6..830c0e7 100644 (file)
@@ -266,12 +266,22 @@ class Wave_read:
     #
 
     def _read_fmt_chunk(self, chunk):
-        wFormatTag, self._nchannels, self._framerate, dwAvgBytesPerSec, wBlockAlign = struct.unpack('<HHLLH', chunk.read(14))
+        try:
+            wFormatTag, self._nchannels, self._framerate, dwAvgBytesPerSec, wBlockAlign = struct.unpack('<HHLLH', chunk.read(14))
+        except struct.error:
+            raise (EOFError);
         if wFormatTag == WAVE_FORMAT_PCM:
-            sampwidth = struct.unpack('<H', chunk.read(2))[0]
+            try:
+                sampwidth = struct.unpack('<H', chunk.read(2))[0]
+            except struct.error:
+                raise (EOFError);
             self._sampwidth = (sampwidth + 7) // 8
+            if not self._sampwidth:
+                raise Error('bad sample width')
         else:
             raise Error, 'unknown format: %r' % (wFormatTag,)
+        if not self._nchannels:
+            raise Error('bad # of channels')
         self._framesize = self._nchannels * self._sampwidth
         self._comptype = 'NONE'
         self._compname = 'not compressed'