Merge pull request #1062 from wiredfool/png-dos-2.6.1

Png text decompression dos fix, for 2.6.x
This commit is contained in:
wiredfool 2014-12-31 20:03:05 -08:00
commit 9b66864e07
9 changed files with 95 additions and 12 deletions

View File

@ -1,6 +1,12 @@
Changelog (Pillow)
==================
2.6.2 (2015-01-01)
------------------
- Fix potential PNG decompression DOS #1060
[wiredfool]
2.6.1 (2014-10-13)
------------------

View File

@ -72,6 +72,19 @@ _MODES = {
_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.
@ -184,7 +197,6 @@ class PngInfo:
tkey = tkey.encode("utf-8", "strict")
if zip:
import zlib
self.add(b"iTXt", key + b"\0\x01\0" + lang + b"\0" + tkey + b"\0" +
zlib.compress(value))
else:
@ -206,7 +218,6 @@ class PngInfo:
key = key.encode('latin-1', 'strict')
if zip:
import zlib
self.add(b"zTXt", key + b"\0\0" + zlib.compress(value))
else:
self.add(b"tEXt", key + b"\0" + value)
@ -229,6 +240,14 @@ class PngStream(ChunkStream):
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
@ -247,7 +266,7 @@ class PngStream(ChunkStream):
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
@ -341,6 +360,8 @@ class PngStream(ChunkStream):
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):
@ -359,9 +380,8 @@ class PngStream(ChunkStream):
if comp_method != 0:
raise SyntaxError("Unknown compression method %s in zTXt chunk" %
comp_method)
import zlib
try:
v = zlib.decompress(v[1:])
v = _safe_zlib_decompress(v[1:])
except zlib.error:
v = b""
@ -371,6 +391,8 @@ class PngStream(ChunkStream):
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):
@ -390,9 +412,8 @@ class PngStream(ChunkStream):
return s
if cf != 0:
if cm == 0:
import zlib
try:
v = zlib.decompress(v)
v = _safe_zlib_decompress(v)
except zlib.error:
return s
else:
@ -407,7 +428,8 @@ class PngStream(ChunkStream):
return s
self.im_info[k] = self.im_text[k] = iTXt(v, lang, tk)
self.check_text_memory(len(v))
return s

View File

@ -12,7 +12,7 @@
# ;-)
VERSION = '1.1.7' # PIL version
PILLOW_VERSION = '2.6.1' # Pillow
PILLOW_VERSION = '2.6.2' # Pillow
_plugins = ['BmpImagePlugin',
'BufrStubImagePlugin',

47
Tests/check_png_dos.py Normal file
View File

@ -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 not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

@ -153,7 +153,7 @@ class TestFilePng(PillowTestCase):
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)

View File

@ -71,7 +71,7 @@
* See the README file for information on usage and redistribution.
*/
#define PILLOW_VERSION "2.6.1"
#define PILLOW_VERSION "2.6.2"
#include "Python.h"

View File

@ -332,6 +332,14 @@ The :py:meth:`~PIL.Image.Image.open` method sets the following
Transparency color index. This key is omitted if the image is not a
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. 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:
**optimize**

View File

@ -90,7 +90,7 @@ except (ImportError, OSError):
NAME = 'Pillow'
PILLOW_VERSION = '2.6.1'
PILLOW_VERSION = '2.6.2'
TCL_ROOT = None
JPEG_ROOT = None
JPEG2K_ROOT = None