Skip to content

Commit ee635be

Browse files
authored
Merge pull request #5377 from hugovk/security-and-release-notes
Security fixes for 8.2.0
2 parents ef5f294 + 694c84f commit ee635be

36 files changed

+285
-90
lines changed
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

Tests/test_decompression_bomb.py

+1
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ def test_exception(self):
5252
with Image.open(TEST_FILE):
5353
pass
5454

55+
@pytest.mark.xfail(reason="different exception")
5556
def test_exception_ico(self):
5657
with pytest.raises(Image.DecompressionBombError):
5758
with Image.open("Tests/images/decompression_bomb.ico"):

Tests/test_file_apng.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,7 @@ def open_frames_zero_default():
312312
exception = e
313313
assert exception is None
314314

315-
with pytest.raises(SyntaxError):
315+
with pytest.raises(OSError):
316316
with Image.open("Tests/images/apng/syntax_num_frames_high.png") as im:
317317
im.seek(im.n_frames - 1)
318318
im.load()

Tests/test_file_blp.py

+21
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import pytest
2+
13
from PIL import Image
24

35
from .helper import assert_image_equal_tofile
@@ -16,3 +18,22 @@ def test_load_blp2_dxt1():
1618
def test_load_blp2_dxt1a():
1719
with Image.open("Tests/images/blp/blp2_dxt1a.blp") as im:
1820
assert_image_equal_tofile(im, "Tests/images/blp/blp2_dxt1a.png")
21+
22+
23+
@pytest.mark.parametrize(
24+
"test_file",
25+
[
26+
"Tests/images/timeout-060745d3f534ad6e4128c51d336ea5489182c69d.blp",
27+
"Tests/images/timeout-31c8f86233ea728339c6e586be7af661a09b5b98.blp",
28+
"Tests/images/timeout-60d8b7c8469d59fc9ffff6b3a3dc0faeae6ea8ee.blp",
29+
"Tests/images/timeout-8073b430977660cdd48d96f6406ddfd4114e69c7.blp",
30+
"Tests/images/timeout-bba4f2e026b5786529370e5dfe9a11b1bf991f07.blp",
31+
"Tests/images/timeout-d6ec061c4afdef39d3edf6da8927240bb07fe9b7.blp",
32+
"Tests/images/timeout-ef9112a065e7183fa7faa2e18929b03e44ee16bf.blp",
33+
],
34+
)
35+
def test_crashes(test_file):
36+
with open(test_file, "rb") as f:
37+
with Image.open(f) as im:
38+
with pytest.raises(OSError):
39+
im.load()

Tests/test_file_eps.py

+12
Original file line numberDiff line numberDiff line change
@@ -264,3 +264,15 @@ def test_emptyline():
264264
assert image.mode == "RGB"
265265
assert image.size == (460, 352)
266266
assert image.format == "EPS"
267+
268+
269+
@pytest.mark.timeout(timeout=5)
270+
@pytest.mark.parametrize(
271+
"test_file",
272+
["Tests/images/timeout-d675703545fee17acab56e5fec644c19979175de.eps"],
273+
)
274+
def test_timeout(test_file):
275+
with open(test_file, "rb") as f:
276+
with pytest.raises(Image.UnidentifiedImageError):
277+
with Image.open(f):
278+
pass

Tests/test_file_fli.py

+15
Original file line numberDiff line numberDiff line change
@@ -123,3 +123,18 @@ def test_seek():
123123
im.seek(50)
124124

125125
assert_image_equal_tofile(im, "Tests/images/a_fli.png")
126+
127+
128+
@pytest.mark.parametrize(
129+
"test_file",
130+
[
131+
"Tests/images/timeout-9139147ce93e20eb14088fe238e541443ffd64b3.fli",
132+
"Tests/images/timeout-bff0a9dc7243a8e6ede2408d2ffa6a9964698b87.fli",
133+
],
134+
)
135+
@pytest.mark.timeout(timeout=3)
136+
def test_timeouts(test_file):
137+
with open(test_file, "rb") as f:
138+
with Image.open(f) as im:
139+
with pytest.raises(OSError):
140+
im.load()

Tests/test_file_jpeg2k.py

+16
Original file line numberDiff line numberDiff line change
@@ -231,3 +231,19 @@ def test_parser_feed():
231231

