Skip to content

Commit 70ac26d

Browse files
committed
[CVE-2017-18207] add check for channels of wav file in sound related modules
Source is gh#python/cpython!5951 (released upstream in 3.7.0) Fixes: bsc#1083507
1 parent e763908 commit 70ac26d

File tree

7 files changed

+150
-7
lines changed

7 files changed

+150
-7
lines changed

Lib/aifc.py

+4
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,10 @@ def _read_comm_chunk(self, chunk):
467467
self._nframes = _read_long(chunk)
468468
self._sampwidth = (_read_short(chunk) + 7) // 8
469469
self._framerate = int(_read_float(chunk))
470+
if self._sampwidth <= 0:
471+
raise Error('bad sample width')
472+
if self._nchannels <= 0:
473+
raise Error('bad # of channels')
470474
self._framesize = self._nchannels * self._sampwidth
471475
if self._aifc:
472476
#DEBUG: SGI's soundeditor produces a bad size :-(

Lib/sunau.py

+2
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,8 @@ def initfp(self, file):
207207
raise Error('unknown encoding')
208208
self._framerate = int(_read_u32(file))
209209
self._nchannels = int(_read_u32(file))
210+
if not self._nchannels:
211+
raise Error('bad # of channels')
210212
self._framesize = self._framesize * self._nchannels
211213
if self._hdr_size > 24:
212214
self._info = file.read(self._hdr_size - 24)

Lib/test/test_aifc.py

+30-5
Original file line numberDiff line numberDiff line change
@@ -265,21 +265,44 @@ def test_read_no_comm_chunk(self):
265265

266266
def test_read_no_ssnd_chunk(self):
267267
b = b'FORM' + struct.pack('>L', 4) + b'AIFC'
268-
b += b'COMM' + struct.pack('>LhlhhLL', 38, 0, 0, 0, 0, 0, 0)
268+
b += b'COMM' + struct.pack('>LhlhhLL', 38, 1, 0, 8,
269+
0x4000 | 12, 11025<<18, 0)
269270
b += b'NONE' + struct.pack('B', 14) + b'not compressed' + b'\x00'
270271
with self.assertRaisesRegex(aifc.Error, 'COMM chunk and/or SSND chunk'
271272
' missing'):
272273
aifc.open(io.BytesIO(b))
273274

274275
def test_read_wrong_compression_type(self):
275276
b = b'FORM' + struct.pack('>L', 4) + b'AIFC'
276-
b += b'COMM' + struct.pack('>LhlhhLL', 23, 0, 0, 0, 0, 0, 0)
277+
b += b'COMM' + struct.pack('>LhlhhLL', 23, 1, 0, 8,
278+
0x4000 | 12, 11025<<18, 0)
277279
b += b'WRNG' + struct.pack('B', 0)
278280
self.assertRaises(aifc.Error, aifc.open, io.BytesIO(b))
279281

282+
def test_read_wrong_number_of_channels(self):
283+
for nchannels in 0, -1:
284+
b = b'FORM' + struct.pack('>L', 4) + b'AIFC'
285+
b += b'COMM' + struct.pack('>LhlhhLL', 38, nchannels, 0, 8,
286+
0x4000 | 12, 11025<<18, 0)
287+
b += b'NONE' + struct.pack('B', 14) + b'not compressed' + b'\x00'
288+
b += b'SSND' + struct.pack('>L', 8) + b'\x00' * 8
289+
with self.assertRaisesRegex(aifc.Error, 'bad # of channels'):
290+
aifc.open(io.BytesIO(b))
291+
292+
def test_read_wrong_sample_width(self):
293+
for sampwidth in 0, -1:
294+
b = b'FORM' + struct.pack('>L', 4) + b'AIFC'
295+
b += b'COMM' + struct.pack('>LhlhhLL', 38, 1, 0, sampwidth,
296+
0x4000 | 12, 11025<<18, 0)
297+
b += b'NONE' + struct.pack('B', 14) + b'not compressed' + b'\x00'
298+
b += b'SSND' + struct.pack('>L', 8) + b'\x00' * 8
299+
with self.assertRaisesRegex(aifc.Error, 'bad sample width'):
300+
aifc.open(io.BytesIO(b))
301+
280302
def test_read_wrong_marks(self):
281303
b = b'FORM' + struct.pack('>L', 4) + b'AIFF'
282-
b += b'COMM' + struct.pack('>LhlhhLL', 18, 0, 0, 0, 0, 0, 0)
304+
b += b'COMM' + struct.pack('>LhlhhLL', 18, 1, 0, 8,
305+
0x4000 | 12, 11025<<18, 0)
283306
b += b'SSND' + struct.pack('>L', 8) + b'\x00' * 8
284307
b += b'MARK' + struct.pack('>LhB', 3, 1, 1)
285308
with self.assertWarns(UserWarning) as cm:
@@ -290,7 +313,8 @@ def test_read_wrong_marks(self):
290313

