Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 30 additions & 4 deletions PIL/PngImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,19 @@

_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):
dobj = zlib.decompressobj()
plaintext = dobj.decompress(s, MAX_TEXT_CHUNK)
if dobj.unconsumed_tail:
raise ValueError("Decompressed Data Too Large")
return plaintext


# --------------------------------------------------------------------
# Support classes. Suitable for PNG and related formats like MNG etc.
Expand Down Expand Up @@ -260,6 +273,14 @@ def __init__(self, fp):
self.im_tile = 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):

# ICC profile
Expand All @@ -278,7 +299,7 @@ def chunk_iCCP(self, pos, length):
raise SyntaxError("Unknown compression method %s in iCCP chunk" %
comp_method)
try:
icc_profile = zlib.decompress(s[i+2:])
icc_profile = _safe_zlib_decompress(s[i+2:])
except zlib.error:
icc_profile = None # FIXME
self.im_info["icc_profile"] = icc_profile
Expand Down Expand Up @@ -372,6 +393,8 @@ def chunk_tEXt(self, pos, length):
v = v.decode('latin-1', 'replace')

self.im_info[k] = self.im_text[k] = v
self.check_text_memory(len(v))

return s

def chunk_zTXt(self, pos, length):
Expand All @@ -391,7 +414,7 @@ def chunk_zTXt(self, pos, length):
raise SyntaxError("Unknown compression method %s in zTXt chunk" %
comp_method)
try:
v = zlib.decompress(v[1:])
v = _safe_zlib_decompress(v[1:])
except zlib.error:
v = b""

Expand All @@ -401,6 +424,8 @@ def chunk_zTXt(self, pos, length):
v = v.decode('latin-1', 'replace')

self.im_info[k] = self.im_text[k] = v
self.check_text_memory(len(v))

return s

def chunk_iTXt(self, pos, length):
Expand All @@ -421,7 +446,7 @@ def chunk_iTXt(self, pos, length):
if cf != 0:
if cm == 0:
try:
v = zlib.decompress(v)
v = _safe_zlib_decompress(v)
except zlib.error:
return s
else:
Expand All @@ -436,7 +461,8 @@ def chunk_iTXt(self, pos, length):
return s

self.im_info[k] = self.im_text[k] = iTXt(v, lang, tk)

self.check_text_memory(len(v))

return s


Expand Down
47 changes: 47 additions & 0 deletions Tests/check_png_dos.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
from helper import unittest, PillowTestCase
from PIL import Image, PngImagePlugin
from io import BytesIO
import zlib

TEST_FILE = "Tests/images/png_decompression_dos.png"

class TestPngDos(PillowTestCase):
def test_dos_text(self):

try:
im = Image.open(TEST_FILE)
im.load()
except ValueError as msg:
self.assertTrue(msg, "Decompressed Data Too Large")
return

for s in im.text.values():
self.assertLess(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.assertIn("Too much memory", msg)
return

total_len = 0
for txt in im2.text.values():
total_len += len(txt)
self.assertLess(total_len, 64*1024*1024, "Total text chunks greater than 64M")

if __name__ == '__main__':
unittest.main()
Binary file added Tests/images/png_decompression_dos.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion Tests/test_file_png.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ def test_bad_itxt(self):

im = load(HEAD + chunk(b'iTXt', b'spam\0\1\0en\0Spam\0' +
zlib.compress(b"egg")[:1]) + TAIL)
self.assertEqual(im.info, {})
self.assertEqual(im.info, {'spam':''})

im = load(HEAD + chunk(b'iTXt', b'spam\0\1\1en\0Spam\0' +
zlib.compress(b"egg")) + TAIL)
Expand Down
7 changes: 6 additions & 1 deletion docs/handbook/image-file-formats.rst
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,12 @@ The :py:meth:`~PIL.Image.Image.open` method sets the following
transparent palette image.

``Open`` also sets ``Image.text`` to a list of the values of the
``tEXt``, ``zTXt``, and ``iTXt`` chunks of the PNG image.
``tEXt``, ``zTXt``, and ``iTXt`` chunks of the PNG image. Individual
compressed chunks are limited to a decompressed size of
``PngImagePlugin.MAX_TEXT_CHUNK``, by default 1MB, to prevent
decompression bombs. Additionally, the total size of all of the text
chunks is limited to ``PngImagePlugin.MAX_TEXT_MEMORY``, defaulting to
64MB.

The :py:meth:`~PIL.Image.Image.save` method supports the following options:

Expand Down