232232
# Assert
233233
assert p.image.size == (640, 480)
234+
235+
236+
@pytest.mark.parametrize(
237+
"test_file",
238+
[
239+
"Tests/images/crash-4fb027452e6988530aa5dabee76eecacb3b79f8a.j2k",
240+
"Tests/images/crash-7d4c83eb92150fb8f1653a697703ae06ae7c4998.j2k",
241+
"Tests/images/crash-ccca68ff40171fdae983d924e127a721cab2bd50.j2k",
242+
"Tests/images/crash-d2c93af851d3ab9a19e34503626368b2ecde9c03.j2k",
243+
],
244+
)
245+
def test_crashes(test_file):
246+
with open(test_file, "rb") as f:
247+
with Image.open(f) as im:
248+
# Valgrind should not complain here
249+
im.load()

Tests/test_file_psd.py

+22
Original file line numberDiff line numberDiff line change
@@ -130,3 +130,25 @@ def test_combined_larger_than_size():
130130
with pytest.raises(OSError):
131131
with Image.open("Tests/images/combined_larger_than_size.psd"):
132132
pass
133+
134+
135+
@pytest.mark.parametrize(
136+
"test_file,raises",
137+
[
138+
(
139+
"Tests/images/timeout-1ee28a249896e05b83840ae8140622de8e648ba9.psd",
140+
Image.UnidentifiedImageError,
141+
),
142+
(
143+
"Tests/images/timeout-598843abc37fc080ec36a2699ebbd44f795d3a6f.psd",
144+
Image.UnidentifiedImageError,
145+
),
146+
("Tests/images/timeout-c8efc3fded6426986ba867a399791bae544f59bc.psd", OSError),
147+
("Tests/images/timeout-dedc7a4ebd856d79b4359bbcc79e8ef231ce38f6.psd", OSError),
148+
],
149+
)
150+
def test_crashes(test_file, raises):
151+
with open(test_file, "rb") as f:
152+
with pytest.raises(raises):
153+
with Image.open(f):
154+
pass

Tests/test_file_tiff.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -625,9 +625,9 @@ def test_close_on_load_nonexclusive(self, tmp_path):
625625
)
626626
def test_string_dimension(self):
627627
# Assert that an error is raised if one of the dimensions is a string
628-
with pytest.raises(ValueError):
629-
with Image.open("Tests/images/string_dimension.tiff"):
630-
pass
628+
with Image.open("Tests/images/string_dimension.tiff") as im:
629+
with pytest.raises(OSError):
630+
im.load()
631631

632632

633633
@pytest.mark.skipif(not is_win32(), reason="Windows only")

Tests/test_imagefont.py

+13
Original file line numberDiff line numberDiff line change
@@ -997,3 +997,16 @@ def fake_version_module(module):
997997
# Act / Assert
998998
with pytest.warns(DeprecationWarning):
999999
ImageFont.truetype(FONT_PATH, FONT_SIZE)
1000+
1001+
1002+
@pytest.mark.parametrize(
1003+
"test_file",
1004+
[
1005+
"Tests/fonts/oom-e8e927ba6c0d38274a37c1567560eb33baf74627.ttf",
1006+
],
1007+
)
1008+
def test_oom(test_file):
1009+
with open(test_file, "rb") as f:
1010+
font = ImageFont.truetype(BytesIO(f.read()))
1011+
with pytest.raises(Image.DecompressionBombError):
1012+
font.getmask("Test Text")

docs/releasenotes/8.2.0.rst

+92-42
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,6 @@
44
Deprecations
55
============
66

7-
Tk/Tcl 8.4
8-
^^^^^^^^^^
9-
10-
Support for Tk/Tcl 8.4 is deprecated and will be removed in Pillow 10.0.0 (2023-01-02),
11-
when Tk/Tcl 8.5 will be the minimum supported.
12-
137
Categories
148
^^^^^^^^^^
159

@@ -20,6 +14,12 @@ along with the related ``Image.NORMAL``, ``Image.SEQUENCE`` and
2014
To determine if an image has multiple frames or not,
2115
``getattr(im, "is_animated", False)`` can be used instead.
2216

17+
Tk/Tcl 8.4
18+
^^^^^^^^^^
19+
20+
Support for Tk/Tcl 8.4 is deprecated and will be removed in Pillow 10.0.0 (2023-01-02),
21+
when Tk/Tcl 8.5 will be the minimum supported.
22+
2323
API Changes
2424
===========
2525

