diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index ed49f15c0..af2f9b3e8 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -104,7 +104,7 @@ jobs: cibw_arch: arm64_iphonesimulator - name: "iOS x86_64 simulator" platform: ios - os: macos-26-intel + os: macos-15-intel cibw_arch: x86_64_iphonesimulator steps: - uses: actions/checkout@v6 diff --git a/Tests/test_file_mpo.py b/Tests/test_file_mpo.py index 4db62bd6d..7f35693f5 100644 --- a/Tests/test_file_mpo.py +++ b/Tests/test_file_mpo.py @@ -6,7 +6,14 @@ from typing import Any import pytest -from PIL import Image, ImageFile, JpegImagePlugin, MpoImagePlugin +from PIL import ( + Image, + ImageFile, + JpegImagePlugin, + MpoImagePlugin, + TiffImagePlugin, + _binary, +) from .helper import ( assert_image_equal, @@ -145,6 +152,32 @@ def test_parallax() -> None: assert exif.get_ifd(0x927C)[0xB211] == -3.125 +def test_truncated_makernote() -> None: + def check(ifd: TiffImagePlugin.ImageFileDirectory_v2) -> None: + fp = BytesIO() + ifd.save(fp) + + e = Image.Exif() + e.load(fp.getvalue()) + assert e.get_ifd(37500) == {} + + # Nintendo + ifd = TiffImagePlugin.ImageFileDirectory_v2() + ifd[271] = "Nintendo" + ifd[34665] = {37500: b" "} + check(ifd) + + # Fujifilm + for data in ( + b"FUJIFILM", + b"FUJIFILM" + _binary.o32le(50), + b"FUJIFILM" + _binary.o32le(0), + ): + ifd = TiffImagePlugin.ImageFileDirectory_v2() + ifd[34665] = {37500: data} + check(ifd) + + def test_reload_exif_after_seek() -> None: with Image.open("Tests/images/sugarshack.mpo") as im: exif = im.getexif() diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index c6c8467d6..e442471d1 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -16,6 +16,7 @@ from PIL import ( TiffImagePlugin, TiffTags, UnidentifiedImageError, + _binary, ) from PIL.TiffImagePlugin import RESOLUTION_UNIT, X_RESOLUTION, Y_RESOLUTION @@ -941,6 +942,15 @@ class TestFileTiff: 4001, ] + def test_truncated_photoshop_blocks(self) -> None: + with Image.open("Tests/images/hopper.tif") as im: + assert isinstance(im, TiffImagePlugin.TiffImageFile) + im.tag_v2[34377] = b"8BIM" + assert im.get_photoshop_blocks() == {} + + im.tag_v2[34377] = b"8BIM" + _binary.o16be(0) + _binary.o8(2) + b" " * 5 + assert im.get_photoshop_blocks() == {} + def test_tiff_chunks(self, tmp_path: Path) -> None: tmpfile = tmp_path / "temp.tif" diff --git a/src/PIL/Image.py b/src/PIL/Image.py index f154cda2b..bde335504 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -4234,80 +4234,83 @@ class Exif(_ExifBase): if tag == ExifTags.IFD.MakerNote: from .TiffImagePlugin import ImageFileDirectory_v2 - if tag_data.startswith(b"FUJIFILM"): - ifd_offset = i32le(tag_data, 8) - ifd_data = tag_data[ifd_offset:] + try: + if tag_data.startswith(b"FUJIFILM"): + ifd_offset = i32le(tag_data, 8) + ifd_data = tag_data[ifd_offset:] - makernote = {} - for i in range(struct.unpack(" 4: - (offset,) = struct.unpack(" 4: + (offset,) = struct.unpack("H", tag_data[:2])[0]): - ifd_tag, typ, count, data = struct.unpack( - ">HHL4s", tag_data[i * 12 + 2 : (i + 1) * 12 + 2] - ) - if ifd_tag == 0x1101: - # CameraInfo - (offset,) = struct.unpack(">L", data) - self.fp.seek(offset) + if not data: + continue - camerainfo: dict[str, int | bytes] = { - "ModelID": self.fp.read(4) - } + makernote[ifd_tag] = handler( + ImageFileDirectory_v2(), data, False + ) + self._ifds[tag] = dict(self._fixup_dict(makernote)) + elif self.get(0x010F) == "Nintendo": + makernote = {} + for i in range(struct.unpack(">H", tag_data[:2])[0]): + ifd_tag, typ, count, data = struct.unpack( + ">HHL4s", tag_data[i * 12 + 2 : (i + 1) * 12 + 2] + ) + if ifd_tag == 0x1101: + # CameraInfo + (offset,) = struct.unpack(">L", data) + self.fp.seek(offset) - self.fp.read(4) - # Seconds since 2000 - camerainfo["TimeStamp"] = i32le(self.fp.read(12)) + camerainfo: dict[str, int | bytes] = { + "ModelID": self.fp.read(4) + } - self.fp.read(4) - camerainfo["InternalSerialNumber"] = self.fp.read(4) + self.fp.read(4) + # Seconds since 2000 + camerainfo["TimeStamp"] = i32le(self.fp.read(12)) - self.fp.read(12) - parallax = self.fp.read(4) - handler = ImageFileDirectory_v2._load_dispatch[ - TiffTags.FLOAT - ][1] - camerainfo["Parallax"] = handler( - ImageFileDirectory_v2(), parallax, False - )[0] + self.fp.read(4) + camerainfo["InternalSerialNumber"] = self.fp.read(4) - self.fp.read(4) - camerainfo["Category"] = self.fp.read(2) + self.fp.read(12) + parallax = self.fp.read(4) + handler = ImageFileDirectory_v2._load_dispatch[ + TiffTags.FLOAT + ][1] + camerainfo["Parallax"] = handler( + ImageFileDirectory_v2(), parallax, False + )[0] - makernote = {0x1101: camerainfo} - self._ifds[tag] = makernote + self.fp.read(4) + camerainfo["Category"] = self.fp.read(2) + + makernote = {0x1101: camerainfo} + self._ifds[tag] = makernote + except struct.error: + pass else: # Interop ifd = self._get_ifd_dict(tag_data, tag) diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index de2ce066e..3eec94dca 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -1287,10 +1287,13 @@ class TiffImageFile(ImageFile.ImageFile): blocks = {} val = self.tag_v2.get(ExifTags.Base.ImageResources) if val: - while val.startswith(b"8BIM"): + while val.startswith(b"8BIM") and len(val) >= 12: id = i16(val[4:6]) n = math.ceil((val[6] + 1) / 2) * 2 - size = i32(val[6 + n : 10 + n]) + try: + size = i32(val[6 + n : 10 + n]) + except struct.error: + break data = val[10 + n : 10 + n + size] blocks[id] = {"data": data} diff --git a/src/_avif.c b/src/_avif.c index 47a55a53a..feebcf446 100644 --- a/src/_avif.c +++ b/src/_avif.c @@ -721,17 +721,29 @@ _decoder_get_info(AvifDecoderObject *self) { mode = "RGB"; } - if (image->icc.size) { - icc = PyBytes_FromStringAndSize((const char *)image->icc.data, image->icc.size); + if (image->xmp.size) { + xmp = PyBytes_FromStringAndSize((const char *)image->xmp.data, image->xmp.size); + if (!xmp) { + return NULL; + } } if (image->exif.size) { exif = PyBytes_FromStringAndSize((const char *)image->exif.data, image->exif.size); + if (!exif) { + Py_XDECREF(xmp); + return NULL; + } } - if (image->xmp.size) { - xmp = PyBytes_FromStringAndSize((const char *)image->xmp.data, image->xmp.size); + if (image->icc.size) { + icc = PyBytes_FromStringAndSize((const char *)image->icc.data, image->icc.size); + if (!icc) { + Py_XDECREF(xmp); + Py_XDECREF(exif); + return NULL; + } } ret = Py_BuildValue( @@ -822,6 +834,7 @@ _decoder_get_frame(AvifDecoderObject *self, PyObject *args) { if (rgb.height > PY_SSIZE_T_MAX / rgb.rowBytes) { PyErr_SetString(PyExc_MemoryError, "Integer overflow in pixel size"); + avifRGBImageFreePixels(&rgb); return NULL; } @@ -829,6 +842,9 @@ _decoder_get_frame(AvifDecoderObject *self, PyObject *args) { bytes = PyBytes_FromStringAndSize((char *)rgb.pixels, size); avifRGBImageFreePixels(&rgb); + if (!bytes) { + return NULL; + } ret = Py_BuildValue( "SKKK", diff --git a/src/_imaging.c b/src/_imaging.c index 697fe0ce5..ac0317f78 100644 --- a/src/_imaging.c +++ b/src/_imaging.c @@ -254,6 +254,9 @@ void ReleaseArrowSchemaPyCapsule(PyObject *capsule) { struct ArrowSchema *schema = (struct ArrowSchema *)PyCapsule_GetPointer(capsule, "arrow_schema"); + if (!schema) { + return; + } if (schema->release != NULL) { schema->release(schema); } @@ -276,6 +279,9 @@ void ReleaseArrowArrayPyCapsule(PyObject *capsule) { struct ArrowArray *array = (struct ArrowArray *)PyCapsule_GetPointer(capsule, "arrow_array"); + if (!array) { + return; + } if (array->release != NULL) { array->release(array); } @@ -2473,6 +2479,9 @@ _split(ImagingObject *self) { } list = PyTuple_New(self->image->bands); + if (!list) { + return NULL; + } for (i = 0; i < self->image->bands; i++) { imaging_object = PyImagingNew(bands[i]); if (!imaging_object) { @@ -3769,6 +3778,9 @@ _ptr_destructor(PyObject *capsule) { static PyObject * _getattr_ptr(ImagingObject *self, void *closure) { PyObject *capsule = PyCapsule_New(self->image, IMAGING_MAGIC, _ptr_destructor); + if (!capsule) { + return NULL; + } Py_INCREF(self); PyCapsule_SetContext(capsule, self); return capsule; diff --git a/src/_imagingcms.c b/src/_imagingcms.c index 7b4fb00a4..469be05f4 100644 --- a/src/_imagingcms.c +++ b/src/_imagingcms.c @@ -558,7 +558,13 @@ cms_transform_apply(CmsTransformObject *self, PyObject *args) { } im = (Imaging)PyCapsule_GetPointer(i0, IMAGING_MAGIC); + if (!im) { + return NULL; + } imOut = (Imaging)PyCapsule_GetPointer(i1, IMAGING_MAGIC); + if (!imOut) { + return NULL; + } return Py_BuildValue("i", pyCMSdoTransform(im, imOut, self->transform)); } diff --git a/src/_imagingft.c b/src/_imagingft.c index 8395eee2c..5d91eaad6 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -941,8 +941,18 @@ font_render(FontObject *self, PyObject *args) { return NULL; } PyObject *imagePtr = PyObject_GetAttrString(image, "ptr"); + if (!imagePtr) { + PyMem_Del(glyph_info); + Py_DECREF(image); + return NULL; + } im = (Imaging)PyCapsule_GetPointer(imagePtr, IMAGING_MAGIC); - Py_XDECREF(imagePtr); + Py_DECREF(imagePtr); + if (!im) { + PyMem_Del(glyph_info); + Py_DECREF(image); + return NULL; + } x_offset = round(x_offset - stroke_width); y_offset = round(y_offset - stroke_width); diff --git a/src/_imagingmath.c b/src/_imagingmath.c index 48c395900..c04361468 100644 --- a/src/_imagingmath.c +++ b/src/_imagingmath.c @@ -187,8 +187,17 @@ _unop(PyObject *self, PyObject *args) { } unop = (void *)PyCapsule_GetPointer(op, MATH_FUNC_UNOP_MAGIC); + if (!unop) { + return NULL; + } out = (Imaging)PyCapsule_GetPointer(i0, IMAGING_MAGIC); + if (!out) { + return NULL; + } im1 = (Imaging)PyCapsule_GetPointer(i1, IMAGING_MAGIC); + if (!im1) { + return NULL; + } unop(out, im1); @@ -219,9 +228,21 @@ _binop(PyObject *self, PyObject *args) { } binop = (void *)PyCapsule_GetPointer(op, MATH_FUNC_BINOP_MAGIC); + if (!binop) { + return NULL; + } out = (Imaging)PyCapsule_GetPointer(i0, IMAGING_MAGIC); + if (!out) { + return NULL; + } im1 = (Imaging)PyCapsule_GetPointer(i1, IMAGING_MAGIC); + if (!im1) { + return NULL; + } im2 = (Imaging)PyCapsule_GetPointer(i2, IMAGING_MAGIC); + if (!im2) { + return NULL; + } binop(out, im1, im2); diff --git a/src/_imagingmorph.c b/src/_imagingmorph.c index 5995f9d42..b6f307c84 100644 --- a/src/_imagingmorph.c +++ b/src/_imagingmorph.c @@ -53,7 +53,13 @@ apply(PyObject *self, PyObject *args) { } imgin = (Imaging)PyCapsule_GetPointer(i0, IMAGING_MAGIC); + if (!imgin) { + return NULL; + } imgout = (Imaging)PyCapsule_GetPointer(i1, IMAGING_MAGIC); + if (!imgout) { + return NULL; + } width = imgin->xsize; height = imgin->ysize; @@ -143,6 +149,9 @@ match(PyObject *self, PyObject *args) { } imgin = (Imaging)PyCapsule_GetPointer(i0, IMAGING_MAGIC); + if (!imgin) { + return NULL; + } if (imgin->type != IMAGING_TYPE_UINT8 || imgin->bands != 1) { PyErr_SetString(PyExc_RuntimeError, "Unsupported image type"); @@ -185,6 +194,10 @@ match(PyObject *self, PyObject *args) { (b6 << 6) | (b7 << 7) | (b8 << 8)); if (lut[lut_idx]) { PyObject *coordObj = Py_BuildValue("(nn)", col_idx, row_idx); + if (!coordObj) { + Py_DECREF(ret); + return NULL; + } PyList_Append(ret, coordObj); Py_XDECREF(coordObj); } @@ -216,6 +229,9 @@ get_on_pixels(PyObject *self, PyObject *args) { } img = (Imaging)PyCapsule_GetPointer(i0, IMAGING_MAGIC); + if (!img) { + return NULL; + } rows = img->image8; width = img->xsize; height = img->ysize; @@ -230,6 +246,10 @@ get_on_pixels(PyObject *self, PyObject *args) { for (col_idx = 0; col_idx < width; col_idx++) { if (row[col_idx]) { PyObject *coordObj = Py_BuildValue("(nn)", col_idx, row_idx); + if (!coordObj) { + Py_DECREF(ret); + return NULL; + } PyList_Append(ret, coordObj); Py_XDECREF(coordObj); } diff --git a/src/_webp.c b/src/_webp.c index ea7e77133..115141273 100644 --- a/src/_webp.c +++ b/src/_webp.c @@ -262,6 +262,9 @@ _anim_encoder_add(PyObject *self, PyObject *args) { } im = (Imaging)PyCapsule_GetPointer(i0, IMAGING_MAGIC); + if (!im) { + return NULL; + } // Setup config for this frame if (!WebPConfigInit(&config)) { @@ -505,6 +508,9 @@ _anim_decoder_get_next(PyObject *self) { bytes = PyBytes_FromStringAndSize( (char *)buf, decp->info.canvas_width * 4 * decp->info.canvas_height ); + if (!bytes) { + return NULL; + } ret = Py_BuildValue("Si", bytes, timestamp); @@ -607,6 +613,9 @@ WebPEncode_wrapper(PyObject *self, PyObject *args) { } im = (Imaging)PyCapsule_GetPointer(i0, IMAGING_MAGIC); + if (!im) { + return NULL; + } // Setup config for this frame if (!WebPConfigInit(&config)) { diff --git a/src/display.c b/src/display.c index 5b5853a3c..944c60b70 100644 --- a/src/display.c +++ b/src/display.c @@ -480,6 +480,9 @@ PyImaging_GrabClipboardWin32(PyObject *self, PyObject *args) { GlobalUnlock(handle); CloseClipboard(); + if (!result) { + return NULL; + } return Py_BuildValue("zN", format_names[format], result); } diff --git a/src/libImaging/Storage.c b/src/libImaging/Storage.c index c09062c92..c8175612e 100644 --- a/src/libImaging/Storage.c +++ b/src/libImaging/Storage.c @@ -677,9 +677,15 @@ ImagingNewArrow( Imaging im; struct ArrowSchema *schema = (struct ArrowSchema *)PyCapsule_GetPointer(schema_capsule, "arrow_schema"); + if (!schema) { + return NULL; + } struct ArrowArray *external_array = (struct ArrowArray *)PyCapsule_GetPointer(array_capsule, "arrow_array"); + if (!external_array) { + return NULL; + } if (xsize < 0 || ysize < 0) { return (Imaging)ImagingError_ValueError("bad image size"); diff --git a/src/libImaging/codec_fd.c b/src/libImaging/codec_fd.c index dc8577298..c5614e603 100644 --- a/src/libImaging/codec_fd.c +++ b/src/libImaging/codec_fd.c @@ -41,6 +41,9 @@ _imaging_write_pyFd(PyObject *fd, char *src, Py_ssize_t bytes) { PyObject *byteObj; byteObj = PyBytes_FromStringAndSize(src, bytes); + if (!byteObj) { + return -1; + } result = PyObject_CallMethod(fd, "write", "O", byteObj); Py_DECREF(byteObj);