diff mbox series

[meta-python] python3-pillow: patch CVE-2026-112393

Message ID 20261102211142.319090-1-kris.gavvala@windriver.com
State Under Review
Headers show
Series [meta-python] python3-pillow: patch CVE-2026-112393 | expand

Commit Message

Kris Gavvala Nov. 2, 2026, 9:11 p.m. UTC
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 <kris.gavvala@windriver.com>
---
 .../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

Comments

Tim Orling Nov. 2, 2026, 9:37 p.m. UTC | #1
There is a pillow 13.0.0 release and I would rather see an upgrade than
patches. If this is for a stable release branch, please include the branch
in the subject line, e.g. [wrynose].

On Mon, Nov 2, 2026 at 13:12 Gavvala, Kris via openembedded-devel
<Kris.Gavvala=windriver.com@lists.openembedded.org> wrote:

> 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 <kris.gavvala@windriver.com>
> ---
>  .../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 <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
> +
> 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}
> <http://github.com/python-pillow/Pillow.git;branch=main;protocol=https;tag=$%7BPV%7D>
> \
>             file://0001-support-cross-compiling.patch \
> +           file://CVE-2026-112393.patch \
>             "
>  SRCREV = "bb1d8e8ab8d29048624d96e3ee53cecf7c13d13d"
>
> --
> 2.43.0
>
>
> -=-=-=-=-=-=-=-=-=-=-=-
> Links: You receive all messages sent to this group.
> View/Reply Online (#131753):
> https://lists.openembedded.org/g/openembedded-devel/message/131753
> Mute This Topic: https://lists.openembedded.org/mt/122066176/924729
> Group Owner: openembedded-devel+owner@lists.openembedded.org
> Unsubscribe: https://lists.openembedded.org/g/openembedded-devel/unsub [
> ticotimo@gmail.com]
> -=-=-=-=-=-=-=-=-=-=-=-
>
>
Kris Gavvala Nov. 2, 2026, 10:12 p.m. UTC | #2
Understood. I was going to send the patch to master before backporting 
to stable releases, but if upgrading is better for master then I will do 
that.

Thanks,

Kris Gavvala

