Merge pull request #5377 from hugovk/security-and-release-notes

Security fixes for 8.2.0
This commit is contained in:
Hugo van Kemenade 2021-04-01 20:00:22 +03:00 committed by GitHub
commit ee635befc6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 294 additions and 99 deletions

View File

@ -52,6 +52,7 @@ class TestDecompressionBomb:
with Image.open(TEST_FILE): with Image.open(TEST_FILE):
pass pass
@pytest.mark.xfail(reason="different exception")
def test_exception_ico(self): def test_exception_ico(self):
with pytest.raises(Image.DecompressionBombError): with pytest.raises(Image.DecompressionBombError):
with Image.open("Tests/images/decompression_bomb.ico"): with Image.open("Tests/images/decompression_bomb.ico"):

View File

@ -312,7 +312,7 @@ def test_apng_syntax_errors():
exception = e exception = e
assert exception is None assert exception is None
with pytest.raises(SyntaxError): with pytest.raises(OSError):
with Image.open("Tests/images/apng/syntax_num_frames_high.png") as im: with Image.open("Tests/images/apng/syntax_num_frames_high.png") as im:
im.seek(im.n_frames - 1) im.seek(im.n_frames - 1)
im.load() im.load()

View File

@ -1,3 +1,5 @@
import pytest
from PIL import Image from PIL import Image
from .helper import assert_image_equal_tofile from .helper import assert_image_equal_tofile
@ -16,3 +18,22 @@ def test_load_blp2_dxt1():
def test_load_blp2_dxt1a(): def test_load_blp2_dxt1a():
with Image.open("Tests/images/blp/blp2_dxt1a.blp") as im: with Image.open("Tests/images/blp/blp2_dxt1a.blp") as im:
assert_image_equal_tofile(im, "Tests/images/blp/blp2_dxt1a.png") assert_image_equal_tofile(im, "Tests/images/blp/blp2_dxt1a.png")
@pytest.mark.parametrize(
"test_file",
[
"Tests/images/timeout-060745d3f534ad6e4128c51d336ea5489182c69d.blp",
"Tests/images/timeout-31c8f86233ea728339c6e586be7af661a09b5b98.blp",
"Tests/images/timeout-60d8b7c8469d59fc9ffff6b3a3dc0faeae6ea8ee.blp",
"Tests/images/timeout-8073b430977660cdd48d96f6406ddfd4114e69c7.blp",
"Tests/images/timeout-bba4f2e026b5786529370e5dfe9a11b1bf991f07.blp",
"Tests/images/timeout-d6ec061c4afdef39d3edf6da8927240bb07fe9b7.blp",
"Tests/images/timeout-ef9112a065e7183fa7faa2e18929b03e44ee16bf.blp",
],
)
def test_crashes(test_file):
with open(test_file, "rb") as f:
with Image.open(f) as im:
with pytest.raises(OSError):
im.load()

View File

@ -264,3 +264,15 @@ def test_emptyline():
assert image.mode == "RGB" assert image.mode == "RGB"
assert image.size == (460, 352) assert image.size == (460, 352)
assert image.format == "EPS" assert image.format == "EPS"
@pytest.mark.timeout(timeout=5)
@pytest.mark.parametrize(
"test_file",
["Tests/images/timeout-d675703545fee17acab56e5fec644c19979175de.eps"],
)
def test_timeout(test_file):
with open(test_file, "rb") as f:
with pytest.raises(Image.UnidentifiedImageError):
with Image.open(f):
pass

View File

@ -123,3 +123,18 @@ def test_seek():
im.seek(50) im.seek(50)
assert_image_equal_tofile(im, "Tests/images/a_fli.png") assert_image_equal_tofile(im, "Tests/images/a_fli.png")
@pytest.mark.parametrize(
"test_file",
[
"Tests/images/timeout-9139147ce93e20eb14088fe238e541443ffd64b3.fli",
"Tests/images/timeout-bff0a9dc7243a8e6ede2408d2ffa6a9964698b87.fli",
],
)
@pytest.mark.timeout(timeout=3)
def test_timeouts(test_file):
with open(test_file, "rb") as f:
with Image.open(f) as im:
with pytest.raises(OSError):
im.load()

