[meta-python][scarthgap][PATCH] python3-pillow: fix CVE-2026-42311

Hitendra Prajapati <[email protected]>
Newsgroups org.openembedded.lists.openembedded-devel
Message-ID <[email protected]>
Details: https://nvd.nist.gov/vuln/detail/CVE-2026-42311

Pick patch from [1] also mentioned at Debian report in [2]

[1] https://github.com/python-pillow/Pillow/commit/58f9a1d166dcb0c274807d4423522d205b0c35ea
[2] https://security-tracker.debian.org/tracker/CVE-2026-42311

Signed-off-by: Hitendra Prajapati <[email protected]>
---
 .../python3-pillow/CVE-2026-42311.patch       | 356 ++++++++++++++++++
 .../python/python3-pillow_10.3.0.bb           |   1 +
 2 files changed, 357 insertions(+)
 create mode 100644 meta-python/recipes-devtools/python/python3-pillow/CVE-2026-42311.patch

diff --git a/meta-python/recipes-devtools/python/python3-pillow/CVE-2026-42311.patch b/meta-python/recipes-devtools/python/python3-pillow/CVE-2026-42311.patch
new file mode 100644
index 0000000000..326b747d1b
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-pillow/CVE-2026-42311.patch
@@ -0,0 +1,356 @@
+From 4bada07dc6c24319edd1eb76f1dd28d968d58207 Mon Sep 17 00:00:00 2001
+From: Andrew Murray <[email protected]>
+Date: Wed, 18 Feb 2026 22:24:03 +1100
+Subject: [PATCH] Avoid overflow by not adding extents together
+
+Reference : https://security-tracker.debian.org/tracker/DSA-6357-1
+
+CVE: CVE-2026-42311
+Upstream-Status: Backport [https://github.com/python-pillow/Pillow/commit/58f9a1d166dcb0c274807d4423522d205b0c35ea]
+Signed-off-by: Hitendra Prajapati <[email protected]>
+---
+ Tests/test_imagefile.py | 70 +++++++++++++++++++++++++++++++++++++++--
+ src/PIL/Image.py        |  4 +--
+ src/PIL/ImageFile.py    | 18 ++++-------
+ src/decode.c            | 61 +++++++++++++++++++++++------------
+ src/encode.c            | 60 ++++++++++++++++++++++-------------
+ 5 files changed, 156 insertions(+), 57 deletions(-)
+
+diff --git a/Tests/test_imagefile.py b/Tests/test_imagefile.py
+index 8aa102729..a00f111e1 100644
+--- a/Tests/test_imagefile.py
++++ b/Tests/test_imagefile.py
+@@ -142,6 +142,27 @@ class TestImageFile:
+         with pytest.raises(SystemError, match="tile cannot extend outside image"):
+             ImageFile._save(im, fp, [ImageFile._Tile("raw", xy + (1, 1), 0, "1")])
+ 
++    def test_extents_none(self) -> None:
++        with Image.open("Tests/images/hopper.jpg") as im:
++            im.tile = [im.tile[0]._replace(extents=None)]
++            im.load()
++
++        for extents in ("invalid", (0,), ("0", "0", "0", "0")):
++            with Image.open("Tests/images/hopper.jpg") as im:
++                im.tile = [im.tile[0]._replace(extents=extents)]  # type: ignore[arg-type]
++                with pytest.raises(ValueError, match="invalid extents"):
++                    im.load()
++
++        im2 = Image.new("L", (1, 1))
++        fp = BytesIO()
++        tile = ImageFile._Tile("jpeg", None, 0, "L")
++        ImageFile._save(im2, fp, [tile])
++
++        for extents in ("invalid", (0,), ("0", "0", "0", "0")):
++            tile = tile._replace(extents=extents)  # type: ignore[arg-type]
++            with pytest.raises(ValueError, match="invalid extents"):
++                ImageFile._save(im2, fp, [tile])
++
+     def test_no_format(self) -> None:
+         buf = BytesIO(b"\x00" * 255)
+ 
+@@ -279,7 +300,20 @@ class TestPyDecoder(CodecsTest):
+         assert MockPyDecoder.last.state.xsize == 200
+         assert MockPyDecoder.last.state.ysize == 200
+ 
+-    def test_negsize(self) -> None:
++    def test_negative_offset(self) -> None:
++        buf = BytesIO(b"\x00" * 255)
++
++        im = MockImageFile(buf)
++        im.tile = [ImageFile._Tile("MOCK", (-10, yoff, xsize, ysize), 32, None)]
++
++        with pytest.raises(ValueError):
++            im.load()
++
++        im.tile = [ImageFile._Tile("MOCK", (xoff, -10, xsize, ysize), 32, None)]
++        with pytest.raises(ValueError):
++            im.load()
++
++    def test_negative_size(self) -> None:
+         buf = BytesIO(b"\x00" * 255)
+ 
+         im = MockImageFile(buf)
+@@ -341,7 +375,39 @@ class TestPyEncoder(CodecsTest):
+         assert MockPyEncoder.last.state.xsize == 200
+         assert MockPyEncoder.last.state.ysize == 200
+ 
+-    def test_negsize(self) -> None:
++    def test_negative_offset(self) -> None:
++        buf = BytesIO(b"\x00" * 255)
++
++        im = MockImageFile(buf)
++
++        fp = BytesIO()
++        MockPyEncoder.last = None
++        with pytest.raises(ValueError):
++            ImageFile._save(
++                im,
++                fp,
++                [
++                    ImageFile._Tile(
++                        "MOCK", (-10, yoff, xoff + xsize, yoff + ysize), 0, "RGB"
++                    )
++                ],
++            )
++        last: MockPyEncoder | None = MockPyEncoder.last
++        assert last
++        assert last.cleanup_called
++
++        with pytest.raises(ValueError):
++            ImageFile._save(
++                im,
++                fp,
++                [
++                    ImageFile._Tile(
++                        "MOCK", (xoff, -10, xoff + xsize, yoff + ysize), 0, "RGB"
++                    )
++                ],
++            )
++
++    def test_negative_size(self) -> None:
+         buf = BytesIO(b"\x00" * 255)
+ 
+         im = MockImageFile(buf)
+diff --git a/src/PIL/Image.py b/src/PIL/Image.py
+index baef0aa11..94e021a48 100644
+--- a/src/PIL/Image.py
++++ b/src/PIL/Image.py
+@@ -759,7 +759,7 @@ class Image:
+ 
+         # unpack data
+         e = _getencoder(self.mode, encoder_name, args)
+-        e.setimage(self.im)
++        e.setimage(self.im, (0, 0) + self.size)
+ 
+         bufsize = max(65536, self.size[0] * 4)  # see RawEncode.c
+ 
+@@ -822,7 +822,7 @@ class Image:
+ 
+         # unpack data
+         d = _getdecoder(self.mode, decoder_name, args)
+-        d.setimage(self.im)
++        d.setimage(self.im, (0, 0) + self.size)
+         s = d.decode(data)
+ 
+         if s[0] >= 0:
+diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py
+index 0283fa2fd..ac14d53ed 100644
+--- a/src/PIL/ImageFile.py
++++ b/src/PIL/ImageFile.py
+@@ -666,28 +666,22 @@ class PyCodec:
+ 
+         if extents:
+             (x0, y0, x1, y1) = extents
+-        else:
+-            (x0, y0, x1, y1) = (0, 0, 0, 0)
+ 
+-        if x0 == 0 and x1 == 0:
+-            self.state.xsize, self.state.ysize = self.im.size
+-        else:
++            if x0 < 0 or y0 < 0 or x1 > self.im.size[0] or y1 > self.im.size[1]:
++                msg = "Tile cannot extend outside image"
++                raise ValueError(msg)
++
+             self.state.xoff = x0
+             self.state.yoff = y0
+             self.state.xsize = x1 - x0
+             self.state.ysize = y1 - y0
++        else:
++            self.state.xsize, self.state.ysize = self.im.size
+ 
+         if self.state.xsize <= 0 or self.state.ysize <= 0:
+             msg = "Size cannot be negative"
+             raise ValueError(msg)
+ 
+-        if (
+-            self.state.xsize + self.state.xoff > self.im.size[0]
+-            or self.state.ysize + self.state.yoff > self.im.size[1]
+-        ):
+-            msg = "Tile cannot extend outside image"
+-            raise ValueError(msg)
+-
+ 
+ class PyDecoder(PyCodec):
+     """
+diff --git a/src/decode.c b/src/decode.c
+index 43fa0ae3e..2b12a29bf 100644
+--- a/src/decode.c
++++ b/src/decode.c
+@@ -154,44 +154,65 @@ PyImaging_AsImaging(PyObject *op);
+ 
+ static PyObject *
+ _setimage(ImagingDecoderObject *decoder, PyObject *args) {
+-    PyObject *op;
++    PyObject *op, *extents;
+     Imaging im;
+     ImagingCodecState state;
+     int x0, y0, x1, y1;
+ 
+-    x0 = y0 = x1 = y1 = 0;
+-
+     /* FIXME: should publish the ImagingType descriptor */
+-    if (!PyArg_ParseTuple(args, "O|(iiii)", &op, &x0, &y0, &x1, &y1)) {
++    if (!PyArg_ParseTuple(args, "OO", &op, &extents)) {
+         return NULL;
+     }
+     im = PyImaging_AsImaging(op);
+     if (!im) {
+         return NULL;
+     }
+-
+-    decoder->im = im;
+-
+-    state = &decoder->state;
+-
+-    /* Setup decoding tile extent */
+-    if (x0 == 0 && x1 == 0) {
+-        state->xsize = im->xsize;
+-        state->ysize = im->ysize;
++    if (extents == Py_None) {
++        x0 = 0;
++        y0 = 0;
++        x1 = im->xsize;
++        y1 = im->ysize;
+     } else {
+-        state->xoff = x0;
+-        state->yoff = y0;
+-        state->xsize = x1 - x0;
+-        state->ysize = y1 - y0;
++        if (!PyTuple_Check(extents) || PyTuple_GET_SIZE(extents) != 4) {
++            PyErr_SetString(PyExc_ValueError, "invalid extents");
++            return NULL;
++        }
++        for (int i = 0; i < 4; i++) {
++            PyObject *extent = PyTuple_GetItem(extents, i);
++            if (!PyLong_Check(extent)) {
++                PyErr_SetString(PyExc_ValueError, "invalid extents");
++                return NULL;
++            }
++            int e = (int)PyLong_AsLong(extent);
++
++            if (i == 0) {
++                x0 = e;
++            } else if (i == 1) {
++                y0 = e;
++            } else if (i == 2) {
++                x1 = e;
++            } else {
++                y1 = e;
++            }
++        }
+     }
+ 
+-    if (state->xoff < 0 || state->xsize <= 0 ||
+-        state->xsize + state->xoff > (int)im->xsize || state->yoff < 0 ||
+-        state->ysize <= 0 || state->ysize + state->yoff > (int)im->ysize) {
++    if (x0 < 0 || y0 < 0 || x1 <= x0 || y1 <= y0 || x1 > (int)im->xsize ||
++        y1 > (int)im->ysize) {
+         PyErr_SetString(PyExc_ValueError, "tile cannot extend outside image");
+         return NULL;
+     }
+ 
++    decoder->im = im;
++
++    state = &decoder->state;
++
++    /* Setup decoding tile extent */
++    state->xoff = x0;
++    state->yoff = y0;
++    state->xsize = x1 - x0;
++    state->ysize = y1 - y0;
++
+     /* Allocate memory buffer (if bits field is set) */
+     if (state->bits > 0) {
+         if (!state->bytes) {
+diff --git a/src/encode.c b/src/encode.c
+index 87426cdec..360f26f97 100644
+--- a/src/encode.c
++++ b/src/encode.c
+@@ -218,45 +218,63 @@ PyImaging_AsImaging(PyObject *op);
+ 
+ static PyObject *
+ _setimage(ImagingEncoderObject *encoder, PyObject *args) {
+-    PyObject *op;
++    PyObject *op, *extents;
+     Imaging im;
+     ImagingCodecState state;
+     Py_ssize_t x0, y0, x1, y1;
+ 
+-    /* Define where image data should be stored */
+-
+-    x0 = y0 = x1 = y1 = 0;
+-
+     /* FIXME: should publish the ImagingType descriptor */
+-    if (!PyArg_ParseTuple(args, "O|(nnnn)", &op, &x0, &y0, &x1, &y1)) {
++    if (!PyArg_ParseTuple(args, "OO", &op, &extents)) {
+         return NULL;
+     }
+     im = PyImaging_AsImaging(op);
+     if (!im) {
+         return NULL;
+     }
+-
+-    encoder->im = im;
+-
+-    state = &encoder->state;
+-
+-    if (x0 == 0 && x1 == 0) {
+-        state->xsize = im->xsize;
+-        state->ysize = im->ysize;
++    if (extents == Py_None) {
++        x0 = 0;
++        y0 = 0;
++        x1 = im->xsize;
++        y1 = im->ysize;
+     } else {
+-        state->xoff = x0;
+-        state->yoff = y0;
+-        state->xsize = x1 - x0;
+-        state->ysize = y1 - y0;
++        if (!PyTuple_Check(extents) || PyTuple_GET_SIZE(extents) != 4) {
++            PyErr_SetString(PyExc_ValueError, "invalid extents");
++            return NULL;
++        }
++        for (int i = 0; i < 4; i++) {
++            PyObject *extent = PyTuple_GetItem(extents, i);
++            if (!PyLong_Check(extent)) {
++                PyErr_SetString(PyExc_ValueError, "invalid extents");
++                return NULL;
++            }
++            Py_ssize_t e = (Py_ssize_t)PyLong_AsLong(extent);
++
++            if (i == 0) {
++                x0 = e;
++            } else if (i == 1) {
++                y0 = e;
++            } else if (i == 2) {
++                x1 = e;
++            } else {
++                y1 = e;
++            }
++        }
+     }
+ 
+-    if (state->xoff < 0 || state->xsize <= 0 ||
+-        state->xsize + state->xoff > im->xsize || state->yoff < 0 ||
+-        state->ysize <= 0 || state->ysize + state->yoff > im->ysize) {
++    if (x0 < 0 || y0 < 0 || x1 <= x0 || y1 <= y0 || x1 > im->xsize || y1 > im->ysize) {
+         PyErr_SetString(PyExc_SystemError, "tile cannot extend outside image");
+         return NULL;
+     }
+ 
++    encoder->im = im;
++
++    state = &encoder->state;
++
++    state->xoff = x0;
++    state->yoff = y0;
++    state->xsize = x1 - x0;
++    state->ysize = y1 - y0;
++
+     /* Allocate memory buffer (if bits field is set) */
+     if (state->bits > 0) {
+         if (state->xsize > ((INT_MAX / state->bits) - 7)) {
+-- 
+2.50.1
+
diff --git a/meta-python/recipes-devtools/python/python3-pillow_10.3.0.bb b/meta-python/recipes-devtools/python/python3-pillow_10.3.0.bb
index 9f1ef87a46..8f7d11195d 100644
--- a/meta-python/recipes-devtools/python/python3-pillow_10.3.0.bb
+++ b/meta-python/recipes-devtools/python/python3-pillow_10.3.0.bb
@@ -10,6 +10,7 @@ SRC_URI = "git://github.com/python-pillow/Pillow.git;branch=main;protocol=https
            file://run-ptest \
            file://CVE-2026-25990.patch \
            file://CVE-2026-40192.patch \
+           file://CVE-2026-42311.patch \
            "
 SRCREV = "5c89d88eee199ba53f64581ea39b6a1bc52feb1a"
 
-- 
2.50.1
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.