291314
def test_read_comm_kludge_compname_even(self):
292315
b = b'FORM' + struct.pack('>L', 4) + b'AIFC'
293-
b += b'COMM' + struct.pack('>LhlhhLL', 18, 0, 0, 0, 0, 0, 0)
316+
b += b'COMM' + struct.pack('>LhlhhLL', 18, 1, 0, 8,
317+
0x4000 | 12, 11025<<18, 0)
294318
b += b'NONE' + struct.pack('B', 4) + b'even' + b'\x00'
295319
b += b'SSND' + struct.pack('>L', 8) + b'\x00' * 8
296320
with self.assertWarns(UserWarning) as cm:
@@ -300,7 +324,8 @@ def test_read_comm_kludge_compname_even(self):
300324

301325
def test_read_comm_kludge_compname_odd(self):
302326
b = b'FORM' + struct.pack('>L', 4) + b'AIFC'
303-
b += b'COMM' + struct.pack('>LhlhhLL', 18, 0, 0, 0, 0, 0, 0)
327+
b += b'COMM' + struct.pack('>LhlhhLL', 18, 1, 0, 8,
328+
0x4000 | 12, 11025<<18, 0)
304329
b += b'NONE' + struct.pack('B', 3) + b'odd'
305330
b += b'SSND' + struct.pack('>L', 8) + b'\x00' * 8
306331
with self.assertWarns(UserWarning) as cm:

Lib/test/test_sunau.py

+37
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import unittest
22
from test import audiotests
33
from audioop import byteswap
4+
import io
5+
import struct
46
import sys
57
import sunau
68

@@ -117,5 +119,40 @@ class SunauULAWTest(SunauTest, unittest.TestCase):
117119
frames = byteswap(frames, 2)
118120

119121

122+
class SunauLowLevelTest(unittest.TestCase):
123+
124+
def test_read_bad_magic_number(self):
125+
b = b'SPA'
126+
with self.assertRaises(EOFError):
127+
sunau.open(io.BytesIO(b))
128+
b = b'SPAM'
129+
with self.assertRaisesRegex(sunau.Error, 'bad magic number'):
130+
sunau.open(io.BytesIO(b))
131+
132+
def test_read_too_small_header(self):
133+
b = struct.pack('>LLLLL', sunau.AUDIO_FILE_MAGIC, 20, 0,
134+
sunau.AUDIO_FILE_ENCODING_LINEAR_8, 11025)
135+
with self.assertRaisesRegex(sunau.Error, 'header size too small'):
136+
sunau.open(io.BytesIO(b))
137+
138+
def test_read_too_large_header(self):
139+
b = struct.pack('>LLLLLL', sunau.AUDIO_FILE_MAGIC, 124, 0,
140+
sunau.AUDIO_FILE_ENCODING_LINEAR_8, 11025, 1)
141+
b += b'\0' * 100
142+
with self.assertRaisesRegex(sunau.Error, 'header size ridiculously large'):
143+
sunau.open(io.BytesIO(b))
144+
145+
def test_read_wrong_encoding(self):
146+
b = struct.pack('>LLLLLL', sunau.AUDIO_FILE_MAGIC, 24, 0, 0, 11025, 1)
147+
with self.assertRaisesRegex(sunau.Error, r'encoding not \(yet\) supported'):
148+
sunau.open(io.BytesIO(b))
149+
150+
def test_read_wrong_number_of_channels(self):
151+
b = struct.pack('>LLLLLL', sunau.AUDIO_FILE_MAGIC, 24, 0,
152+
sunau.AUDIO_FILE_ENCODING_LINEAR_8, 11025, 0)
153+
with self.assertRaisesRegex(sunau.Error, 'bad # of channels'):
154+
sunau.open(io.BytesIO(b))
155+
156+
120157
if __name__ == "__main__":
121158
unittest.main()

Lib/test/test_wave.py

+62
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
from test import audiotests
33
from test import support
44
from audioop import byteswap
5+
import io
6+
import struct
57
import sys
68
import wave
79

@@ -109,5 +111,65 @@ def test__all__(self):
109111
support.check__all__(self, wave, blacklist=blacklist)
110112

111113