@@ -48,14 +48,28 @@ These changes only affect :py:meth:`~PIL.Image.Image.getexif`, introduced in Pil
4848
Image._MODEINFO
4949
^^^^^^^^^^^^^^^
5050

51-
This internal dictionary has been deprecated by a comment since PIL, and is now
51+
This internal dictionary had been deprecated by a comment since PIL, and is now
5252
removed. Instead, ``Image.getmodebase()``, ``Image.getmodetype()``,
5353
``Image.getmodebandnames()``, ``Image.getmodebands()`` or ``ImageMode.getmode()``
5454
can be used.
5555

5656
API Additions
5757
=============
5858

59+
getxmp() for JPEG images
60+
^^^^^^^^^^^^^^^^^^^^^^^^
61+
62+
A new method has been added to return
63+
`XMP data <https://en.wikipedia.org/wiki/Extensible_Metadata_Platform>`_ for JPEG
64+
images. It reads the XML data into a dictionary of names and values.
65+
66+
For example::
67+
68+
>>> from PIL import Image
69+
>>> with Image.open("Tests/images/xmp_test.jpg") as im:
70+
>>> print(im.getxmp())
71+
{'RDF': {}, 'Description': {'Version': '10.4', 'ProcessVersion': '10.0', ...}, ...}
72+
5973
ImageDraw.rounded_rectangle
6074
^^^^^^^^^^^^^^^^^^^^^^^^^^^
6175

@@ -71,17 +85,13 @@ create a circle, but not any other ellipse.
7185
draw = ImageDraw.Draw(im)
7286
draw.rounded_rectangle(xy=(10, 20, 190, 180), radius=30, fill="red")
7387
74-
ImageShow.IPythonViewer
75-
^^^^^^^^^^^^^^^^^^^^^^^
76-
77-
If IPython is present, this new :py:class:`PIL.ImageShow.Viewer` subclass will be
78-
registered. It displays images on all IPython frontends. This will be helpful
79-
to users of Google Colab, allowing ``im.show()`` to display images.
88+
ImageOps.autocontrast: preserve_tone
89+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
8090

81-
It is lower in priority than the other default :py:class:`PIL.ImageShow.Viewer`
82-
instances, so it will only be used by ``im.show()`` or :py:func:`.ImageShow.show()`
83-
if none of the other viewers are available. This means that the behaviour of
84-
:py:class:`PIL.ImageShow` will stay the same for most Pillow users.
91+
The default behaviour of :py:meth:`~PIL.ImageOps.autocontrast` is to normalize
92+
separate histograms for each color channel, changing the tone of the image. The new
93+
``preserve_tone`` argument keeps the tone unchanged by using one luminance histogram
94+
for all channels.
8595

8696
ImageShow.GmDisplayViewer
8797
^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -95,6 +105,18 @@ counterpart. Thus, if both ImageMagick and GraphicsMagick are installed,
95105
ImageMagick, i.e the behaviour stays the same for Pillow users having
96106
ImageMagick installed.
97107

108+
ImageShow.IPythonViewer
109+
^^^^^^^^^^^^^^^^^^^^^^^
110+
111+
If IPython is present, this new :py:class:`PIL.ImageShow.Viewer` subclass will be
112+
registered. It displays images on all IPython frontends. This will be helpful
113+
to users of Google Colab, allowing ``im.show()`` to display images.
114+
115+
It is lower in priority than the other default :py:class:`PIL.ImageShow.Viewer`
116+
instances, so it will only be used by ``im.show()`` or :py:func:`.ImageShow.show()`
117+
if none of the other viewers are available. This means that the behaviour of
118+
:py:class:`PIL.ImageShow` will stay the same for most Pillow users.
119+
98120
Saving TIFF with ICC profile
99121
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
100122

@@ -104,32 +126,59 @@ be specified through a keyword argument::
104126
im.save("out.tif", icc_profile=...)
105127

106128

107-
ImageOps.autocontrast: preserve_tone
108-
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
129+
Security
130+
========
109131

110-
The default behaviour of :py:meth:`~PIL.ImageOps.autocontrast` is to normalize
111-
separate histograms for each color channel, changing the tone of the image. The new
112-
``preserve_tone`` argument keeps the tone unchanged by using one luminance histogram
113-
for all channels.
132+
These were all found with `OSS-Fuzz`_.
114133

