What did you do?
I opened a truncated QOI file. Pillow reads its 14-byte header as a 1x220 RGBA image, then reaches EOF while decoding the declared pixels.
What did you expect to happen?
The truncated/malformed image should be rejected with Pillow's standard image-decoding exception, for example, an OSError describing a truncated or broken data stream, as other decoders do when reporting a premature EOF.
What actually happened?
QoiDecoder.decode() reaches EOF and indexes the empty result of read(1), so an implementation-level IndexError escapes from Image.load():
QOI (1, 220) RGBA
Traceback (most recent call last):
File "repro.py", line 10, in <module>
im.load()
File ".../PIL/ImageFile.py", line 382, in load
err_code = decoder.decode(b"")[1]
File ".../PIL/QoiImagePlugin.py", line 64, in decode
byte = self.fd.read(1)[0]
IndexError: index out of range
self.fd.read(1) returns b"" at EOF and is indexed without a length check. The one-byte QOI_OP_LUMA read (second_byte = self.fd.read(1)[0]) and the QOI_OP_RGB / QOI_OP_RGBA payload reads (self.fd.read(3) / read(4)) have the same missing short-read handling and can leak similar errors on other truncated inputs.
Those exceptions become an uncaught exception, but not a vulnerability.
What are your OS, Python and Pillow versions?
- OS: Ubuntu 22.04.5 LTS, x86_64
- Python: 3.10.12
- Pillow: 12.3.0
--------------------------------------------------------------------
Pillow 12.3.0
Python 3.10.12 (main, Jun 22 2026, 18:55:27) [GCC 11.4.0]
--------------------------------------------------------------------
--- PIL CORE support ok, compiled for 12.3.0
*** TKINTER support not installed
--- FREETYPE2 support ok, loaded 2.14.3
--- LITTLECMS2 support ok, loaded 2.19
--- WEBP support ok, loaded 1.6.0
--- AVIF support ok, loaded 1.4.2
--- JPEG support ok, compiled for libjpeg-turbo 3.1.4.1
--- OPENJPEG (JPEG2000) support ok, loaded 2.5.4
--- ZLIB (PNG/ZIP) support ok, loaded 1.2.11, compiled for zlib-ng 2.3.3
--- LIBTIFF support ok, loaded 4.7.1
--- RAQM (Bidirectional Text) support ok, loaded 0.10.5, fribidi 1.0.8, harfbuzz 14.2.1
*** LIBIMAGEQUANT (Quantization method) support not installed
--- XCB (X protocol) support ok
--------------------------------------------------------------------
import base64, io
from PIL import Image
data = base64.b64decode("cW9pZgAAAAEAAADc///T//QAAAEAAwAAAAABAA==")
im = Image.open(io.BytesIO(data))
print(im.format, im.size, im.mode) # QOI (1, 220) RGBA
im.load() # IndexError: index out of range
What did you do?
I opened a truncated QOI file. Pillow reads its 14-byte header as a
1x220RGBA image, then reaches EOF while decoding the declared pixels.What did you expect to happen?
The truncated/malformed image should be rejected with Pillow's standard image-decoding exception, for example, an
OSErrordescribing a truncated or broken data stream, as other decoders do when reporting a premature EOF.What actually happened?
QoiDecoder.decode()reaches EOF and indexes the empty result ofread(1), so an implementation-levelIndexErrorescapes fromImage.load():self.fd.read(1)returnsb""at EOF and is indexed without a length check. The one-byteQOI_OP_LUMAread (second_byte = self.fd.read(1)[0]) and theQOI_OP_RGB/QOI_OP_RGBApayload reads (self.fd.read(3)/read(4)) have the same missing short-read handling and can leak similar errors on other truncated inputs.Those exceptions become an uncaught exception, but not a vulnerability.
What are your OS, Python and Pillow versions?