From 97e111d079c23d3d287ceb627287fe3decdded90 Mon Sep 17 00:00:00 2001 From: Nikita Uvarov Date: Thu, 7 Aug 2014 16:23:08 +0300 Subject: [PATCH 01/15] Fixed wrong mode of gif image. In case of L mode and small image. --- PIL/GifImagePlugin.py | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/PIL/GifImagePlugin.py b/PIL/GifImagePlugin.py index 4107c6ba3..640af9efc 100644 --- a/PIL/GifImagePlugin.py +++ b/PIL/GifImagePlugin.py @@ -268,10 +268,9 @@ def _save(im, fp, filename): except IOError: pass # write uncompressed file - try: - rawmode = RAWMODE[im.mode] + if im.mode in RAWMODE: imOut = im - except KeyError: + else: # convert on the fly (EXPERIMENTAL -- I'm not sure PIL # should automatically convert images on save...) if Image.getmodebase(im.mode) == "RGB": @@ -279,10 +278,8 @@ def _save(im, fp, filename): if im.palette: palette_size = len(im.palette.getdata()[1]) // 3 imOut = im.convert("P", palette=1, colors=palette_size) - rawmode = "P" else: imOut = im.convert("L") - rawmode = "L" # header try: @@ -290,12 +287,6 @@ def _save(im, fp, filename): except KeyError: palette = None im.encoderinfo["optimize"] = im.encoderinfo.get("optimize", True) - if im.encoderinfo["optimize"]: - # When the mode is L, and we optimize, we end up with - # im.mode == P and rawmode = L, which fails. - # If we're optimizing the palette, we're going to be - # in a rawmode of P anyway. - rawmode = 'P' header, usedPaletteColors = getheader(imOut, palette, im.encoderinfo) for s in header: @@ -352,7 +343,7 @@ def _save(im, fp, filename): o8(8)) # bits imOut.encoderconfig = (8, interlace) - ImageFile._save(imOut, fp, [("gif", (0,0)+im.size, 0, rawmode)]) + ImageFile._save(imOut, fp, [("gif", (0,0)+im.size, 0, RAWMODE[imOut.mode])]) fp.write(b"\0") # end of image data From 616d8cd4c00757be02ed93c700cf6b8926673e5f Mon Sep 17 00:00:00 2001 From: Hugo Date: Sat, 16 Aug 2014 18:12:31 +0300 Subject: [PATCH 02/15] Pyroma installation is slow on Py3, so just do it for Py2 --- .travis.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 62fc372a5..1b046d4e6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,7 +20,10 @@ install: - "pip install cffi" - "pip install coveralls nose coveralls-merge" - "gem install coveralls-lcov" - - travis_retry pip install pyroma + + # Pyroma installation is slow on Py3, so just do it for Py2. + - if [ ${TRAVIS_PYTHON_VERSION:0:1} == "2" ]; then travis_retry pip install pyroma; fi + - if [ "$TRAVIS_PYTHON_VERSION" == "2.6" ]; then pip install unittest2; fi # webp From 9604cf814bdb210a1ed2e684e2910875a40df6c7 Mon Sep 17 00:00:00 2001 From: Nikita Uvarov Date: Tue, 19 Aug 2014 12:24:44 +0300 Subject: [PATCH 03/15] Added test case for gif image (mode L): optimization turned on, but not needed. --- Tests/test_file_gif.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index 93b826fd6..0111d3965 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -35,6 +35,14 @@ class TestFileGif(PillowTestCase): self.assertEqual(test(0), 800) self.assertEqual(test(1), 38) + def test_optimize_full_L(self): + from io import BytesIO + + im = Image.frombytes("L", (16, 16), bytes(range(256))) + file = BytesIO() + im.save(file, "GIF", optimize=optimize) + self.assertEqual(im.mode, "L") + def test_roundtrip(self): out = self.tempfile('temp.gif') im = lena() From 78081a24989469ef43d3b5d31265ab0c73f80d08 Mon Sep 17 00:00:00 2001 From: Nikita Uvarov Date: Tue, 19 Aug 2014 12:32:52 +0300 Subject: [PATCH 04/15] Fixed test_optimize_full_l gif file test case. --- Tests/test_file_gif.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index 0111d3965..12e12b2f3 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -35,12 +35,12 @@ class TestFileGif(PillowTestCase): self.assertEqual(test(0), 800) self.assertEqual(test(1), 38) - def test_optimize_full_L(self): + def test_optimize_full_l(self): from io import BytesIO im = Image.frombytes("L", (16, 16), bytes(range(256))) file = BytesIO() - im.save(file, "GIF", optimize=optimize) + im.save(file, "GIF", optimize=True) self.assertEqual(im.mode, "L") def test_roundtrip(self): From 778768c9bc333ecba88b1080512e2b384c6f5b3c Mon Sep 17 00:00:00 2001 From: Nikita Uvarov Date: Tue, 19 Aug 2014 15:00:15 +0300 Subject: [PATCH 05/15] Fixed test_optimize_full_l test case for python2. --- Tests/test_file_gif.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index 12e12b2f3..bd4a6e76c 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -38,7 +38,7 @@ class TestFileGif(PillowTestCase): def test_optimize_full_l(self): from io import BytesIO - im = Image.frombytes("L", (16, 16), bytes(range(256))) + im = Image.frombytes("L", (16, 16), bytes(bytearray(range(256)))) file = BytesIO() im.save(file, "GIF", optimize=True) self.assertEqual(im.mode, "L") From 1bd4919a359e87e82a92dc02c1852e6c3d6df036 Mon Sep 17 00:00:00 2001 From: Alexey Buzanov Date: Tue, 19 Aug 2014 17:53:51 +0400 Subject: [PATCH 06/15] Fix tga files with image ID field --- PIL/TgaImagePlugin.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/PIL/TgaImagePlugin.py b/PIL/TgaImagePlugin.py index 55790db08..049fb2dbb 100644 --- a/PIL/TgaImagePlugin.py +++ b/PIL/TgaImagePlugin.py @@ -19,6 +19,7 @@ __version__ = "0.3" +import os from PIL import Image, ImageFile, ImagePalette, _binary @@ -42,9 +43,6 @@ MODES = { } -def _accept(prefix): - return prefix[0:1] == b"\0" - ## # Image plugin for Targa files. @@ -58,7 +56,7 @@ class TgaImageFile(ImageFile.ImageFile): # process header s = self.fp.read(18) - id = i8(s[0]) + idlen = i8(s[0]) colormaptype = i8(s[1]) imagetype = i8(s[2]) @@ -70,7 +68,7 @@ class TgaImageFile(ImageFile.ImageFile): self.size = i16(s[12:]), i16(s[14:]) # validate header fields - if id != 0 or colormaptype not in (0, 1) or\ + if colormaptype not in (0, 1) or\ self.size[0] <= 0 or self.size[1] <= 0 or\ depth not in (1, 8, 16, 24, 32): raise SyntaxError("not a TGA file") @@ -103,6 +101,9 @@ class TgaImageFile(ImageFile.ImageFile): if imagetype & 8: self.info["compression"] = "tga_rle" + if idlen: + self.fp.seek(idlen, os.SEEK_CUR) + if colormaptype: # read palette start, size, mapdepth = i16(s[3:]), i16(s[5:]), i16(s[7:]) @@ -191,7 +192,7 @@ def _save(im, fp, filename, check=0): # -------------------------------------------------------------------- # Registry -Image.register_open("TGA", TgaImageFile, _accept) +Image.register_open("TGA", TgaImageFile) Image.register_save("TGA", _save) Image.register_extension("TGA", ".tga") From 347a1d8d956f9e64af4463ee25311b60cdd5657d Mon Sep 17 00:00:00 2001 From: wiredfool Date: Tue, 12 Aug 2014 12:31:37 -0700 Subject: [PATCH 07/15] J2k DOS fix -- CVE-2014-3598 Found and reported by Andrew Drake of dropbox.com --- PIL/Jpeg2KImagePlugin.py | 3 +++ Tests/check_j2k_dos.py | 11 +++++++++++ 2 files changed, 14 insertions(+) create mode 100644 Tests/check_j2k_dos.py diff --git a/PIL/Jpeg2KImagePlugin.py b/PIL/Jpeg2KImagePlugin.py index 0a7a6e297..53b10ca1a 100644 --- a/PIL/Jpeg2KImagePlugin.py +++ b/PIL/Jpeg2KImagePlugin.py @@ -70,6 +70,9 @@ def _parse_jp2_header(fp): else: hlen = 8 + if lbox < hlen: + raise SyntaxError('Invalid JP2 header length') + if tbox == b'jp2h': header = fp.read(lbox - hlen) break diff --git a/Tests/check_j2k_dos.py b/Tests/check_j2k_dos.py new file mode 100644 index 000000000..68f065bbc --- /dev/null +++ b/Tests/check_j2k_dos.py @@ -0,0 +1,11 @@ +# Tests potential DOS of Jpeg2kImagePlugin with 0 length block. +# Run from anywhere that PIL is importable. + +from PIL import Image +from io import BytesIO + +if bytes is str: + Image.open(BytesIO(bytes('\x00\x00\x00\x0cjP\x20\x20\x0d\x0a\x87\x0a\x00\x00\x00\x00hang'))) +else: + Image.open(BytesIO(bytes('\x00\x00\x00\x0cjP\x20\x20\x0d\x0a\x87\x0a\x00\x00\x00\x00hang', 'latin-1'))) + From 2d634d3019472ce09fb0aadbb4ec4d20fd8d7d47 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Tue, 12 Aug 2014 12:40:15 -0700 Subject: [PATCH 08/15] Bump Version/Changelog --- CHANGES.rst | 9 +++++++++ PIL/__init__.py | 2 +- _imaging.c | 2 +- setup.py | 2 +- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 141d5c715..47df8c176 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 2.6.0 (unreleased) ------------------ +- Fixed CVE-2014-3598, a DOS in the Jpeg2KImagePlugin (backport) + [Andrew Drake] + - Fixed CVE-2014-3589, a DOS in the IcnsImagePlugin [Andrew Drake] @@ -61,6 +64,12 @@ Changelog (Pillow) - Test PalmImagePlugin and method to skip known bad tests #776 [hugovk, wiredfool] +2.5.3 (2014-08-18) +------------------ + +- Fixed CVE-2014-3598, a DOS in the Jpeg2KImagePlugin (backport) + [Andrew Drake] + 2.5.2 (2014-08-13) ------------------ diff --git a/PIL/__init__.py b/PIL/__init__.py index 56edaf247..7b4b8abfa 100644 --- a/PIL/__init__.py +++ b/PIL/__init__.py @@ -12,7 +12,7 @@ # ;-) VERSION = '1.1.7' # PIL version -PILLOW_VERSION = '2.5.0' # Pillow +PILLOW_VERSION = '2.5.3' # Pillow _plugins = ['BmpImagePlugin', 'BufrStubImagePlugin', diff --git a/_imaging.c b/_imaging.c index c28bd4d93..ec8205dd4 100644 --- a/_imaging.c +++ b/_imaging.c @@ -71,7 +71,7 @@ * See the README file for information on usage and redistribution. */ -#define PILLOW_VERSION "2.5.0" +#define PILLOW_VERSION "2.5.3" #include "Python.h" diff --git a/setup.py b/setup.py index ac3bc3ea8..5cf0e5e65 100644 --- a/setup.py +++ b/setup.py @@ -90,7 +90,7 @@ except (ImportError, OSError): NAME = 'Pillow' -PILLOW_VERSION = '2.5.0' +PILLOW_VERSION = '2.5.3' TCL_ROOT = None JPEG_ROOT = None JPEG2K_ROOT = None From cd092e184647d4ebb3f054f26da453b41c97b53d Mon Sep 17 00:00:00 2001 From: wiredfool Date: Tue, 19 Aug 2014 08:56:46 -0700 Subject: [PATCH 09/15] Update CHANGES.rst --- CHANGES.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 47df8c176..75629cf2b 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,7 +4,7 @@ Changelog (Pillow) 2.6.0 (unreleased) ------------------ -- Fixed CVE-2014-3598, a DOS in the Jpeg2KImagePlugin (backport) +- Fixed CVE-2014-3598, a DOS in the Jpeg2KImagePlugin [Andrew Drake] - Fixed CVE-2014-3589, a DOS in the IcnsImagePlugin From 74f45d4e12e3a5b9b74a1038de6fd8af600860ac Mon Sep 17 00:00:00 2001 From: Hugo Date: Wed, 20 Aug 2014 00:13:30 +0300 Subject: [PATCH 10/15] Use tempfile() instead of /tmp/ --- Tests/test_file_jpeg2k.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Tests/test_file_jpeg2k.py b/Tests/test_file_jpeg2k.py index a0e7dfb53..fcd4b9d4d 100644 --- a/Tests/test_file_jpeg2k.py +++ b/Tests/test_file_jpeg2k.py @@ -52,7 +52,8 @@ class TestFileJpeg2k(PillowTestCase): def test_lossless(self): im = Image.open('Tests/images/test-card-lossless.jp2') im.load() - im.save('/tmp/test-card.png') + outfile = self.tempfile('test-card.png') + im.save(outfile) self.assert_image_similar(im, test_card, 1.0e-3) def test_lossy_tiled(self): From 7ff78e518d419c1ae4b3488a1a21fececfeb9473 Mon Sep 17 00:00:00 2001 From: Hugo Date: Wed, 20 Aug 2014 00:23:49 +0300 Subject: [PATCH 11/15] Update CHANGES.rst [CI skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 75629cf2b..eb1aa1b2f 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 2.6.0 (unreleased) ------------------ +- Fixed wrong P-mode of small, unoptimized L-mode GIF #843 + [uvNikita] + - Fixed CVE-2014-3598, a DOS in the Jpeg2KImagePlugin [Andrew Drake] From 8922104a093986c041950bbe1f9e2b7e818f53a7 Mon Sep 17 00:00:00 2001 From: Hugo Date: Wed, 20 Aug 2014 00:36:29 +0300 Subject: [PATCH 12/15] Need 'temp_' or 'temp.' in filename --- Tests/test_file_jpeg2k.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/test_file_jpeg2k.py b/Tests/test_file_jpeg2k.py index fcd4b9d4d..db67e9551 100644 --- a/Tests/test_file_jpeg2k.py +++ b/Tests/test_file_jpeg2k.py @@ -52,7 +52,7 @@ class TestFileJpeg2k(PillowTestCase): def test_lossless(self): im = Image.open('Tests/images/test-card-lossless.jp2') im.load() - outfile = self.tempfile('test-card.png') + outfile = self.tempfile('temp_test-card.png') im.save(outfile) self.assert_image_similar(im, test_card, 1.0e-3) From f94b6b4025e3ed7ab46841fd3f253d5244ff7162 Mon Sep 17 00:00:00 2001 From: Alexey Buzanov Date: Wed, 20 Aug 2014 10:32:06 +0400 Subject: [PATCH 13/15] flake8 and tests --- PIL/TgaImagePlugin.py | 25 +++++++++++++------------ Tests/images/tga_id_field.tga | Bin 0 -> 30050 bytes Tests/test_file_tga.py | 22 ++++++++++++++++++++++ 3 files changed, 35 insertions(+), 12 deletions(-) create mode 100644 Tests/images/tga_id_field.tga create mode 100644 Tests/test_file_tga.py diff --git a/PIL/TgaImagePlugin.py b/PIL/TgaImagePlugin.py index 049fb2dbb..46eafe8d0 100644 --- a/PIL/TgaImagePlugin.py +++ b/PIL/TgaImagePlugin.py @@ -19,7 +19,6 @@ __version__ = "0.3" -import os from PIL import Image, ImageFile, ImagePalette, _binary @@ -77,7 +76,7 @@ class TgaImageFile(ImageFile.ImageFile): if imagetype in (3, 11): self.mode = "L" if depth == 1: - self.mode = "1" # ??? + self.mode = "1" # ??? elif imagetype in (1, 9): self.mode = "P" elif imagetype in (2, 10): @@ -102,24 +101,24 @@ class TgaImageFile(ImageFile.ImageFile): self.info["compression"] = "tga_rle" if idlen: - self.fp.seek(idlen, os.SEEK_CUR) + self.info["id_section"] = self.fp.read(idlen) if colormaptype: # read palette start, size, mapdepth = i16(s[3:]), i16(s[5:]), i16(s[7:]) if mapdepth == 16: - self.palette = ImagePalette.raw("BGR;16", - b"\0"*2*start + self.fp.read(2*size)) + self.palette = ImagePalette.raw( + "BGR;16", b"\0"*2*start + self.fp.read(2*size)) elif mapdepth == 24: - self.palette = ImagePalette.raw("BGR", - b"\0"*3*start + self.fp.read(3*size)) + self.palette = ImagePalette.raw( + "BGR", b"\0"*3*start + self.fp.read(3*size)) elif mapdepth == 32: - self.palette = ImagePalette.raw("BGRA", - b"\0"*4*start + self.fp.read(4*size)) + self.palette = ImagePalette.raw( + "BGRA", b"\0"*4*start + self.fp.read(4*size)) # setup tile descriptor try: - rawmode = MODES[(imagetype&7, depth)] + rawmode = MODES[(imagetype & 7, depth)] if imagetype & 8: # compressed self.tile = [("tga_rle", (0, 0)+self.size, @@ -128,7 +127,7 @@ class TgaImageFile(ImageFile.ImageFile): self.tile = [("raw", (0, 0)+self.size, self.fp.tell(), (rawmode, 0, orientation))] except KeyError: - pass # cannot decode + pass # cannot decode # # -------------------------------------------------------------------- @@ -146,6 +145,7 @@ SAVE = { "RGBA": ("BGRA", 32, 0, 2), } + def _save(im, fp, filename, check=0): try: @@ -186,7 +186,8 @@ def _save(im, fp, filename, check=0): if colormaptype: fp.write(im.im.getpalette("RGB", "BGR")) - ImageFile._save(im, fp, [("raw", (0,0)+im.size, 0, (rawmode, 0, orientation))]) + ImageFile._save( + im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, 0, orientation))]) # # -------------------------------------------------------------------- diff --git a/Tests/images/tga_id_field.tga b/Tests/images/tga_id_field.tga new file mode 100644 index 0000000000000000000000000000000000000000..a3d666848d1833725ed3078782a676bcaa44bc0e GIT binary patch literal 30050 zcmeI)F-ikL6b9hM!ZX;X)*GaZ5D*1d#a0R125fSJ+#n~&1#*BKU=NT!efre;$xOw?Tja0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zATYGR>2#V-r<2KKv)R;jz1!^$heK}n`+dnLC^cq6`45SvX@;(PnDw{YZ3IHJTCEZU zDkDL-j>ltp8qX5MEphV0)H?L4*?u8PA$_a!`CLj%cLgfFl)f6e*x}a?MYk}v16ZQJ#F9nZ|^J~hrD2svmO@rgk32~IN6E6KR!80I#~%pWII z*>zsmAu4gPN}Mk0lKpAjtE?o)eC@2pjBw%J(_^Fk26Z8kX#@xmAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7csf&UW7Z=`?6%k%5(ZC=lx9&g|8=MS^H F>IX^pF_8cO literal 0 HcmV?d00001 diff --git a/Tests/test_file_tga.py b/Tests/test_file_tga.py new file mode 100644 index 000000000..6affba780 --- /dev/null +++ b/Tests/test_file_tga.py @@ -0,0 +1,22 @@ +from helper import unittest, PillowTestCase + +from PIL import Image + + +class TestFileSun(PillowTestCase): + + def test_sanity(self): + # tga file with id field + test_file = "Tests/images/tga_id_field.tga" + + # Act + im = Image.open(test_file) + + # Assert + self.assertEqual(im.size, (100, 100)) + + +if __name__ == '__main__': + unittest.main() + +# End of file From 5dd0d377c9437e4251f85590dda17dceb83d2f10 Mon Sep 17 00:00:00 2001 From: Alexey Buzanov Date: Wed, 20 Aug 2014 10:39:11 +0400 Subject: [PATCH 14/15] rename test --- Tests/test_file_tga.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Tests/test_file_tga.py b/Tests/test_file_tga.py index 6affba780..ea94dee64 100644 --- a/Tests/test_file_tga.py +++ b/Tests/test_file_tga.py @@ -3,9 +3,9 @@ from helper import unittest, PillowTestCase from PIL import Image -class TestFileSun(PillowTestCase): +class TestFileTga(PillowTestCase): - def test_sanity(self): + def test_id_field(self): # tga file with id field test_file = "Tests/images/tga_id_field.tga" @@ -18,5 +18,3 @@ class TestFileSun(PillowTestCase): if __name__ == '__main__': unittest.main() - -# End of file From 75a51275063cc910346aaa2445eff555d7cbc71a Mon Sep 17 00:00:00 2001 From: Hugo Date: Wed, 20 Aug 2014 10:00:38 +0300 Subject: [PATCH 15/15] Update CHANGES.rst [CI skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index eb1aa1b2f..97bbd5d86 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 2.6.0 (unreleased) ------------------ +- Fix TGA files with image ID field #856 + [megabuz] + - Fixed wrong P-mode of small, unoptimized L-mode GIF #843 [uvNikita]