View File

@ -231,3 +231,19 @@ def test_parser_feed():
# Assert # Assert
assert p.image.size == (640, 480) assert p.image.size == (640, 480)
@pytest.mark.parametrize(
"test_file",
[
"Tests/images/crash-4fb027452e6988530aa5dabee76eecacb3b79f8a.j2k",
"Tests/images/crash-7d4c83eb92150fb8f1653a697703ae06ae7c4998.j2k",
"Tests/images/crash-ccca68ff40171fdae983d924e127a721cab2bd50.j2k",
"Tests/images/crash-d2c93af851d3ab9a19e34503626368b2ecde9c03.j2k",
],
)
def test_crashes(test_file):
with open(test_file, "rb") as f:
with Image.open(f) as im:
# Valgrind should not complain here
im.load()

View File

@ -130,3 +130,25 @@ def test_combined_larger_than_size():
with pytest.raises(OSError): with pytest.raises(OSError):
with Image.open("Tests/images/combined_larger_than_size.psd"): with Image.open("Tests/images/combined_larger_than_size.psd"):
pass pass
@pytest.mark.parametrize(
"test_file,raises",
[
(
"Tests/images/timeout-1ee28a249896e05b83840ae8140622de8e648ba9.psd",
Image.UnidentifiedImageError,
),
(
"Tests/images/timeout-598843abc37fc080ec36a2699ebbd44f795d3a6f.psd",
Image.UnidentifiedImageError,
),
("Tests/images/timeout-c8efc3fded6426986ba867a399791bae544f59bc.psd", OSError),
("Tests/images/timeout-dedc7a4ebd856d79b4359bbcc79e8ef231ce38f6.psd", OSError),
],
)
def test_crashes(test_file, raises):
with open(test_file, "rb") as f:
with pytest.raises(raises):
with Image.open(f):
pass

View File

@ -625,9 +625,9 @@ class TestFileTiff:
) )
def test_string_dimension(self): def test_string_dimension(self):
# Assert that an error is raised if one of the dimensions is a string # Assert that an error is raised if one of the dimensions is a string
with pytest.raises(ValueError): with Image.open("Tests/images/string_dimension.tiff") as im:
with Image.open("Tests/images/string_dimension.tiff"): with pytest.raises(OSError):
pass im.load()
@pytest.mark.skipif(not is_win32(), reason="Windows only") @pytest.mark.skipif(not is_win32(), reason="Windows only")

View File

@ -997,3 +997,16 @@ def test_freetype_deprecation(monkeypatch):
# Act / Assert # Act / Assert
with pytest.warns(DeprecationWarning): with pytest.warns(DeprecationWarning):
ImageFont.truetype(FONT_PATH, FONT_SIZE) ImageFont.truetype(FONT_PATH, FONT_SIZE)
@pytest.mark.parametrize(
"test_file",
[
"Tests/fonts/oom-e8e927ba6c0d38274a37c1567560eb33baf74627.ttf",
],
)
def test_oom(test_file):
with open(test_file, "rb") as f:
font = ImageFont.truetype(BytesIO(f.read()))
with pytest.raises(Image.DecompressionBombError):
font.getmask("Test Text")

View File

