Summary
Image.save() first validates the JPEG qtables sequence and then reads it again during encoding.
If the sequence raises an exception when it is read again, Pillow crashes instead of propagating the exception.
Versions
Pillow 12.3.0, CPython 3.12.3, Ubuntu 24.04 x86_64, glibc 2.39.
Reproducer
from io import BytesIO
from PIL import Image
class SecondIterRaises(list):
def __init__(self, values):
super().__init__(values)
self.calls = 0
def __iter__(self):
self.calls += 1
if self.calls == 1:
return super().__iter__()
raise RuntimeError("second iteration failed")
Image.new("RGB", (1, 1)).save(
BytesIO(),
format="JPEG",
qtables=SecondIterRaises([[1] * 64]),
)
Running it produces:
Segmentation fault (core dumped)
An ASan/UBSan build reports a null-pointer read in get_qtables_arrays() at src/encode.c:1131:
/usr/include/python3.12/object.h:220:16: runtime error:
member access within null pointer of type 'PyObject'
AddressSanitizer: SEGV on unknown address 0x000000000008
The signal is caused by a READ memory access.
#0 Py_TYPE object.h:220:16
#1 get_qtables_arrays src/encode.c:1131:17
#2 PyImaging_JpegEncoderNew src/encode.c:1234:15
I found this while fuzzing Python C extension modules for a small research project.
Summary
Image.save()first validates the JPEGqtablessequence and then reads it again during encoding.If the sequence raises an exception when it is read again, Pillow crashes instead of propagating the exception.
Versions
Pillow 12.3.0, CPython 3.12.3, Ubuntu 24.04 x86_64, glibc 2.39.
Reproducer
Running it produces:
Segmentation fault (core dumped)An ASan/UBSan build reports a null-pointer read in
get_qtables_arrays()atsrc/encode.c:1131:I found this while fuzzing Python C extension modules for a small research project.