114+
class WaveLowLevelTest(unittest.TestCase):
115+
116+
def test_read_no_chunks(self):
117+
b = b'SPAM'
118+
with self.assertRaises(EOFError):
119+
wave.open(io.BytesIO(b))
120+
121+
def test_read_no_riff_chunk(self):
122+
b = b'SPAM' + struct.pack('<L', 0)
123+
with self.assertRaisesRegex(wave.Error,
124+
'file does not start with RIFF id'):
125+
wave.open(io.BytesIO(b))
126+
127+
def test_read_not_wave(self):
128+
b = b'RIFF' + struct.pack('<L', 4) + b'SPAM'
129+
with self.assertRaisesRegex(wave.Error,
130+
'not a WAVE file'):
131+
wave.open(io.BytesIO(b))
132+
133+
def test_read_no_fmt_no_data_chunk(self):
134+
b = b'RIFF' + struct.pack('<L', 4) + b'WAVE'
135+
with self.assertRaisesRegex(wave.Error,
136+
'fmt chunk and/or data chunk missing'):
137+
wave.open(io.BytesIO(b))
138+
139+
def test_read_no_data_chunk(self):
140+
b = b'RIFF' + struct.pack('<L', 28) + b'WAVE'
141+
b += b'fmt ' + struct.pack('<LHHLLHH', 16, 1, 1, 11025, 11025, 1, 8)
142+
with self.assertRaisesRegex(wave.Error,
143+
'fmt chunk and/or data chunk missing'):
144+
wave.open(io.BytesIO(b))
145+
146+
def test_read_no_fmt_chunk(self):
147+
b = b'RIFF' + struct.pack('<L', 12) + b'WAVE'
148+
b += b'data' + struct.pack('<L', 0)
149+
with self.assertRaisesRegex(wave.Error, 'data chunk before fmt chunk'):
150+
wave.open(io.BytesIO(b))
151+
152+
def test_read_wrong_form(self):
153+
b = b'RIFF' + struct.pack('<L', 36) + b'WAVE'
154+
b += b'fmt ' + struct.pack('<LHHLLHH', 16, 2, 1, 11025, 11025, 1, 1)
155+
b += b'data' + struct.pack('<L', 0)
156+
with self.assertRaisesRegex(wave.Error, 'unknown format: 2'):
157+
wave.open(io.BytesIO(b))
158+
159+
def test_read_wrong_number_of_channels(self):
160+
b = b'RIFF' + struct.pack('<L', 36) + b'WAVE'
161+
b += b'fmt ' + struct.pack('<LHHLLHH', 16, 1, 0, 11025, 11025, 1, 8)
162+
b += b'data' + struct.pack('<L', 0)
163+
with self.assertRaisesRegex(wave.Error, 'bad # of channels'):
164+
wave.open(io.BytesIO(b))
165+
166+
def test_read_wrong_sample_width(self):
167+
b = b'RIFF' + struct.pack('<L', 36) + b'WAVE'
168+
b += b'fmt ' + struct.pack('<LHHLLHH', 16, 1, 1, 11025, 11025, 1, 0)
169+
b += b'data' + struct.pack('<L', 0)
170+
with self.assertRaisesRegex(wave.Error, 'bad sample width'):
171+
wave.open(io.BytesIO(b))
172+
173+
112174
if __name__ == '__main__':
113175
unittest.main()

Lib/wave.py

+12-2
Original file line numberDiff line numberDiff line change
@@ -252,12 +252,22 @@ def readframes(self, nframes):
252252
#
253253

254254
def _read_fmt_chunk(self, chunk):
255-
wFormatTag, self._nchannels, self._framerate, dwAvgBytesPerSec, wBlockAlign = struct.unpack_from('<HHLLH', chunk.read(14))
255+
try:
256+
wFormatTag, self._nchannels, self._framerate, dwAvgBytesPerSec, wBlockAlign = struct.unpack_from('<HHLLH', chunk.read(14))
257+
except struct.error:
258+
raise EOFError from None
256259
if wFormatTag == WAVE_FORMAT_PCM:
257-
sampwidth = struct.unpack_from('<H', chunk.read(2))[0]
260+
try:
261+
sampwidth = struct.unpack_from('<H', chunk.read(2))[0]
262+
except struct.error:
263+
raise EOFError from None
258264
self._sampwidth = (sampwidth + 7) // 8
265+
if not self._sampwidth:
266+
raise Error('bad sample width')
259267
else:
260268
raise Error('unknown format: %r' % (wFormatTag,))
269+
if not self._nchannels:
270+
raise Error('bad # of channels')
261271
self._framesize = self._nchannels * self._sampwidth
262272
self._comptype = 'NONE'
263273
self._compname = 'not compressed'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Improved exceptions raised for invalid number of channels and sample width
2+
when read an audio file in modules :mod:`aifc`, :mod:`wave` and
3+
:mod:`sunau`.

0 commit comments

Comments
 (0)