Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added Tests/images/psd-large-bytecounts.psd
Binary file not shown.
17 changes: 15 additions & 2 deletions Tests/test_file_psd.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from __future__ import annotations

import io
import sys
import warnings

import pytest
Expand Down Expand Up @@ -192,6 +191,20 @@ def test_unknown_channel_id() -> None:
assert im.layers[0][1] == ""


@pytest.mark.parametrize("bbox", ((1, 0, 0, 1), (0, 1, 1, 0)))
def test_negative_tile_size(bbox: tuple[int, int, int, int]) -> None:
fp = io.BytesIO(b"\x00\x00")
with pytest.raises(ValueError, match="negative tile size"):
PsdImagePlugin._maketile(fp, "RGB", bbox, 3)


def test_large_bytecounts() -> None:
# The declared height requires a packbits bytecounts table of ~26 GB.
# Reading it must fail cleanly instead of attempting a huge allocation.
with pytest.raises(OSError, match="Truncated File Read"):
Image.open("Tests/images/psd-large-bytecounts.psd")


def test_combined_larger_than_size() -> None:
# The combined size of the individual parts is larger than the
# declared 'size' of the extra data field, resulting in a backwards seek.
Expand Down Expand Up @@ -251,5 +264,5 @@ def test_bounds_crash_overflow() -> None:
with Image.open("Tests/images/psd-oob-write-overflow.psd") as im:
assert isinstance(im, PsdImagePlugin.PsdImageFile)
im.load()
with pytest.raises(OverflowError if sys.maxsize <= 2**32 else ValueError):
with pytest.raises(ValueError, match="negative tile size"):
im.seek(im.n_frames)
19 changes: 9 additions & 10 deletions src/PIL/PsdImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -287,16 +287,15 @@ def read(size: int) -> bytes:
def _maketile(
file: IO[bytes], mode: str, bbox: tuple[int, int, int, int], channels: int
) -> list[ImageFile._Tile]:
tiles = []
read = file.read

compression = i16(read(2))

xsize = bbox[2] - bbox[0]
ysize = bbox[3] - bbox[1]
if xsize < 0 or ysize < 0:
msg = "negative tile size"
raise ValueError(msg)

tiles = []
compression = i16(file.read(2))
offset = file.tell()

if compression == 0:
#
# raw compression
Expand All @@ -305,27 +304,27 @@ def _maketile(
if mode == "CMYK":
layer += ";I"
tiles.append(ImageFile._Tile("raw", bbox, offset, layer))
offset = offset + xsize * ysize
offset += xsize * ysize

elif compression == 1:
#
# packbits compression
i = 0
bytecount = read(channels * ysize * 2)
bytecount = ImageFile._safe_read(file, channels * ysize * 2)
offset = file.tell()
for channel in range(channels):
layer = mode[channel]
if mode == "CMYK":
layer += ";I"
tiles.append(ImageFile._Tile("packbits", bbox, offset, layer))
for y in range(ysize):
offset = offset + i16(bytecount, i)
offset += i16(bytecount, i)
i += 2

file.seek(offset)

if offset & 1:
read(1) # padding
file.read(1) # padding

return tiles

Expand Down
Loading