From 53cf975e9b45344cb7bf0822e1fda7626e7ee244 Mon Sep 17 00:00:00 2001 From: Kris Gavvala Date: Mon, 2 Nov 2026 15:11:42 -0600 Subject: [PATCH] python3-pillow: patch CVE-2026-112393 This patch applies the upstream fix and related tests in commit [1] to address the issue in [2]. [1] https://github.com/python-pillow/Pillow/commit/78c08bd4d9c08037f77465a61d90633f669013f4 [2] https://github.com/python-pillow/Pillow/security/advisories/GHSA-332p-7hrc-92wf Signed-off-by: Kris Gavvala Signed-off-by: Khem Raj --- .../python3-pillow/CVE-2026-112393.patch | 124 ++++++++++++++++++ .../python/python3-pillow_12.3.0.bb | 1 + 2 files changed, 125 insertions(+) create mode 100644 meta-python/recipes-devtools/python/python3-pillow/CVE-2026-112393.patch diff --git a/meta-python/recipes-devtools/python/python3-pillow/CVE-2026-112393.patch b/meta-python/recipes-devtools/python/python3-pillow/CVE-2026-112393.patch new file mode 100644 index 0000000000..cb9be3b33e --- /dev/null +++ b/meta-python/recipes-devtools/python/python3-pillow/CVE-2026-112393.patch @@ -0,0 +1,124 @@ +From 9b693ea929423da2bebef5523881ae3850c3877e Mon Sep 17 00:00:00 2001 +From: Jeffrey 'Alex' Clark +Date: Mon, 12 Oct 2026 21:18:41 -0400 +Subject: [PATCH] Fix heap buffer overflow in _tiffWriteProc when saving TIFF + to BytesIO + +libtiff seeks past the current end of data when writing the directory +and writes tag values there. _tiffWriteProc sized its realloc() from +state->size + size instead of the write position state->loc + size, +so the first out-of-line tag value written after such a seek could +overrun the allocation by up to the size of the directory. The bytes +skipped by the seek were also never zeroed, leaking stale heap +contents into the saved file. + +Size the buffer from the write position and zero the space added by +realloc, matching the semantics of writing to a real file. Reject +writes where the position is still beyond the buffer for the +non-reallocating case. + +Reported-by: carlini +Reported-by: Tomer-PL +Fixes: GHSA-332p-7hrc-92wf + +Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> +Co-Authored-By: Claude Opus 5.5 + +CVE: CVE-2026-112393 +Upstream-Status: Backport [https://github.com/python-pillow/Pillow/commit/78c08bd4d9c08037f77465a61d90633f669013f4] + +Signed-off-by: Kris Gavvala +--- + Tests/test_file_libtiff.py | 36 ++++++++++++++++++++++++++++++++++++ + src/libImaging/TiffDecode.c | 15 ++++++++++++++- + 2 files changed, 50 insertions(+), 1 deletion(-) + +diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py +index c9849a056..84e4299a1 100644 +--- a/Tests/test_file_libtiff.py ++++ b/Tests/test_file_libtiff.py +@@ -757,6 +757,42 @@ class TestFileLibTiff(LibTiffTestCase): + save_bytesio("packbits") + save_bytesio("tiff_lzw") + ++ ++ def test_save_bytesio_seek_past_end(self, tmp_path: Path) -> None: ++ # When writing the directory, libtiff seeks past the end of the data ++ # written so far, and then writes the tag values there. Check that the ++ # in-memory write buffer grows to fit, and that the bytes that are ++ # skipped over are zero, as they would be when writing to a file. ++ ++ # 7 distinct bytes per row: PackBits emits 8 bytes per row, so the strip ++ # plus the header are exactly 64 KiB, the initial size of the buffer ++ im = Image.new("L", (7, 8191)) ++ im.putdata([(i * 37) & 0xFF for i in range(7 * 8191)]) ++ kwargs: dict[str, Any] = { ++ "compression": "packbits", ++ "description": "D" * 65535, ++ } ++ ++ outfile = tmp_path / "temp.tif" ++ im.save(outfile, **kwargs) ++ buffer_io = io.BytesIO() ++ im.save(buffer_io, format="tiff", **kwargs) ++ assert buffer_io.getvalue() == outfile.read_bytes() ++ ++ # odd-length tag values, so that libtiff pads between them. Large enough ++ # that the buffer has to be reallocated ++ im = hopper().resize((300, 300)) ++ kwargs = { ++ "compression": "tiff_lzw", ++ "description": "abcdef", ++ "software": "xyz0", ++ "tiffinfo": {315: "artist", 33432: "copyright!"}, ++ } ++ im.save(outfile, **kwargs) ++ buffer_io = io.BytesIO() ++ im.save(buffer_io, format="tiff", **kwargs) ++ assert buffer_io.getvalue() == outfile.read_bytes() ++ + def test_save_ycbcr(self, tmp_path: Path) -> None: + im = hopper("YCbCr") + outfile = tmp_path / "temp.tif" +diff --git a/src/libImaging/TiffDecode.c b/src/libImaging/TiffDecode.c +index 72e0d7b30..e21327f02 100644 +--- a/src/libImaging/TiffDecode.c ++++ b/src/libImaging/TiffDecode.c +@@ -88,10 +88,12 @@ _tiffWriteProc(thandle_t hdata, tdata_t buf, tsize_t size) { + dump_state(state); + + to_write = min(size, state->size - (tsize_t)state->loc); ++ // libtiff may have seeked past the end of the buffer, e.g. when writing the ++ // directory, so the required size is based on the current location. + if (state->flrealloc && size > to_write) { + tdata_t new_data; + tsize_t newsize = state->size; +- while (newsize < (size + state->size)) { ++ while (newsize < (size + (tsize_t)state->loc)) { + if (newsize > INT_MAX - 64 * 1024) { + return 0; + } +@@ -105,9 +107,20 @@ _tiffWriteProc(thandle_t hdata, tdata_t buf, tsize_t size) { + // fail out + return 0; + } ++ // zero the new space, so that any bytes libtiff seeks over without ++ // writing are 0 in the output, as they would be in a file ++ memset((UINT8 *)new_data + state->size, 0, newsize - state->size); + state->data = new_data; + state->size = newsize; + to_write = size; ++ } else if (to_write < 0) { ++ TIFFError( ++ "_tiffWriteProc", ++ "Invalid Write at loc %" PRIu64 ", size: %" PRIu64, ++ state->loc, ++ (toff_t)state->size ++ ); ++ return 0; + } + + TRACE(("to_write: %d\n", (int)to_write)); +-- +2.43.0 + diff --git a/meta-python/recipes-devtools/python/python3-pillow_12.3.0.bb b/meta-python/recipes-devtools/python/python3-pillow_12.3.0.bb index 1c6668e53d..e4826da1a6 100644 --- a/meta-python/recipes-devtools/python/python3-pillow_12.3.0.bb +++ b/meta-python/recipes-devtools/python/python3-pillow_12.3.0.bb @@ -7,6 +7,7 @@ LIC_FILES_CHKSUM = "file://LICENSE;md5=a6f0ac3777cfc96ded1b825e32ae7c99" SRC_URI = "git://github.com/python-pillow/Pillow.git;branch=main;protocol=https;tag=${PV} \ file://0001-support-cross-compiling.patch \ + file://CVE-2026-112393.patch \ " SRCREV = "bb1d8e8ab8d29048624d96e3ee53cecf7c13d13d"