diff --git a/CHANGES.rst b/CHANGES.rst index d774aa963..3b1927430 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -2,6 +2,14 @@ Changelog (Pillow) ================== +6.2.2.2 (date TBD) +------------------ + +- This is the second Pillow release to support Python 2.7 from ActiveState + +- Fix OOB Read in Jpeg2KDecode. CVE 2021-25287, CVE 2021-25288 + [emilieyyu] + 6.2.2.1 (2021-10-08) ------------------ diff --git a/Tests/images/crash-4fb027452e6988530aa5dabee76eecacb3b79f8a.j2k b/Tests/images/crash-4fb027452e6988530aa5dabee76eecacb3b79f8a.j2k new file mode 100644 index 000000000..c9bd7fc0a Binary files /dev/null and b/Tests/images/crash-4fb027452e6988530aa5dabee76eecacb3b79f8a.j2k differ diff --git a/Tests/images/crash-7d4c83eb92150fb8f1653a697703ae06ae7c4998.j2k b/Tests/images/crash-7d4c83eb92150fb8f1653a697703ae06ae7c4998.j2k new file mode 100644 index 000000000..fd2f4dd36 Binary files /dev/null and b/Tests/images/crash-7d4c83eb92150fb8f1653a697703ae06ae7c4998.j2k differ diff --git a/Tests/images/crash-ccca68ff40171fdae983d924e127a721cab2bd50.j2k b/Tests/images/crash-ccca68ff40171fdae983d924e127a721cab2bd50.j2k new file mode 100644 index 000000000..c3ad0d633 Binary files /dev/null and b/Tests/images/crash-ccca68ff40171fdae983d924e127a721cab2bd50.j2k differ diff --git a/Tests/images/crash-d2c93af851d3ab9a19e34503626368b2ecde9c03.j2k b/Tests/images/crash-d2c93af851d3ab9a19e34503626368b2ecde9c03.j2k new file mode 100644 index 000000000..3aadfc377 Binary files /dev/null and b/Tests/images/crash-d2c93af851d3ab9a19e34503626368b2ecde9c03.j2k differ diff --git a/Tests/test_file_jpeg2k.py b/Tests/test_file_jpeg2k.py index 72b374a0b..d3b4c6a78 100644 --- a/Tests/test_file_jpeg2k.py +++ b/Tests/test_file_jpeg2k.py @@ -4,6 +4,8 @@ from PIL import Image, Jpeg2KImagePlugin from .helper import PillowTestCase +import pytest + codecs = dir(Image.core) test_card = Image.open("Tests/images/test-card.png") @@ -210,3 +212,19 @@ class TestFileJpeg2k(PillowTestCase): # Assert self.assertEqual(p.image.size, (640, 480)) + + +@pytest.mark.parametrize( + "test_file", + [ + "Tests/images/crash-4fb027452e6988530aa5dabee76eecacb3b79f8a.j2k", + "Tests/images/crash-7d4c83eb92150fb8f1653a697703ae06ae7c4998.j2k", + "Tests/images/crash-ccca68ff40171fdae983d924e127a721cab2bd50.j2k", + "Tests/images/crash-d2c93af851d3ab9a19e34503626368b2ecde9c03.j2k", + ], +) +def test_crashes(test_file): + with open(test_file, "rb") as f: + with Image.open(f) as im: + # Valgrind should not complain here + im.load() diff --git a/docs/releasenotes/6.2.2.2.rst b/docs/releasenotes/6.2.2.2.rst new file mode 100644 index 000000000..bc9917c6b --- /dev/null +++ b/docs/releasenotes/6.2.2.2.rst @@ -0,0 +1,10 @@ +6.2.2.2 +------- + +Security +======== + +This release addresses several critical CVEs. + +CVE 2021-25287, CVE 2021-25288 has out-of-bounds read in J2kDecode, in +j2ku_graya_la. diff --git a/docs/releasenotes/index.rst b/docs/releasenotes/index.rst index 88ac182ec..3eaab9c1f 100644 --- a/docs/releasenotes/index.rst +++ b/docs/releasenotes/index.rst @@ -6,6 +6,7 @@ Release Notes .. toctree:: :maxdepth: 2 + 6.2.2.2 6.2.2.1 6.2.2 6.2.1 diff --git a/src/libImaging/Jpeg2KDecode.c b/src/libImaging/Jpeg2KDecode.c index f2e437dda..0e61663a5 100644 --- a/src/libImaging/Jpeg2KDecode.c +++ b/src/libImaging/Jpeg2KDecode.c @@ -557,8 +557,9 @@ j2k_decode_entry(Imaging im, ImagingCodecState state) opj_dparameters_t params; OPJ_COLOR_SPACE color_space; j2k_unpacker_t unpack = NULL; - size_t buffer_size = 0; - unsigned n; + size_t buffer_size = 0, tile_bytes = 0; + unsigned n, tile_height, tile_width; + int total_component_width = 0; stream = opj_stream_create(BUFFER_SIZE, OPJ_TRUE); @@ -703,6 +704,59 @@ j2k_decode_entry(Imaging im, ImagingCodecState state) tile_info.x1 = (tile_info.x1 + correction) >> context->reduce; tile_info.y1 = (tile_info.y1 + correction) >> context->reduce; + /* Check the tile bounds; if the tile is outside the image area, + or if it has a negative width or height (i.e. the coordinates are + swapped), bail. */ + if (tile_info.x0 >= tile_info.x1 || tile_info.y0 >= tile_info.y1 || + tile_info.x0 < 0 || tile_info.y0 < 0 || + (OPJ_UINT32)tile_info.x0 < image->x0 || + (OPJ_UINT32)tile_info.y0 < image->y0 || + (OPJ_INT32)(tile_info.x1 - image->x0) > im->xsize || + (OPJ_INT32)(tile_info.y1 - image->y0) > im->ysize) { + state->errcode = IMAGING_CODEC_BROKEN; + state->state = J2K_STATE_FAILED; + 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 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; + + /* 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 * total_component_width; + + if (tile_bytes > tile_info.data_size) { + tile_info.data_size = tile_bytes; + } + if (buffer_size < tile_info.data_size) { /* malloc check ok, tile_info.data_size from openjpeg */ UINT8 *new = realloc (state->buffer, tile_info.data_size);