diff --git a/Tests/images/timeout-041dd17dfde800360a47a172269df127af138c6b.dds b/Tests/images/timeout-041dd17dfde800360a47a172269df127af138c6b.dds new file mode 100644 index 00000000000..8831f8fb294 Binary files /dev/null and b/Tests/images/timeout-041dd17dfde800360a47a172269df127af138c6b.dds differ diff --git a/Tests/images/timeout-52d106579505547091ef69b58341351a37c23e31.dds b/Tests/images/timeout-52d106579505547091ef69b58341351a37c23e31.dds new file mode 100644 index 00000000000..2815eb01059 Binary files /dev/null and b/Tests/images/timeout-52d106579505547091ef69b58341351a37c23e31.dds differ diff --git a/Tests/images/timeout-755a4d204f4208e3597ac3391edebee196462bd0.dds b/Tests/images/timeout-755a4d204f4208e3597ac3391edebee196462bd0.dds new file mode 100644 index 00000000000..1debdffa8d9 Binary files /dev/null and b/Tests/images/timeout-755a4d204f4208e3597ac3391edebee196462bd0.dds differ diff --git a/Tests/images/timeout-c60a3d7314213624607bfb3e38d551a8b24a7435.dds b/Tests/images/timeout-c60a3d7314213624607bfb3e38d551a8b24a7435.dds new file mode 100644 index 00000000000..d032ab7fe43 Binary files /dev/null and b/Tests/images/timeout-c60a3d7314213624607bfb3e38d551a8b24a7435.dds differ diff --git a/Tests/test_file_dds.py b/Tests/test_file_dds.py index 931ff02f1fb..6d0ae20eaa4 100644 --- a/Tests/test_file_dds.py +++ b/Tests/test_file_dds.py @@ -540,3 +540,18 @@ def test_save_large_file(tmp_path: Path, pixel_format: str, mode: str) -> None: im = hopper(mode).resize((440, 440)) # should not error in valgrind im.save(tmp_path / "img.dds", pixel_format=pixel_format) + + +@pytest.mark.parametrize( + "test_file", + [ + "Tests/images/timeout-041dd17dfde800360a47a172269df127af138c6b.dds", + "Tests/images/timeout-755a4d204f4208e3597ac3391edebee196462bd0.dds", + "Tests/images/timeout-52d106579505547091ef69b58341351a37c23e31.dds", + "Tests/images/timeout-c60a3d7314213624607bfb3e38d551a8b24a7435.dds", + ], +) +def test_not_enough_image_data(test_file: str) -> None: + with Image.open(test_file) as im: + with pytest.raises(ValueError, match="not enough image data"): + im.load() diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py index 312f602a6b1..897c0741ff4 100644 --- a/src/PIL/DdsImagePlugin.py +++ b/src/PIL/DdsImagePlugin.py @@ -489,9 +489,14 @@ class DdsRgbDecoder(ImageFile.PyDecoder): _pulls_fd = True def decode(self, buffer: bytes | Image.SupportsArrayInterface) -> tuple[int, int]: - assert self.fd is not None bitcount, masks = self.args + data = bytearray() + bytecount = bitcount // 8 + if not bytecount: + self.set_as_raw(data) + return -1, 0 + # Some masks will be padded with zeros, e.g. R 0b11 G 0b1100 # Calculate how many zeros each mask is padded with mask_offsets = [] @@ -505,11 +510,13 @@ def decode(self, buffer: bytes | Image.SupportsArrayInterface) -> tuple[int, int mask_offsets.append(offset) mask_totals.append(mask >> offset) - data = bytearray() - bytecount = bitcount // 8 + assert self.fd is not None dest_length = self.state.xsize * self.state.ysize * len(masks) while len(data) < dest_length: - value = int.from_bytes(self.fd.read(bytecount), "little") + bytes_read = self.fd.read(bytecount) + if len(bytes_read) < bytecount: + break + value = int.from_bytes(bytes_read, "little") for i, mask in enumerate(masks): masked_value = value & mask # Remove the zero padding, and scale it to 8 bits