From 7d1af1ec9367e2ea30d75148bef11999f22ad71e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 27 Jun 2026 14:40:29 +1000 Subject: [PATCH] Raise error in frombytes() if P;2L or P;4L data is truncated --- Tests/test_file_pcx.py | 13 +++++++++++++ Tests/test_image_array.py | 6 ++++++ src/PIL/Image.py | 9 +++++++++ 3 files changed, 28 insertions(+) diff --git a/Tests/test_file_pcx.py b/Tests/test_file_pcx.py index 509d93469e6..76fd09dac9b 100644 --- a/Tests/test_file_pcx.py +++ b/Tests/test_file_pcx.py @@ -202,3 +202,16 @@ def test_break_padding(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: for x in range(5): px[x, 3] = 0 _test_buffer_overflow(tmp_path, im, monkeypatch) + + +@pytest.mark.parametrize( + "data_len, rawmode", + ( + (5, "P;4L"), + (3, "P;2L"), + ), +) +def test_truncated(data_len: int, rawmode: str) -> None: + data = b"\x00" * data_len + with pytest.raises(ValueError, match="not enough image data"): + Image.frombuffer("P", (9, 1), data, "raw", rawmode, 0, 1) diff --git a/Tests/test_image_array.py b/Tests/test_image_array.py index 220e128d168..099c08dbb48 100644 --- a/Tests/test_image_array.py +++ b/Tests/test_image_array.py @@ -60,6 +60,9 @@ def __init__(self, img: Image.Image, arr_params: dict[str, Any]) -> None: self.img = img self.__array_interface__ = arr_params + def __len__(self) -> int: + return len(self.img.tobytes()) + def tobytes(self) -> bytes: return self.img.tobytes() @@ -100,6 +103,9 @@ class Wrapper: def __init__(self, arr_params: dict[str, Any]) -> None: self.__array_interface__ = arr_params + def __len__(self) -> int: + return 1 + with pytest.raises(ValueError): wrapped = Wrapper({"shape": (1, 1), "strides": (1, 1), "typestr": "|u1"}) Image.fromarray(wrapped, "L") diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 0d766b6ea36..ebbd8fd35f8 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -943,6 +943,12 @@ def frombytes( # may pass tuple instead of argument list decoder_args = decoder_args[0] + if decoder_args and decoder_args[0] in {"P;2L", "P;4L"}: + multiple = 4 if decoder_args[0] == "P;2L" else 8 + if len(data) % multiple: + msg = "not enough image data" + raise ValueError(msg) + # default format if decoder_name == "raw" and decoder_args == (): decoder_args = self.mode @@ -3350,6 +3356,9 @@ class SupportsArrayInterface(Protocol): def __array_interface__(self) -> dict[str, Any]: raise NotImplementedError() + def __len__(self) -> int: + raise NotImplementedError() + DecoderInput = bytes | bytearray | memoryview | SupportsArrayInterface