From 382cb5bb13590e79372e24c5ca09802d37717639 Mon Sep 17 00:00:00 2001 From: Alex Vandiver Date: Thu, 11 Jul 2024 02:50:44 +0000 Subject: [PATCH] thumbnail: Lock down which formats we parse. --- zerver/lib/thumbnail.py | 20 +++++++++++++++++++- zerver/tests/images/actually-a-bmp.png | Bin 0 -> 1094 bytes zerver/tests/test_upload.py | 7 +++++++ 3 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 zerver/tests/images/actually-a-bmp.png diff --git a/zerver/lib/thumbnail.py b/zerver/lib/thumbnail.py index 32df086a29..e8d557f7bb 100644 --- a/zerver/lib/thumbnail.py +++ b/zerver/lib/thumbnail.py @@ -29,7 +29,8 @@ MAX_EMOJI_GIF_FILE_SIZE_BYTES = 128 * 1024 # 128 kb # provided by the browser, and may not match the bytes they uploaded. # # This should be kept synced with the client-side image-picker in -# web/upload_widget.ts. +# web/upload_widget.ts. Any additions below must be accompanied by +# changes to the pyvips block below as well. THUMBNAIL_ACCEPT_IMAGE_TYPES = frozenset( [ "image/avif", @@ -42,6 +43,23 @@ THUMBNAIL_ACCEPT_IMAGE_TYPES = frozenset( ] ) +# This is what enforces security limitations on which formats are +# parsed; we disable all loaders, then re-enable the ones we support +# -- then explicitly disable any "untrusted" ones, in case libvips for +# some reason marks one of the above formats as such (because they are +# no longer fuzzed, for instance). +# +# Note that only libvips >= 8.13 (Uubntu 24.04 or later, Debian 12 or +# later) supports this! These are no-ops on earlier versions of libvips. +pyvips.operation_block_set("VipsForeignLoad", True) +pyvips.operation_block_set("VipsForeignLoadHeif", False) # image/avif, image/heic +pyvips.operation_block_set("VipsForeignLoadNsgif", False) # image/gif +pyvips.operation_block_set("VipsForeignLoadJpeg", False) # image/jpeg +pyvips.operation_block_set("VipsForeignLoadPng", False) # image/png +pyvips.operation_block_set("VipsForeignLoadTiff", False) # image/tiff +pyvips.operation_block_set("VipsForeignLoadWebp", False) # image/webp +pyvips.block_untrusted_set(True) + class BadImageError(JsonableError): code = ErrorCode.BAD_IMAGE diff --git a/zerver/tests/images/actually-a-bmp.png b/zerver/tests/images/actually-a-bmp.png new file mode 100644 index 0000000000000000000000000000000000000000..4e7ede7bb9ab4a001d4c45c71cb1a7c53a7a7c89 GIT binary patch literal 1094 zcmY+BXEYEB0EItNR#s*S6_wSXNRdQ|N>kEMiL{6m5m6dwFHI^Mq9WRbmZFG^B9u|6 ztSA|U=Q;1syXV|>?$2v(V<|)r47**kJBf5tu$uxs38Z`0|3si?=qUmM0tgBU(yLc5 zgoK3X-Mcq^`t(6qSQrrz5ky5r5fc+bTwI*KefuIIA%Uc%BvMjRNJ~p2BO^n_ds;Z)1;EC`OMSjh>z! z`uh5e88e2lW5;4(V1S{aA>+o4!^p^p@#DucVZsEAjg2ueF=67wiI|$2GHKEz%*@P~ zJb5za=H^&fSTJSE6sAs{ilwC`)229dkvV^5em*U{yz_Mk_ zSiXEYD^{$)(b18WD_7#=uec8HoD}H`{`1|{_ zZQC{i0s;sO3}pNE?d;gGgP@=wcJAECu3fv>y?Zx%_Us`zIGB)-5JE#k*}Hcy`}XZ) z|Ni|PIB;>VKFpCLM>u-)D94T+Qq6`ZQTOB%E}@;JDay}-;$G)LvC&^d3kx{=jZe8-8G`~#`Yh+qH! literal 0 HcmV?d00001 diff --git a/zerver/tests/test_upload.py b/zerver/tests/test_upload.py index eb63c1e73a..634552d2e6 100644 --- a/zerver/tests/test_upload.py +++ b/zerver/tests/test_upload.py @@ -10,6 +10,8 @@ import orjson import pyvips from django.conf import settings from django.utils.timezone import now as timezone_now +from pyvips import at_least_libvips +from pyvips import version as libvips_version from typing_extensions import override from urllib3 import encode_multipart_formdata from urllib3.fields import RequestField @@ -1343,11 +1345,16 @@ class AvatarTest(UploadSerializeMixin, ZulipTestCase): corrupt_files = [ ("text.txt", False), ("unsupported.bmp", False), + ("actually-a-bmp.png", True), ("corrupt.png", True), ("corrupt.gif", True), ] for fname, is_valid_image_format in corrupt_files: with self.subTest(fname=fname): + if not at_least_libvips(8, 13) and "actually-a-" in fname: # nocoverage + self.skipTest( + f"libvips is only version {libvips_version(0)}.{libvips_version(1)}" + ) self.login("hamlet") with get_test_image_file(fname) as fp: result = self.client_post("/json/users/me/avatar", {"file": fp})