From a26a584812d7e9b1f4d1d98797ea95e10b1e52d6 Mon Sep 17 00:00:00 2001 From: Alexander Date: Mon, 22 May 2017 19:38:38 +0300 Subject: [PATCH 1/5] Do not raise SyntaxError for wrong chunks (just ignore them) --- PIL/PngImagePlugin.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/PIL/PngImagePlugin.py b/PIL/PngImagePlugin.py index 5e5eb14c7..e329a6c0d 100644 --- a/PIL/PngImagePlugin.py +++ b/PIL/PngImagePlugin.py @@ -113,7 +113,8 @@ class ChunkStream(object): length = i32(s) if not is_cid(cid): - raise SyntaxError("broken PNG file (chunk %s)" % repr(cid)) + if not ImageFile.LOAD_TRUNCATED_IMAGES: + raise SyntaxError("broken PNG file (chunk %s)" % repr(cid)) return cid, pos, length From 873d9ec08921a67dfbcfa41cc2ad0a263711c225 Mon Sep 17 00:00:00 2001 From: Alexander Date: Mon, 22 May 2017 19:38:57 +0300 Subject: [PATCH 2/5] fix memory leak if loading is failed --- libImaging/ZipDecode.c | 1 + 1 file changed, 1 insertion(+) diff --git a/libImaging/ZipDecode.c b/libImaging/ZipDecode.c index 37cb2866c..2f6cdce34 100644 --- a/libImaging/ZipDecode.c +++ b/libImaging/ZipDecode.c @@ -85,6 +85,7 @@ ImagingZipDecode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) err = inflateInit(&context->z_stream); if (err < 0) { state->errcode = IMAGING_CODEC_CONFIG; + free(context->previous); return -1; } From 8c69132579967861972b1a3634da97961464ea8d Mon Sep 17 00:00:00 2001 From: Alexander Date: Tue, 23 May 2017 17:26:13 +0300 Subject: [PATCH 3/5] fix another memory leak --- decode.c | 1 + libImaging/Imaging.h | 1 + libImaging/ZipDecode.c | 20 ++++++++++++++++++++ 3 files changed, 22 insertions(+) diff --git a/decode.c b/decode.c index d7fe02fae..f749a40a7 100644 --- a/decode.c +++ b/decode.c @@ -779,6 +779,7 @@ PyImaging_ZipDecoderNew(PyObject* self, PyObject* args) return NULL; decoder->decode = ImagingZipDecode; + decoder->cleanup = ImagingZipDecodeCleanup; ((ZIPSTATE*)decoder->state.context)->interlaced = interlaced; diff --git a/libImaging/Imaging.h b/libImaging/Imaging.h index 2030821f2..99fff7f67 100644 --- a/libImaging/Imaging.h +++ b/libImaging/Imaging.h @@ -455,6 +455,7 @@ extern int ImagingXbmEncode(Imaging im, ImagingCodecState state, #ifdef HAVE_LIBZ extern int ImagingZipDecode(Imaging im, ImagingCodecState state, UINT8* buffer, int bytes); +extern int ImagingZipDecodeCleanup(ImagingCodecState state); extern int ImagingZipEncode(Imaging im, ImagingCodecState state, UINT8* buffer, int bytes); extern int ImagingZipEncodeCleanup(ImagingCodecState state); diff --git a/libImaging/ZipDecode.c b/libImaging/ZipDecode.c index 2f6cdce34..dc5a7684e 100644 --- a/libImaging/ZipDecode.c +++ b/libImaging/ZipDecode.c @@ -86,6 +86,7 @@ ImagingZipDecode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) if (err < 0) { state->errcode = IMAGING_CODEC_CONFIG; free(context->previous); + context->previous = NULL; return -1; } @@ -127,6 +128,7 @@ ImagingZipDecode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) else state->errcode = IMAGING_CODEC_CONFIG; free(context->previous); + context->previous = NULL; inflateEnd(&context->z_stream); return -1; } @@ -192,6 +194,7 @@ ImagingZipDecode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) default: state->errcode = IMAGING_CODEC_UNKNOWN; free(context->previous); + context->previous = NULL; inflateEnd(&context->z_stream); return -1; } @@ -259,6 +262,7 @@ ImagingZipDecode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) state->errcode = IMAGING_CODEC_BROKEN; */ free(context->previous); + context->previous = NULL; inflateEnd(&context->z_stream); return -1; /* end of file (errcode=0) */ @@ -275,4 +279,20 @@ ImagingZipDecode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) } + +int ImagingZipDecodeCleanup(ImagingCodecState state){ + /* called to fee the decompression engine when the decode terminates + due to a corrupt or truncated image + */ + ZIPSTATE* context = (ZIPSTATE*) state->context; + + /* Clean up */ + if (context->previous) { + inflateEnd(&context->z_stream); + free(context->previous); + context->previous = NULL; + } + return -1; +} + #endif From e6da335206d7f091b8d032dca2aa0e34532e7083 Mon Sep 17 00:00:00 2001 From: Alexander Date: Thu, 25 May 2017 13:59:11 +0300 Subject: [PATCH 4/5] =?UTF-8?q?fee=20=E2=86=92=20free?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- libImaging/JpegDecode.c | 2 +- libImaging/ZipDecode.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libImaging/JpegDecode.c b/libImaging/JpegDecode.c index 6ebdb8f93..4bb929b6a 100644 --- a/libImaging/JpegDecode.c +++ b/libImaging/JpegDecode.c @@ -268,7 +268,7 @@ ImagingJpegDecode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) /* -------------------------------------------------------------------- */ int ImagingJpegDecodeCleanup(ImagingCodecState state){ - /* called to fee the decompression engine when the decode terminates + /* called to free the decompression engine when the decode terminates due to a corrupt or truncated image */ JPEGSTATE* context = (JPEGSTATE*) state->context; diff --git a/libImaging/ZipDecode.c b/libImaging/ZipDecode.c index dc5a7684e..e96e3200c 100644 --- a/libImaging/ZipDecode.c +++ b/libImaging/ZipDecode.c @@ -281,7 +281,7 @@ ImagingZipDecode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) int ImagingZipDecodeCleanup(ImagingCodecState state){ - /* called to fee the decompression engine when the decode terminates + /* called to free the decompression engine when the decode terminates due to a corrupt or truncated image */ ZIPSTATE* context = (ZIPSTATE*) state->context; From 8eb1dcb7c5b125cee27ae5ba2e1ea7359d18ee6f Mon Sep 17 00:00:00 2001 From: wiredfool Date: Wed, 21 Jun 2017 03:31:32 -0700 Subject: [PATCH 5/5] test for truncated png memory leak #2541 --- Tests/test_file_png.py | 38 ++++++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index 32d6a3acd..a4f3f5855 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -1,14 +1,16 @@ from helper import unittest, PillowTestCase, hopper +from PIL import Image, ImageFile, PngImagePlugin from io import BytesIO - -from PIL import Image -from PIL import ImageFile -from PIL import PngImagePlugin import zlib +import sys codecs = dir(Image.core) +# For Truncated phng memory leak +MEM_LIMIT = 1 # max increase in MB +ITERATIONS = 100 + # sample png stream TEST_PNG_FILE = "Tests/images/hopper.png" @@ -530,5 +532,33 @@ class TestFilePng(PillowTestCase): self.assertLess(chunks.index(b"pHYs"), chunks.index(b"IDAT")) +@unittest.skipIf(sys.platform.startswith('win32'), "requires Unix or MacOS") +class TestTruncatedPngPLeaks(PillowTestCase): + + def setUp(self): + if "zip_encoder" not in codecs or "zip_decoder" not in codecs: + self.skipTest("zip/deflate support not available") + + def _get_mem_usage(self): + from resource import getpagesize, getrusage, RUSAGE_SELF + mem = getrusage(RUSAGE_SELF).ru_maxrss + return mem * getpagesize() / 1024 / 1024 + + def test_leak_load(self): + with open('Tests/images/hopper.png', 'rb') as f: + DATA = BytesIO(f.read(16 * 1024)) + + ImageFile.LOAD_TRUNCATED_IMAGES = True + start_mem = self._get_mem_usage() + try: + for _ in range(ITERATIONS): + with Image.open(DATA) as im: + im.load() + mem = (self._get_mem_usage() - start_mem) + self.assertLess(mem, MEM_LIMIT, msg='memory usage limit exceeded') + finally: + ImageFile.LOAD_TRUNCATED_IMAGES = False + + if __name__ == '__main__': unittest.main()