@ -4,12 +4,6 @@
Deprecations Deprecations
============ ============
Tk/Tcl 8.4
^^^^^^^^^^
Support for Tk/Tcl 8.4 is deprecated and will be removed in Pillow 10.0.0 (2023-01-02),
when Tk/Tcl 8.5 will be the minimum supported.
Categories Categories
^^^^^^^^^^ ^^^^^^^^^^
@ -20,6 +14,12 @@ along with the related ``Image.NORMAL``, ``Image.SEQUENCE`` and
To determine if an image has multiple frames or not, To determine if an image has multiple frames or not,
``getattr(im, "is_animated", False)`` can be used instead. ``getattr(im, "is_animated", False)`` can be used instead.
Tk/Tcl 8.4
^^^^^^^^^^
Support for Tk/Tcl 8.4 is deprecated and will be removed in Pillow 10.0.0 (2023-01-02),
when Tk/Tcl 8.5 will be the minimum supported.
API Changes API Changes
=========== ===========
@ -48,7 +48,7 @@ These changes only affect :py:meth:`~PIL.Image.Image.getexif`, introduced in Pil
Image._MODEINFO Image._MODEINFO
^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^
This internal dictionary has been deprecated by a comment since PIL, and is now This internal dictionary had been deprecated by a comment since PIL, and is now
removed. Instead, ``Image.getmodebase()``, ``Image.getmodetype()``, removed. Instead, ``Image.getmodebase()``, ``Image.getmodetype()``,
``Image.getmodebandnames()``, ``Image.getmodebands()`` or ``ImageMode.getmode()`` ``Image.getmodebandnames()``, ``Image.getmodebands()`` or ``ImageMode.getmode()``
can be used. can be used.
@ -56,6 +56,20 @@ can be used.
API Additions API Additions
============= =============
getxmp() for JPEG images
^^^^^^^^^^^^^^^^^^^^^^^^
A new method has been added to return
`XMP data <https://en.wikipedia.org/wiki/Extensible_Metadata_Platform>`_ for JPEG
images. It reads the XML data into a dictionary of names and values.
For example::
>>> from PIL import Image
>>> with Image.open("Tests/images/xmp_test.jpg") as im:
>>> print(im.getxmp())
{'RDF': {}, 'Description': {'Version': '10.4', 'ProcessVersion': '10.0', ...}, ...}
ImageDraw.rounded_rectangle ImageDraw.rounded_rectangle
^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -71,17 +85,13 @@ create a circle, but not any other ellipse.
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
draw.rounded_rectangle(xy=(10, 20, 190, 180), radius=30, fill="red") draw.rounded_rectangle(xy=(10, 20, 190, 180), radius=30, fill="red")
ImageShow.IPythonViewer ImageOps.autocontrast: preserve_tone
^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
If IPython is present, this new :py:class:`PIL.ImageShow.Viewer` subclass will be The default behaviour of :py:meth:`~PIL.ImageOps.autocontrast` is to normalize
registered. It displays images on all IPython frontends. This will be helpful separate histograms for each color channel, changing the tone of the image. The new
to users of Google Colab, allowing ``im.show()`` to display images. ``preserve_tone`` argument keeps the tone unchanged by using one luminance histogram
for all channels.
It is lower in priority than the other default :py:class:`PIL.ImageShow.Viewer`
instances, so it will only be used by ``im.show()`` or :py:func:`.ImageShow.show()`
if none of the other viewers are available. This means that the behaviour of
:py:class:`PIL.ImageShow` will stay the same for most Pillow users.
ImageShow.GmDisplayViewer ImageShow.GmDisplayViewer
^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^
@ -95,6 +105,18 @@ counterpart. Thus, if both ImageMagick and GraphicsMagick are installed,
ImageMagick, i.e the behaviour stays the same for Pillow users having ImageMagick, i.e the behaviour stays the same for Pillow users having
ImageMagick installed. ImageMagick installed.
ImageShow.IPythonViewer
^^^^^^^^^^^^^^^^^^^^^^^
If IPython is present, this new :py:class:`PIL.ImageShow.Viewer` subclass will be
registered. It displays images on all IPython frontends. This will be helpful
to users of Google Colab, allowing ``im.show()`` to display images.
It is lower in priority than the other default :py:class:`PIL.ImageShow.Viewer`
instances, so it will only be used by ``im.show()`` or :py:func:`.ImageShow.show()`
if none of the other viewers are available. This means that the behaviour of
:py:class:`PIL.ImageShow` will stay the same for most Pillow users.
Saving TIFF with ICC profile Saving TIFF with ICC profile
^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -104,32 +126,59 @@ be specified through a keyword argument::
im.save("out.tif", icc_profile=...) im.save("out.tif", icc_profile=...)
ImageOps.autocontrast: preserve_tone
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The default behaviour of :py:meth:`~PIL.ImageOps.autocontrast` is to normalize
separate histograms for each color channel, changing the tone of the image. The new
``preserve_tone`` argument keeps the tone unchanged by using one luminance histogram
for all channels.
getxmp() for JPEG images
^^^^^^^^^^^^^^^^^^^^^^^^
A new method has been added to return
`XMP data <https://en.wikipedia.org/wiki/Extensible_Metadata_Platform>`_ for JPEG
images. It reads the XML data into a dictionary of names and values.
For example::
>>> from PIL import Image
>>> with Image.open("Tests/images/xmp_test.jpg") as im:
>>> print(im.getxmp())
{'RDF': {}, 'Description': {'Version': '10.4', 'ProcessVersion': '10.0', ...}, ...}
Security Security
======== ========
TODO These were all found with `OSS-Fuzz`_.
:cve:`CVE-2021-25287`, :cve:`CVE-2021-25288`: Fix OOB read in Jpeg2KDecode
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* For J2k images with multiple bands, it's legal to have different widths for each band,
e.g. 1 byte for ``L``, 4 bytes for ``A``.
* This dates to Pillow 2.4.0.
:cve:`CVE-2021-28675`: Fix DOS in PsdImagePlugin
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* :py:class:`.PsdImagePlugin.PsdImageFile` did not sanity check the number of input
layers with regard to the size of the data block, this could lead to a
denial-of-service on :py:meth:`~PIL.Image.open` prior to
:py:meth:`~PIL.Image.Image.load`.
* This dates to the PIL fork.
:cve:`CVE-2021-28676`: Fix FLI DOS
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* ``FliDecode.c`` did not properly check that the block advance was non-zero,
potentially leading to an infinite loop on load.
* This dates to the PIL fork.
:cve:`CVE-2021-28677`: Fix EPS DOS on _open
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* The readline used in EPS has to deal with any combination of ``\r`` and ``\n`` as line
endings. It accidentally used a quadratic method of accumulating lines while looking
for a line ending.
* A malicious EPS file could use this to perform a denial-of-service of Pillow in the
open phase, before an image was accepted for opening.
* This dates to the PIL fork.
:cve:`CVE-2021-28678`: Fix BLP DOS
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* ``BlpImagePlugin`` did not properly check that reads after jumping to file offsets
returned data. This could lead to a denial-of-service where the decoder could be run a
large number of times on empty data.
* This dates to Pillow 5.1.0.
Fix memory DOS in ImageFont
^^^^^^^^^^^^^^^^^^^^^^^^^^^
* A corrupt or specially crafted TTF font could have font metrics that lead to
unreasonably large sizes when rendering text in font. ``ImageFont.py`` did not check
the image size before allocating memory for it.
* This dates to the PIL fork.
Other Changes Other Changes
============= =============
@ -146,6 +195,12 @@ The pixel data is encoded using the format specified in the `CompuServe GIF stan
The older encoder used a variant of run-length encoding that was compatible but less The older encoder used a variant of run-length encoding that was compatible but less
efficient. efficient.
GraphicsMagick
^^^^^^^^^^^^^^
The test suite can now be run on systems which have GraphicsMagick_ but not
ImageMagick_ installed. If both are installed, the tests prefer ImageMagick.
Libraqm and FriBiDi linking Libraqm and FriBiDi linking
^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -170,11 +225,6 @@ PyQt6
Support has been added for PyQt6. If it is installed, it will be used instead of Support has been added for PyQt6. If it is installed, it will be used instead of
PySide6, PyQt5 or PySide2. PySide6, PyQt5 or PySide2.
GraphicsMagick
^^^^^^^^^^^^^^
The test suite can now be run on systems which have GraphicsMagick_ but not
ImageMagick_ installed. If both are installed, the tests prefer ImageMagick.
.. _GraphicsMagick: http://www.graphicsmagick.org/ .. _GraphicsMagick: http://www.graphicsmagick.org/
.. _ImageMagick: https://imagemagick.org/ .. _ImageMagick: https://imagemagick.org/
.. _OSS-Fuzz: https://github.com/google/oss-fuzz

View File

@ -286,33 +286,36 @@ class _BLPBaseDecoder(ImageFile.PyDecoder):
raise OSError("Truncated Blp file") from e raise OSError("Truncated Blp file") from e
return 0, 0 return 0, 0
def _safe_read(self, length):
return ImageFile._safe_read(self.fd, length)
def _read_palette(self): def _read_palette(self):
ret = [] ret = []
for i in range(256): for i in range(256):
try: try:
b, g, r, a = struct.unpack("<4B", self.fd.read(4)) b, g, r, a = struct.unpack("<4B", self._safe_read(4))
except struct.error: except struct.error:
break break
ret.append((b, g, r, a)) ret.append((b, g, r, a))
return ret return ret
def _read_blp_header(self): def _read_blp_header(self):
(self._blp_compression,) = struct.unpack("<i", self.fd.read(4)) (self._blp_compression,) = struct.unpack("<i", self._safe_read(4))
(self._blp_encoding,) = struct.unpack("<b", self.fd.read(1)) (self._blp_encoding,) = struct.unpack("<b", self._safe_read(1))
(self._blp_alpha_depth,) = struct.unpack("<b", self.fd.read(1)) (self._blp_alpha_depth,) = struct.unpack("<b", self._safe_read(1))
(self._blp_alpha_encoding,) = struct.unpack("<b", self.fd.read(1)) (self._blp_alpha_encoding,) = struct.unpack("<b", self._safe_read(1))
(self._blp_mips,) = struct.unpack("<b", self.fd.read(1)) (self._blp_mips,) = struct.unpack("<b", self._safe_read(1))
self.size = struct.unpack("<II", self.fd.read(8)) self.size = struct.unpack("<II", self._safe_read(8))
if self.magic == b"BLP1": if self.magic == b"BLP1":
# Only present for BLP1 # Only present for BLP1
(self._blp_encoding,) = struct.unpack("<i", self.fd.read(4)) (self._blp_encoding,) = struct.unpack("<i", self._safe_read(4))
(self._blp_subtype,) = struct.unpack("<i", self.fd.read(4)) (self._blp_subtype,) = struct.unpack("<i", self._safe_read(4))
self._blp_offsets = struct.unpack("<16I", self.fd.read(16 * 4)) self._blp_offsets = struct.unpack("<16I", self._safe_read(16 * 4))
self._blp_lengths = struct.unpack("<16I", self.fd.read(16 * 4)) self._blp_lengths = struct.unpack("<16I", self._safe_read(16 * 4))
class BLP1Decoder(_BLPBaseDecoder): class BLP1Decoder(_BLPBaseDecoder):
@ -324,7 +327,7 @@ class BLP1Decoder(_BLPBaseDecoder):
if self._blp_encoding in (4, 5): if self._blp_encoding in (4, 5):
data = bytearray() data = bytearray()
palette = self._read_palette() palette = self._read_palette()
_data = BytesIO(self.fd.read(self._blp_lengths[0])) _data = BytesIO(self._safe_read(self._blp_lengths[0]))
while True: while True:
try: try:
(offset,) = struct.unpack("<B", _data.read(1)) (offset,) = struct.unpack("<B", _data.read(1))
@ -346,10 +349,10 @@ class BLP1Decoder(_BLPBaseDecoder):
def _decode_jpeg_stream(self): def _decode_jpeg_stream(self):
from PIL.JpegImagePlugin import JpegImageFile from PIL.JpegImagePlugin import JpegImageFile
(jpeg_header_size,) = struct.unpack("<I", self.fd.read(4)) (jpeg_header_size,) = struct.unpack("<I", self._safe_read(4))
jpeg_header = self.fd.read(jpeg_header_size) jpeg_header = self._safe_read(jpeg_header_size)
self.fd.read(self._blp_offsets[0] - self.fd.tell()) # What IS this? self._safe_read(self._blp_offsets[0] - self.fd.tell()) # What IS this?
data = self.fd.read(self._blp_lengths[0]) data = self._safe_read(self._blp_lengths[0])
data = jpeg_header + data data = jpeg_header + data
data = BytesIO(data) data = BytesIO(data)
image = JpegImageFile(data) image = JpegImageFile(data)
@ -370,7 +373,7 @@ class BLP2Decoder(_BLPBaseDecoder):
# Uncompressed or DirectX compression # Uncompressed or DirectX compression
if self._blp_encoding == BLP_ENCODING_UNCOMPRESSED: if self._blp_encoding == BLP_ENCODING_UNCOMPRESSED:
_data = BytesIO(self.fd.read(self._blp_lengths[0])) _data = BytesIO(self._safe_read(self._blp_lengths[0]))
while True: while True:
try: try:
(offset,) = struct.unpack("<B", _data.read(1)) (offset,) = struct.unpack("<B", _data.read(1))
@ -384,20 +387,20 @@ class BLP2Decoder(_BLPBaseDecoder):
linesize = (self.size[0] + 3) // 4 * 8 linesize = (self.size[0] + 3) // 4 * 8
for yb in range((self.size[1] + 3) // 4): for yb in range((self.size[1] + 3) // 4):
for d in decode_dxt1( for d in decode_dxt1(
self.fd.read(linesize), alpha=bool(self._blp_alpha_depth) self._safe_read(linesize), alpha=bool(self._blp_alpha_depth)
): ):
data += d data += d
elif self._blp_alpha_encoding == BLP_ALPHA_ENCODING_DXT3: elif self._blp_alpha_encoding == BLP_ALPHA_ENCODING_DXT3:
linesize = (self.size[0] + 3) // 4 * 16 linesize = (self.size[0] + 3) // 4 * 16
for yb in range((self.size[1] + 3) // 4): for yb in range((self.size[1] + 3) // 4):
for d in decode_dxt3(self.fd.read(linesize)): for d in decode_dxt3(self._safe_read(linesize)):
data += d data += d
elif self._blp_alpha_encoding == BLP_ALPHA_ENCODING_DXT5: elif self._blp_alpha_encoding == BLP_ALPHA_ENCODING_DXT5:
linesize = (self.size[0] + 3) // 4 * 16 linesize = (self.size[0] + 3) // 4 * 16
for yb in range((self.size[1] + 3) // 4): for yb in range((self.size[1] + 3) // 4):
for d in decode_dxt5(self.fd.read(linesize)): for d in decode_dxt5(self._safe_read(linesize)):
data += d data += d
else: else:
raise BLPFormatError( raise BLPFormatError(

View File

@ -170,12 +170,12 @@ class PSFile:
self.fp.seek(offset, whence) self.fp.seek(offset, whence)
def readline(self): def readline(self):
s = self.char or b"" s = [self.char or b""]
self.char = None self.char = None
c = self.fp.read(1) c = self.fp.read(1)
while c not in b"\r\n": while (c not in b"\r\n") and len(c):
s = s + c s.append(c)
c = self.fp.read(1) c = self.fp.read(1)
self.char = self.fp.read(1) self.char = self.fp.read(1)
@ -183,7 +183,7 @@ class PSFile:
if self.char in b"\r\n": if self.char in b"\r\n":
self.char = None self.char = None
return s.decode("latin-1") return b"".join(s).decode("latin-1")
def _accept(prefix): def _accept(prefix):

View File

@ -545,12 +545,18 @@ def _safe_read(fp, size):
:param fp: File handle. Must implement a <b>read</b> method. :param fp: File handle. Must implement a <b>read</b> method.
:param size: Number of bytes to read. :param size: Number of bytes to read.
:returns: A string containing up to <i>size</i> bytes of data. :returns: A string containing <i>size</i> bytes of data.
Raises an OSError if the file is truncated and the read cannot be completed
""" """
if size <= 0: if size <= 0:
return b"" return b""
if size <= SAFEBLOCK: if size <= SAFEBLOCK:
return fp.read(size) data = fp.read(size)
if len(data) < size:
raise OSError("Truncated File Read")
return data
data = [] data = []
while size > 0: while size > 0:
block = fp.read(min(size, SAFEBLOCK)) block = fp.read(min(size, SAFEBLOCK))
@ -558,6 +564,8 @@ def _safe_read(fp, size):
break break
data.append(block) data.append(block)
size -= len(block) size -= len(block)
if sum(len(d) for d in data) < size:
raise OSError("Truncated File Read")
return b"".join(data) return b"".join(data)

View File

@ -669,6 +669,7 @@ class FreeTypeFont:
) )
size = size[0] + stroke_width * 2, size[1] + stroke_width * 2 size = size[0] + stroke_width * 2, size[1] + stroke_width * 2
offset = offset[0] - stroke_width, offset[1] - stroke_width offset = offset[0] - stroke_width, offset[1] - stroke_width
Image._decompression_bomb_check(size)
im = fill("RGBA" if mode == "RGBA" else "L", size, 0) im = fill("RGBA" if mode == "RGBA" else "L", size, 0)
self.font.render( self.font.render(
text, im.id, mode, direction, features, language, stroke_width, ink text, im.id, mode, direction, features, language, stroke_width, ink

View File

@ -119,7 +119,8 @@ class PsdImageFile(ImageFile.ImageFile):
end = self.fp.tell() + size end = self.fp.tell() + size
size = i32(read(4)) size = i32(read(4))
if size: if size:
self.layers = _layerinfo(self.fp) _layer_data = io.BytesIO(ImageFile._safe_read(self.fp, size))
self.layers = _layerinfo(_layer_data, size)
self.fp.seek(end) self.fp.seek(end)
self.n_frames = len(self.layers) self.n_frames = len(self.layers)
self.is_animated = self.n_frames > 1 self.is_animated = self.n_frames > 1
@ -171,11 +172,20 @@ class PsdImageFile(ImageFile.ImageFile):
self.__fp = None self.__fp = None
def _layerinfo(file): def _layerinfo(fp, ct_bytes):
# read layerinfo block # read layerinfo block
layers = [] layers = []
read = file.read
for i in range(abs(i16(read(2)))): def read(size):
return ImageFile._safe_read(fp, size)
ct = i16(read(2))
# sanity check
if ct_bytes < (abs(ct) * 20):
raise SyntaxError("Layer block too short for number of layers requested")
for i in range(abs(ct)):
# bounding box # bounding box
y0 = i32(read(4)) y0 = i32(read(4))
@ -186,7 +196,8 @@ def _layerinfo(file):
# image info # image info
info = [] info = []
mode = [] mode = []
types = list(range(i16(read(2)))) ct_types = i16(read(2))
types = list(range(ct_types))
if len(types) > 4: if len(types) > 4:
continue continue
@ -219,16 +230,16 @@ def _layerinfo(file):
size = i32(read(4)) # length of the extra data field size = i32(read(4)) # length of the extra data field
combined = 0 combined = 0
if size: if size:
data_end = file.tell() + size data_end = fp.tell() + size
length = i32(read(4)) length = i32(read(4))
if length: if length:
file.seek(length - 16, io.SEEK_CUR) fp.seek(length - 16, io.SEEK_CUR)
combined += length + 4 combined += length + 4
length = i32(read(4)) length = i32(read(4))
if length: if length:
file.seek(length, io.SEEK_CUR) fp.seek(length, io.SEEK_CUR)
combined += length + 4 combined += length + 4
length = i8(read(1)) length = i8(read(1))
@ -238,7 +249,7 @@ def _layerinfo(file):
name = read(length).decode("latin-1", "replace") name = read(length).decode("latin-1", "replace")
combined += length + 1 combined += length + 1
file.seek(data_end) fp.seek(data_end)
layers.append((name, mode, (x0, y0, x1, y1))) layers.append((name, mode, (x0, y0, x1, y1)))
# get tiles # get tiles
@ -246,7 +257,7 @@ def _layerinfo(file):
for name, mode, bbox in layers: for name, mode, bbox in layers:
tile = [] tile = []
for m in mode: for m in mode:
t = _maketile(file, m, bbox, 1) t = _maketile(fp, m, bbox, 1)
if t: if t:
tile.extend(t) tile.extend(t)
layers[i] = name, mode, bbox, tile layers[i] = name, mode, bbox, tile

View File

@ -243,6 +243,11 @@ ImagingFliDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t byt
return -1; return -1;
} }
advance = I32(ptr); advance = I32(ptr);
if (advance == 0 ) {
// If there's no advance, we're in an infinite loop
state->errcode = IMAGING_CODEC_BROKEN;
return -1;
}
if (advance < 0 || advance > bytes) { if (advance < 0 || advance > bytes) {
state->errcode = IMAGING_CODEC_OVERRUN; state->errcode = IMAGING_CODEC_OVERRUN;
return -1; return -1;

View File

@ -644,7 +644,7 @@ j2k_decode_entry(Imaging im, ImagingCodecState state) {
j2k_unpacker_t unpack = NULL; j2k_unpacker_t unpack = NULL;
size_t buffer_size = 0, tile_bytes = 0; size_t buffer_size = 0, tile_bytes = 0;
unsigned n, tile_height, tile_width; unsigned n, tile_height, tile_width;
int components; int total_component_width = 0;
stream = opj_stream_create(BUFFER_SIZE, OPJ_TRUE); stream = opj_stream_create(BUFFER_SIZE, OPJ_TRUE);
@ -814,23 +814,40 @@ j2k_decode_entry(Imaging im, ImagingCodecState state) {
goto quick_exit; goto quick_exit;
} }
/* Sometimes the tile_info.datasize we get back from openjpeg if (tile_info.nb_comps != image->numcomps) {
is less than numcomps*w*h, and we overflow in the
shuffle stage */
tile_width = tile_info.x1 - tile_info.x0;
tile_height = tile_info.y1 - tile_info.y0;
components = tile_info.nb_comps == 3 ? 4 : tile_info.nb_comps;
if ((tile_width > UINT_MAX / components) ||
(tile_height > UINT_MAX / components) ||
(tile_width > UINT_MAX / (tile_height * components)) ||
(tile_height > UINT_MAX / (tile_width * components))) {
state->errcode = IMAGING_CODEC_BROKEN; state->errcode = IMAGING_CODEC_BROKEN;
state->state = J2K_STATE_FAILED; state->state = J2K_STATE_FAILED;
goto quick_exit; goto quick_exit;
} }
tile_bytes = tile_width * tile_height * components; /* Sometimes the tile_info.datasize we get back from openjpeg
is less than sum(comp_bytes)*w*h, and we overflow in the
shuffle stage */
tile_width = tile_info.x1 - tile_info.x0;
tile_height = tile_info.y1 - tile_info.y0;
/* Total component width = sum (component_width) e.g, it's
legal for an la file to have a 1 byte width for l, and 4 for
a, and then a malicious file could have a smaller tile_bytes
*/
for (n=0; n < tile_info.nb_comps; n++) {
// see csize /acsize calcs
int csize = (image->comps[n].prec + 7) >> 3;
csize = (csize == 3) ? 4 : csize;
total_component_width += csize;
}
if ((tile_width > UINT_MAX / total_component_width) ||
(tile_height > UINT_MAX / total_component_width) ||
(tile_width > UINT_MAX / (tile_height * total_component_width)) ||
(tile_height > UINT_MAX / (tile_width * total_component_width))) {
state->errcode = IMAGING_CODEC_BROKEN;
state->state = J2K_STATE_FAILED;
goto quick_exit;
}
tile_bytes = tile_width * tile_height * total_component_width;
if (tile_bytes > tile_info.data_size) { if (tile_bytes > tile_info.data_size) {
tile_info.data_size = tile_bytes; tile_info.data_size = tile_bytes;