If Makernote is truncated, do not raise struct.error

This commit is contained in:
Andrew Murray 2026-03-25 09:57:10 +11:00
parent 8e9068e36f
commit f551ecdc43
2 changed files with 101 additions and 65 deletions

View File

@ -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()

View File

@ -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("<H", ifd_data[:2])[0]):
ifd_tag, typ, count, data = struct.unpack(
"<HHL4s", ifd_data[i * 12 + 2 : (i + 1) * 12 + 2]
)
try:
(
unit_size,
handler,
) = ImageFileDirectory_v2._load_dispatch[typ]
except KeyError:
continue
size = count * unit_size
if size > 4:
(offset,) = struct.unpack("<L", data)
data = ifd_data[offset - 12 : offset + size - 12]
else:
data = data[:size]
if len(data) != size:
warnings.warn(
"Possibly corrupt EXIF MakerNote data. "
f"Expecting to read {size} bytes but only got "
f"{len(data)}. Skipping tag {ifd_tag}"
makernote = {}
for i in range(struct.unpack("<H", ifd_data[:2])[0]):
ifd_tag, typ, count, data = struct.unpack(
"<HHL4s", ifd_data[i * 12 + 2 : (i + 1) * 12 + 2]
)
continue
try:
(
unit_size,
handler,
) = ImageFileDirectory_v2._load_dispatch[typ]
except KeyError:
continue
size = count * unit_size
if size > 4:
(offset,) = struct.unpack("<L", data)
data = ifd_data[offset - 12 : offset + size - 12]
else:
data = data[:size]
if not data:
continue
if len(data) != size:
warnings.warn(
"Possibly corrupt EXIF MakerNote data. "
f"Expecting to read {size} bytes but only got "
f"{len(data)}. Skipping tag {ifd_tag}"
)
continue
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)
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)