new file mode 100644
@@ -0,0 +1,124 @@
+From 9b693ea929423da2bebef5523881ae3850c3877e Mon Sep 17 00:00:00 2001
+From: Jeffrey 'Alex' Clark <aclark@aclark.net>
+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 <noreply@anthropic.com>
+
+CVE: CVE-2026-112393
+Upstream-Status: Backport [https://github.com/python-pillow/Pillow/commit/78c08bd4d9c08037f77465a61d90633f669013f4]
+
+Signed-off-by: Kris Gavvala <kris.gavvala@windriver.com>
+---
+ 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
+
@@ -17,6 +17,7 @@ SRC_URI = "git://github.com/python-pillow/Pillow.git;branch=main;protocol=https;
file://CVE-2026-59200.patch \
file://CVE-2026-59203.patch \
file://CVE-2026-59205.patch \
+ file://CVE-2026-112393.patch \
"
SRCREV = "3c41c095064200a02672d89cc5ff629eaf4b0d4f"
This patch applies the upstream fix given by the commit in [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 <kris.gavvala@windriver.com> --- .../python3-pillow/CVE-2026-112393.patch | 124 ++++++++++++++++++ .../python/python3-pillow_12.2.0.bb | 1 + 2 files changed, 125 insertions(+) create mode 100644 meta-python/recipes-devtools/python/python3-pillow/CVE-2026-112393.patch