From 3bf5eddb89afdf690eceaa52bc4d3546ba9a5f87 Mon Sep 17 00:00:00 2001
From: Eric Soroos <eric-github@soroos.net>
Date: Sun, 7 Mar 2021 12:32:12 +0100
Subject: [PATCH] Fix OOB Read in Jpeg2KDecode CVE-2021-25287,CVE-2021-25288

* For J2k images with multiple bands, it's legal in to have different
  widths for each band, e.g. 1 byte for L, 4 bytes for A
* This dates to Pillow 2.4.0
---
 ...b027452e6988530aa5dabee76eecacb3b79f8a.j2k | Bin 0 -> 335 bytes
 ...4c83eb92150fb8f1653a697703ae06ae7c4998.j2k | Bin 0 -> 335 bytes
 ...ca68ff40171fdae983d924e127a721cab2bd50.j2k | Bin 0 -> 335 bytes
 ...c93af851d3ab9a19e34503626368b2ecde9c03.j2k | Bin 0 -> 3886 bytes
 Tests/test_file_jpeg2k.py                     |  16 +++++++++
 libImaging/Jpeg2KDecode.c                 |  33 +++++++++++++-----
 6 files changed, 41 insertions(+), 8 deletions(-)
 create mode 100644 Tests/images/crash-4fb027452e6988530aa5dabee76eecacb3b79f8a.j2k
 create mode 100644 Tests/images/crash-7d4c83eb92150fb8f1653a697703ae06ae7c4998.j2k
 create mode 100644 Tests/images/crash-ccca68ff40171fdae983d924e127a721cab2bd50.j2k
 create mode 100644 Tests/images/crash-d2c93af851d3ab9a19e34503626368b2ecde9c03.j2k

diff --git a/Tests/test_file_jpeg2k.py b/Tests/test_file_jpeg2k.py
index 13ae09af59f..5523d068b2e 100644
--- a/Tests/test_file_jpeg2k.py
+++ b/Tests/test_file_jpeg2k.py
@@ -162,7 +162,20 @@ def test_16bit_jp2_roundtrips(self):
         jp2 = Image.open('Tests/images/16bit.cropped.jp2')
         im = self.roundtrip(jp2)
         self.assert_image_equal(im, jp2)
+
+    def test_crashes(self):
+        test_files = [
+            "Tests/images/crash-4fb027452e6988530aa5dabee76eecacb3b79f8a.j2k",
+            "Tests/images/crash-7d4c83eb92150fb8f1653a697703ae06ae7c4998.j2k",
+            "Tests/images/crash-ccca68ff40171fdae983d924e127a721cab2bd50.j2k",
+            "Tests/images/crash-d2c93af851d3ab9a19e34503626368b2ecde9c03.j2k",
+        ]
+        for test_file in test_files:
+            with open(test_file, "rb") as f:
+                with Image.open(f) as im:
+                    # Valgrind should not complain here
+                    im.load()
 
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/libImaging/Jpeg2KDecode.c b/libImaging/Jpeg2KDecode.c
index a2a7354dbe0..aa4fc58f7a0 100644
--- a/libImaging/Jpeg2KDecode.c
+++ b/libImaging/Jpeg2KDecode.c
@@ -558,7 +558,7 @@ j2k_decode_entry(Imaging im, ImagingCodecState state) {
     j2k_unpacker_t unpack = NULL;
     size_t buffer_size = 0, tile_bytes = 0;
     unsigned n, tile_height, tile_width;
-    int components;
+    int total_component_width = 0;
 
 
     stream = opj_stream_default_create(OPJ_TRUE);
@@ -718,23 +718,40 @@ j2k_decode_entry(Imaging im, ImagingCodecState state) {
             goto quick_exit;
         }
 
+        if (tile_info.nb_comps != image->numcomps) {
+            state->errcode = IMAGING_CODEC_BROKEN;
+            state->state = J2K_STATE_FAILED;
+            goto quick_exit;
+        }
+
         /* Sometimes the tile_info.datasize we get back from openjpeg
-           is less than numcomps*w*h, and we overflow in the
+           is less than sum(comp_bytes)*w*h, and we overflow in the
            shuffle stage */
 
         tile_width = tile_info.x1 - tile_info.x0;
         tile_height = tile_info.y1 - tile_info.y0;
-        components = tile_info.nb_comps == 3 ? 4 : tile_info.nb_comps;
-        if (( tile_width > UINT_MAX / components ) ||
-            ( tile_height > UINT_MAX / components ) ||
-            ( tile_width > UINT_MAX / (tile_height * components )) ||
-            ( tile_height > UINT_MAX / (tile_width * components ))) {
+
+        /* Total component width = sum (component_width) e.g, it's
+         legal for an la file to have a 1 byte width for l, and 4 for
+         a. and then a malicious file could have a smaller tile_bytes
+        */
+
+        for (n=0; n < tile_info.nb_comps; n++) {
+            // see csize /acsize calcs
+            int csize = (image->comps[n].prec + 7) >> 3;
+            csize = (csize == 3) ? 4 : csize;
+            total_component_width += csize;
+        }
+        if ((tile_width > UINT_MAX / total_component_width) ||
+            (tile_height > UINT_MAX / total_component_width) ||
+            (tile_width > UINT_MAX / (tile_height * total_component_width)) ||
+            (tile_height > UINT_MAX / (tile_width * total_component_width))) {
             state->errcode = IMAGING_CODEC_BROKEN;
             state->state = J2K_STATE_FAILED;
             goto quick_exit;
         }
 
-        tile_bytes = tile_width * tile_height * components;
+        tile_bytes = tile_width * tile_height * total_component_width;
 
         if (tile_bytes > tile_info.data_size) {
             tile_info.data_size = tile_bytes;
