python3-pillow: fix CVE-2026-42311

Details: https://nvd.nist.gov/vuln/detail/CVE-2026-42311

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

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

Signed-off-by: Hitendra Prajapati <hprajapati@mvista.com>
Signed-off-by: Anuj Mittal <anuj.mittal@oss.qualcomm.com>
This commit is contained in:
Hitendra Prajapati authored and Anuj Mittal committed 2026-09-28 14:55:29 +05:30
1 parent 90e88f77ab
commit 3e40d728ae
2 files changed
+357

No files matched your search

@@ -0,0 +1,356 @@
From 4bada07dc6c24319edd1eb76f1dd28d968d58207 Mon Sep 17 00:00:00 2001
From: Andrew Murray <radarhere@users.noreply.github.com>
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 <hprajapati@mvista.com>
---
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
@@ -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"