On 11/2/26 3:37 PM, Tim Orling wrote:
> **
> *CAUTION: This email comes from a non Wind River email account!*
> Do not click links or open attachments unless you recognize the sender 
> and know the content is safe.
> There is a pillow 13.0.0 release and I would rather see an upgrade 
> than patches. If this is for a stable release branch, please include 
> the branch in the subject line, e.g. [wrynose].
>
> On Mon, Nov 2, 2026 at 13:12 Gavvala, Kris via openembedded-devel 
> <Kris.Gavvala=windriver.com@lists.openembedded.org> wrote:
>
>     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
>     <https://urldefense.com/v3/__https://github.com/python-pillow/Pillow/commit/78c08bd4d9c08037f77465a61d90633f669013f4__;!!AjveYdw8EvQ!Yojr6TbFBodsGm8doMeEs_UcyjH7e95BjPHg8QK8OTo5A3Dy0OcRIOfKF7EtrhKYBBBDShpjtjSrBjCIaCNLIw$>
>     [2]
>     https://github.com/python-pillow/Pillow/security/advisories/GHSA-332p-7hrc-92wf
>     <https://urldefense.com/v3/__https://github.com/python-pillow/Pillow/security/advisories/GHSA-332p-7hrc-92wf__;!!AjveYdw8EvQ!Yojr6TbFBodsGm8doMeEs_UcyjH7e95BjPHg8QK8OTo5A3Dy0OcRIOfKF7EtrhKYBBBDShpjtjSrBjCE5CP-7g$>
>
>     Signed-off-by: Kris Gavvala <kris.gavvala@windriver.com>
>     ---
>      .../python3-pillow/CVE-2026-112393.patch      | 124
>     ++++++++++++++++++
>      .../python/python3-pillow_12.3.0.bb
>     <https://urldefense.com/v3/__http://python3-pillow_12.3.0.bb__;!!AjveYdw8EvQ!Yojr6TbFBodsGm8doMeEs_UcyjH7e95BjPHg8QK8OTo5A3Dy0OcRIOfKF7EtrhKYBBBDShpjtjSrBjCm5l_KMw$>
>              |   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 <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
>     <mailto:223556219%2BCopilot@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
>     <https://urldefense.com/v3/__https://github.com/python-pillow/Pillow/commit/78c08bd4d9c08037f77465a61d90633f669013f4__;!!AjveYdw8EvQ!Yojr6TbFBodsGm8doMeEs_UcyjH7e95BjPHg8QK8OTo5A3Dy0OcRIOfKF7EtrhKYBBBDShpjtjSrBjCIaCNLIw$>]
>     +
>     +Signed-off-by: Kris Gavvala <kris.gavvala@windriver.com>
>     +---
>     + Tests/test_file_libtiff.py
>     <https://urldefense.com/v3/__http://test_file_libtiff.py__;!!AjveYdw8EvQ!Yojr6TbFBodsGm8doMeEs_UcyjH7e95BjPHg8QK8OTo5A3Dy0OcRIOfKF7EtrhKYBBBDShpjtjSrBjB4c4M0WA$> 
>     | 36 ++++++++++++++++++++++++++++++++++++
>     + src/libImaging/TiffDecode.c | 15 ++++++++++++++-
>     + 2 files changed, 50 insertions(+), 1 deletion(-)
>     +
>     +diff --git a/Tests/test_file_libtiff.py
>     <https://urldefense.com/v3/__http://test_file_libtiff.py__;!!AjveYdw8EvQ!Yojr6TbFBodsGm8doMeEs_UcyjH7e95BjPHg8QK8OTo5A3Dy0OcRIOfKF7EtrhKYBBBDShpjtjSrBjB4c4M0WA$>
>     b/Tests/test_file_libtiff.py
>     <https://urldefense.com/v3/__http://test_file_libtiff.py__;!!AjveYdw8EvQ!Yojr6TbFBodsGm8doMeEs_UcyjH7e95BjPHg8QK8OTo5A3Dy0OcRIOfKF7EtrhKYBBBDShpjtjSrBjB4c4M0WA$>
>     +index c9849a056..84e4299a1 100644
>     +--- a/Tests/test_file_libtiff.py
>     <https://urldefense.com/v3/__http://test_file_libtiff.py__;!!AjveYdw8EvQ!Yojr6TbFBodsGm8doMeEs_UcyjH7e95BjPHg8QK8OTo5A3Dy0OcRIOfKF7EtrhKYBBBDShpjtjSrBjB4c4M0WA$>
>     ++++ b/Tests/test_file_libtiff.py
>     <https://urldefense.com/v3/__http://test_file_libtiff.py__;!!AjveYdw8EvQ!Yojr6TbFBodsGm8doMeEs_UcyjH7e95BjPHg8QK8OTo5A3Dy0OcRIOfKF7EtrhKYBBBDShpjtjSrBjB4c4M0WA$>
>     +@@ -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
>     <https://urldefense.com/v3/__http://python3-pillow_12.3.0.bb__;!!AjveYdw8EvQ!Yojr6TbFBodsGm8doMeEs_UcyjH7e95BjPHg8QK8OTo5A3Dy0OcRIOfKF7EtrhKYBBBDShpjtjSrBjCm5l_KMw$>
>     b/meta-python/recipes-devtools/python/python3-pillow_12.3.0.bb
>     <https://urldefense.com/v3/__http://python3-pillow_12.3.0.bb__;!!AjveYdw8EvQ!Yojr6TbFBodsGm8doMeEs_UcyjH7e95BjPHg8QK8OTo5A3Dy0OcRIOfKF7EtrhKYBBBDShpjtjSrBjCm5l_KMw$>
>     index 1c6668e53d..e4826da1a6 100644
>     --- a/meta-python/recipes-devtools/python/python3-pillow_12.3.0.bb
>     <https://urldefense.com/v3/__http://python3-pillow_12.3.0.bb__;!!AjveYdw8EvQ!Yojr6TbFBodsGm8doMeEs_UcyjH7e95BjPHg8QK8OTo5A3Dy0OcRIOfKF7EtrhKYBBBDShpjtjSrBjCm5l_KMw$>
>     +++ b/meta-python/recipes-devtools/python/python3-pillow_12.3.0.bb
>     <https://urldefense.com/v3/__http://python3-pillow_12.3.0.bb__;!!AjveYdw8EvQ!Yojr6TbFBodsGm8doMeEs_UcyjH7e95BjPHg8QK8OTo5A3Dy0OcRIOfKF7EtrhKYBBBDShpjtjSrBjCm5l_KMw$>
>     @@ -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}
>     <https://urldefense.com/v3/__http://github.com/python-pillow/Pillow.git;branch=main;protocol=https;tag=$*7BPV*7D__;JSU!!AjveYdw8EvQ!Yojr6TbFBodsGm8doMeEs_UcyjH7e95BjPHg8QK8OTo5A3Dy0OcRIOfKF7EtrhKYBBBDShpjtjSrBjACWFfxyA$>
>     \
>     file://0001-support-cross-compiling.patch \
>     + file://CVE-2026-112393.patch \
>                 "
>      SRCREV = "bb1d8e8ab8d29048624d96e3ee53cecf7c13d13d"
>
>     -- 
>     2.43.0
>
>
>     -=-=-=-=-=-=-=-=-=-=-=-
>     Links: You receive all messages sent to this group.
>     View/Reply Online (#131753):
>     https://lists.openembedded.org/g/openembedded-devel/message/131753
>     <https://urldefense.com/v3/__https://lists.openembedded.org/g/openembedded-devel/message/131753__;!!AjveYdw8EvQ!Yojr6TbFBodsGm8doMeEs_UcyjH7e95BjPHg8QK8OTo5A3Dy0OcRIOfKF7EtrhKYBBBDShpjtjSrBjD0zTu62g$>
>     Mute This Topic:
>     https://lists.openembedded.org/mt/122066176/924729
>     <https://urldefense.com/v3/__https://lists.openembedded.org/mt/122066176/924729__;!!AjveYdw8EvQ!Yojr6TbFBodsGm8doMeEs_UcyjH7e95BjPHg8QK8OTo5A3Dy0OcRIOfKF7EtrhKYBBBDShpjtjSrBjDmoU38zg$>
>     Group Owner: openembedded-devel+owner@lists.openembedded.org
>     <mailto:openembedded-devel%2Bowner@lists.openembedded.org>
>     Unsubscribe:
>     https://lists.openembedded.org/g/openembedded-devel/unsub
>     <https://urldefense.com/v3/__https://lists.openembedded.org/g/openembedded-devel/unsub__;!!AjveYdw8EvQ!Yojr6TbFBodsGm8doMeEs_UcyjH7e95BjPHg8QK8OTo5A3Dy0OcRIOfKF7EtrhKYBBBDShpjtjSrBjC2b-x1sg$>
>     [ticotimo@gmail.com]
>     -=-=-=-=-=-=-=-=-=-=-=-
>
diff mbox series

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 <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
+
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"