Limit total text chunk size to 64k

This commit is contained in:
wiredfool 2014-12-29 17:10:27 -08:00
parent b73c4b9e8b
commit 0b75526ffe
2 changed files with 48 additions and 5 deletions

View File

@ -72,9 +72,15 @@ _MODES = {
_simple_palette = re.compile(b'^\xff+\x00\xff*$') _simple_palette = re.compile(b'^\xff+\x00\xff*$')
# Maximum decompressed size for a iTXt or zTXt chunk.
# Eliminates decompression bombs where compressed chunks can expand 1000x
MAX_TEXT_CHUNK = ImageFile.SAFEBLOCK
# Set the maximum total text chunk size.
MAX_TEXT_MEMORY = 64 * MAX_TEXT_CHUNK
def _safe_zlib_decompress(s): def _safe_zlib_decompress(s):
dobj = zlib.decompressobj() dobj = zlib.decompressobj()
plaintext = dobj.decompress(s, ImageFile.SAFEBLOCK) plaintext = dobj.decompress(s, MAX_TEXT_CHUNK)
if dobj.unconsumed_tail: if dobj.unconsumed_tail:
raise ValueError("Decompressed Data Too Large") raise ValueError("Decompressed Data Too Large")
return plaintext return plaintext
@ -267,6 +273,14 @@ class PngStream(ChunkStream):
self.im_tile = None self.im_tile = None
self.im_palette = None self.im_palette = None
self.text_memory = 0
def check_text_memory(self, chunklen):
self.text_memory += chunklen
if self.text_memory > MAX_TEXT_MEMORY:
raise ValueError("Too much memory used in text chunks: %s>MAX_TEXT_MEMORY" %
self.text_memory)
def chunk_iCCP(self, pos, length): def chunk_iCCP(self, pos, length):
# ICC profile # ICC profile
@ -379,6 +393,8 @@ class PngStream(ChunkStream):
v = v.decode('latin-1', 'replace') v = v.decode('latin-1', 'replace')
self.im_info[k] = self.im_text[k] = v self.im_info[k] = self.im_text[k] = v
self.check_text_memory(len(v))
return s return s
def chunk_zTXt(self, pos, length): def chunk_zTXt(self, pos, length):
@ -408,6 +424,8 @@ class PngStream(ChunkStream):
v = v.decode('latin-1', 'replace') v = v.decode('latin-1', 'replace')
self.im_info[k] = self.im_text[k] = v self.im_info[k] = self.im_text[k] = v
self.check_text_memory(len(v))
return s return s
def chunk_iTXt(self, pos, length): def chunk_iTXt(self, pos, length):
@ -443,7 +461,8 @@ class PngStream(ChunkStream):
return s return s
self.im_info[k] = self.im_text[k] = iTXt(v, lang, tk) self.im_info[k] = self.im_text[k] = iTXt(v, lang, tk)
self.check_text_memory(len(v))
return s return s

View File

@ -1,13 +1,12 @@
from helper import unittest, PillowTestCase from helper import unittest, PillowTestCase
import sys import sys
from PIL import Image from PIL import Image, PngImagePlugin
from io import BytesIO from io import BytesIO
import zlib
test_file = "Tests/images/png_decompression_dos.png" test_file = "Tests/images/png_decompression_dos.png"
@unittest.skipIf(sys.platform.startswith('win32'), "requires Unix or MacOS")
class TestPngDos(PillowTestCase): class TestPngDos(PillowTestCase):
def test_dos_text(self): def test_dos_text(self):
try: try:
@ -20,5 +19,30 @@ class TestPngDos(PillowTestCase):
for s in im.text.values(): for s in im.text.values():
self.assert_(len(s) < 1024*1024, "Text chunk larger than 1M") self.assert_(len(s) < 1024*1024, "Text chunk larger than 1M")
def test_dos_total_memory(self):
im = Image.new('L',(1,1))
compressed_data = zlib.compress('a'*1024*1023)
info = PngImagePlugin.PngInfo()
for x in range(64):
info.add_text('t%s'%x, compressed_data, 1)
info.add_itxt('i%s'%x, compressed_data, zip=True)
b = BytesIO()
im.save(b, 'PNG', pnginfo=info)
b.seek(0)
try:
im2 = Image.open(b)
except ValueError as msg:
self.assert_("Too much memory" in msg)
return
total_len = 0
for txt in im2.text.values():
total_len += len(txt)
self.assert_(total_len < 64*1024*1024)
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()