115-
getxmp() for JPEG images
116-
^^^^^^^^^^^^^^^^^^^^^^^^
134+
:cve:`CVE-2021-25287`, :cve:`CVE-2021-25288`: Fix OOB read in Jpeg2KDecode
135+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
117136

118-
A new method has been added to return
119-
`XMP data <https://en.wikipedia.org/wiki/Extensible_Metadata_Platform>`_ for JPEG
120-
images. It reads the XML data into a dictionary of names and values.
137+
* For J2k images with multiple bands, it's legal to have different widths for each band,
138+
e.g. 1 byte for ``L``, 4 bytes for ``A``.
139+
* This dates to Pillow 2.4.0.
121140

122-
For example::
141+
:cve:`CVE-2021-28675`: Fix DOS in PsdImagePlugin
142+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
123143

124-
>>> from PIL import Image
125-
>>> with Image.open("Tests/images/xmp_test.jpg") as im:
126-
>>> print(im.getxmp())
127-
{'RDF': {}, 'Description': {'Version': '10.4', 'ProcessVersion': '10.0', ...}, ...}
144+
* :py:class:`.PsdImagePlugin.PsdImageFile` did not sanity check the number of input
145+
layers with regard to the size of the data block, this could lead to a
146+
denial-of-service on :py:meth:`~PIL.Image.open` prior to
147+
:py:meth:`~PIL.Image.Image.load`.
148+
* This dates to the PIL fork.
128149

129-
Security
130-
========
150+
:cve:`CVE-2021-28676`: Fix FLI DOS
151+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
152+
153+
* ``FliDecode.c`` did not properly check that the block advance was non-zero,
154+
potentially leading to an infinite loop on load.
155+
* This dates to the PIL fork.
156+
157+
:cve:`CVE-2021-28677`: Fix EPS DOS on _open
158+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
131159

132-
TODO
160+
* The readline used in EPS has to deal with any combination of ``\r`` and ``\n`` as line
161+
endings. It accidentally used a quadratic method of accumulating lines while looking
162+
for a line ending.
163+
* A malicious EPS file could use this to perform a denial-of-service of Pillow in the
164+
open phase, before an image was accepted for opening.
165+
* This dates to the PIL fork.
166+
167+
:cve:`CVE-2021-28678`: Fix BLP DOS
168+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
169+
170+
* ``BlpImagePlugin`` did not properly check that reads after jumping to file offsets
171+
returned data. This could lead to a denial-of-service where the decoder could be run a
172+
large number of times on empty data.
173+
* This dates to Pillow 5.1.0.
174+
175+
Fix memory DOS in ImageFont
176+
^^^^^^^^^^^^^^^^^^^^^^^^^^^
177+
178+
* A corrupt or specially crafted TTF font could have font metrics that lead to
179+
unreasonably large sizes when rendering text in font. ``ImageFont.py`` did not check
180+
the image size before allocating memory for it.
181+
* This dates to the PIL fork.
133182

134183
Other Changes
135184
=============
@@ -146,6 +195,12 @@ The pixel data is encoded using the format specified in the `CompuServe GIF stan
146195
The older encoder used a variant of run-length encoding that was compatible but less
147196
efficient.
148197

198+
GraphicsMagick
199+
^^^^^^^^^^^^^^
200+
201+
The test suite can now be run on systems which have GraphicsMagick_ but not
202+
ImageMagick_ installed. If both are installed, the tests prefer ImageMagick.
203+
149204
Libraqm and FriBiDi linking
150205
^^^^^^^^^^^^^^^^^^^^^^^^^^^
151206

@@ -170,11 +225,6 @@ PyQt6
170225
Support has been added for PyQt6. If it is installed, it will be used instead of
171226
PySide6, PyQt5 or PySide2.
172227

173-
GraphicsMagick
174-
^^^^^^^^^^^^^^
175-
176-
The test suite can now be run on systems which have GraphicsMagick_ but not
177-
ImageMagick_ installed. If both are installed, the tests prefer ImageMagick.
178-
179228
.. _GraphicsMagick: http://www.graphicsmagick.org/
180229
.. _ImageMagick: https://imagemagick.org/
230+
.. _OSS-Fuzz: https://github.com/google/oss-fuzz

0 commit comments

Comments
 (0)