diff --git a/zerver/lib/upload.py b/zerver/lib/upload.py index 8f563d6afc..5ec846e583 100644 --- a/zerver/lib/upload.py +++ b/zerver/lib/upload.py @@ -26,6 +26,7 @@ import base64 import os import re from PIL import Image, ImageOps, ExifTags +from PIL.Image import DecompressionBombError from PIL.GifImagePlugin import GifImageFile import io import random @@ -110,6 +111,8 @@ def resize_avatar(image_data: bytes, size: int=DEFAULT_AVATAR_SIZE) -> bytes: im = ImageOps.fit(im, (size, size), Image.ANTIALIAS) except IOError: raise BadImageError(_("Could not decode image; did you upload an image file?")) + except DecompressionBombError: + raise BadImageError(_("Image size exceeds limit.")) out = io.BytesIO() if im.mode == 'CMYK': im = im.convert('RGB') @@ -123,6 +126,8 @@ def resize_logo(image_data: bytes) -> bytes: im.thumbnail((8*DEFAULT_AVATAR_SIZE, DEFAULT_AVATAR_SIZE), Image.ANTIALIAS) except IOError: raise BadImageError(_("Could not decode image; did you upload an image file?")) + except DecompressionBombError: + raise BadImageError(_("Image size exceeds limit.")) out = io.BytesIO() if im.mode == 'CMYK': im = im.convert('RGB') @@ -163,6 +168,8 @@ def resize_emoji(image_data: bytes, size: int=DEFAULT_EMOJI_SIZE) -> bytes: return out.getvalue() except IOError: raise BadImageError(_("Could not decode image; did you upload an image file?")) + except DecompressionBombError: + raise BadImageError(_("Image size exceeds limit.")) ### Common diff --git a/zerver/tests/images/bomb.png b/zerver/tests/images/bomb.png new file mode 100644 index 0000000000..66d8b0a484 Binary files /dev/null and b/zerver/tests/images/bomb.png differ diff --git a/zerver/tests/test_upload.py b/zerver/tests/test_upload.py index ec165ea624..2fe8baa91b 100644 --- a/zerver/tests/test_upload.py +++ b/zerver/tests/test_upload.py @@ -1782,3 +1782,20 @@ class ExifRotateTests(TestCase): img = Image.open(io.BytesIO(img_data)) exif_rotate(img) rotate.assert_called_with(90, expand=True) + +class DecompressionBombTests(ZulipTestCase): + def setUp(self) -> None: + self.test_urls = { + "/json/users/me/avatar": "Image size exceeds limit.", + "/json/realm/logo": "Image size exceeds limit.", + "/json/realm/icon": "Image size exceeds limit.", + "/json/realm/emoji/bomb_emoji": "Image file upload failed.", + } + + def test_decompression_bomb(self) -> None: + self.login(self.example_email("iago")) + with get_test_image_file("bomb.png") as fp: + for url, error_string in self.test_urls.items(): + fp.seek(0, 0) + result = self.client_post(url, {'f1': fp}) + self.assert_json_error(result, error_string)