mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-01-12 18:26:17 +03:00
Merge pull request #5377 from hugovk/security-and-release-notes
Security fixes for 8.2.0
This commit is contained in:
commit
ee635befc6
BIN
Tests/fonts/oom-e8e927ba6c0d38274a37c1567560eb33baf74627.ttf
Normal file
BIN
Tests/fonts/oom-e8e927ba6c0d38274a37c1567560eb33baf74627.ttf
Normal file
Binary file not shown.
BIN
Tests/images/crash-4fb027452e6988530aa5dabee76eecacb3b79f8a.j2k
Normal file
BIN
Tests/images/crash-4fb027452e6988530aa5dabee76eecacb3b79f8a.j2k
Normal file
Binary file not shown.
BIN
Tests/images/crash-7d4c83eb92150fb8f1653a697703ae06ae7c4998.j2k
Normal file
BIN
Tests/images/crash-7d4c83eb92150fb8f1653a697703ae06ae7c4998.j2k
Normal file
Binary file not shown.
BIN
Tests/images/crash-ccca68ff40171fdae983d924e127a721cab2bd50.j2k
Normal file
BIN
Tests/images/crash-ccca68ff40171fdae983d924e127a721cab2bd50.j2k
Normal file
Binary file not shown.
BIN
Tests/images/crash-d2c93af851d3ab9a19e34503626368b2ecde9c03.j2k
Normal file
BIN
Tests/images/crash-d2c93af851d3ab9a19e34503626368b2ecde9c03.j2k
Normal file
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.
|
@ -52,6 +52,7 @@ class TestDecompressionBomb:
|
|||
with Image.open(TEST_FILE):
|
||||
pass
|
||||
|
||||
@pytest.mark.xfail(reason="different exception")
|
||||
def test_exception_ico(self):
|
||||
with pytest.raises(Image.DecompressionBombError):
|
||||
with Image.open("Tests/images/decompression_bomb.ico"):
|
||||
|
|
|
@ -312,7 +312,7 @@ def test_apng_syntax_errors():
|
|||
exception = e
|
||||
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:
|
||||
im.seek(im.n_frames - 1)
|
||||
im.load()
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import pytest
|
||||
|
||||
from PIL import Image
|
||||
|
||||
from .helper import assert_image_equal_tofile
|
||||
|
@ -16,3 +18,22 @@ def test_load_blp2_dxt1():
|
|||
def test_load_blp2_dxt1a():
|
||||
with Image.open("Tests/images/blp/blp2_dxt1a.blp") as im:
|
||||
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()
|
||||
|
|
|
@ -264,3 +264,15 @@ def test_emptyline():
|
|||
assert image.mode == "RGB"
|
||||
assert image.size == (460, 352)
|
||||
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
|
||||
|
|
|
@ -123,3 +123,18 @@ def test_seek():
|
|||
im.seek(50)
|
||||
|
||||
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()
|
||||
|
|
|
@ -231,3 +231,19 @@ def test_parser_feed():
|
|||
|
||||
# Assert
|
||||
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()
|
||||
|
|
|
@ -130,3 +130,25 @@ def test_combined_larger_than_size():
|
|||
with pytest.raises(OSError):
|
||||
with Image.open("Tests/images/combined_larger_than_size.psd"):
|
||||
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
|
||||
|
|
|
@ -625,9 +625,9 @@ class TestFileTiff:
|
|||
)
|
||||
def test_string_dimension(self):
|
||||
# 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"):
|
||||
pass
|
||||
with Image.open("Tests/images/string_dimension.tiff") as im:
|
||||
with pytest.raises(OSError):
|
||||
im.load()
|
||||
|
||||
|
||||
@pytest.mark.skipif(not is_win32(), reason="Windows only")
|
||||
|
|
|
@ -997,3 +997,16 @@ def test_freetype_deprecation(monkeypatch):
|
|||
# Act / Assert
|
||||
with pytest.warns(DeprecationWarning):
|
||||
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")
|
||||
|
|
|
@ -4,12 +4,6 @@
|
|||
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
|
||||
^^^^^^^^^^
|
||||
|
||||
|
@ -20,6 +14,12 @@ along with the related ``Image.NORMAL``, ``Image.SEQUENCE`` and
|
|||
To determine if an image has multiple frames or not,
|
||||
``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
|
||||
===========
|
||||
|
||||
|
@ -48,7 +48,7 @@ These changes only affect :py:meth:`~PIL.Image.Image.getexif`, introduced in Pil
|
|||
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()``,
|
||||
``Image.getmodebandnames()``, ``Image.getmodebands()`` or ``ImageMode.getmode()``
|
||||
can be used.
|
||||
|
@ -56,6 +56,20 @@ can be used.
|
|||
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
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
|
@ -71,17 +85,13 @@ create a circle, but not any other ellipse.
|
|||
draw = ImageDraw.Draw(im)
|
||||
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
|
||||
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.
|
||||
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.
|
||||
|
||||
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 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
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
|
@ -104,32 +126,59 @@ be specified through a keyword argument::
|
|||
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
|
||||
========
|
||||
|
||||
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
|
||||
=============
|
||||
|
@ -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
|
||||
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
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
|
@ -170,11 +225,6 @@ PyQt6
|
|||
Support has been added for PyQt6. If it is installed, it will be used instead of
|
||||
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/
|
||||
.. _ImageMagick: https://imagemagick.org/
|
||||
.. _OSS-Fuzz: https://github.com/google/oss-fuzz
|
||||
|
|
|
@ -286,33 +286,36 @@ class _BLPBaseDecoder(ImageFile.PyDecoder):
|
|||
raise OSError("Truncated Blp file") from e
|
||||
return 0, 0
|
||||
|
||||
def _safe_read(self, length):
|
||||
return ImageFile._safe_read(self.fd, length)
|
||||
|
||||
def _read_palette(self):
|
||||
ret = []
|
||||
for i in range(256):
|
||||
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:
|
||||
break
|
||||
ret.append((b, g, r, a))
|
||||
return ret
|
||||
|
||||
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_alpha_depth,) = struct.unpack("<b", self.fd.read(1))
|
||||
(self._blp_alpha_encoding,) = struct.unpack("<b", self.fd.read(1))
|
||||
(self._blp_mips,) = 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._safe_read(1))
|
||||
(self._blp_alpha_encoding,) = struct.unpack("<b", self._safe_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":
|
||||
# Only present for BLP1
|
||||
(self._blp_encoding,) = struct.unpack("<i", self.fd.read(4))
|
||||
(self._blp_subtype,) = struct.unpack("<i", self.fd.read(4))
|
||||
(self._blp_encoding,) = struct.unpack("<i", self._safe_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_lengths = 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._safe_read(16 * 4))
|
||||
|
||||
|
||||
class BLP1Decoder(_BLPBaseDecoder):
|
||||
|
@ -324,7 +327,7 @@ class BLP1Decoder(_BLPBaseDecoder):
|
|||
if self._blp_encoding in (4, 5):
|
||||
data = bytearray()
|
||||
palette = self._read_palette()
|
||||
_data = BytesIO(self.fd.read(self._blp_lengths[0]))
|
||||
_data = BytesIO(self._safe_read(self._blp_lengths[0]))
|
||||
while True:
|
||||
try:
|
||||
(offset,) = struct.unpack("<B", _data.read(1))
|
||||
|
@ -346,10 +349,10 @@ class BLP1Decoder(_BLPBaseDecoder):
|
|||
def _decode_jpeg_stream(self):
|
||||
from PIL.JpegImagePlugin import JpegImageFile
|
||||
|
||||
(jpeg_header_size,) = struct.unpack("<I", self.fd.read(4))
|
||||
jpeg_header = self.fd.read(jpeg_header_size)
|
||||
self.fd.read(self._blp_offsets[0] - self.fd.tell()) # What IS this?
|
||||
data = self.fd.read(self._blp_lengths[0])
|
||||
(jpeg_header_size,) = struct.unpack("<I", self._safe_read(4))
|
||||
jpeg_header = self._safe_read(jpeg_header_size)
|
||||
self._safe_read(self._blp_offsets[0] - self.fd.tell()) # What IS this?
|
||||
data = self._safe_read(self._blp_lengths[0])
|
||||
data = jpeg_header + data
|
||||
data = BytesIO(data)
|
||||
image = JpegImageFile(data)
|
||||
|
@ -370,7 +373,7 @@ class BLP2Decoder(_BLPBaseDecoder):
|
|||
# Uncompressed or DirectX compression
|
||||
|
||||
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:
|
||||
try:
|
||||
(offset,) = struct.unpack("<B", _data.read(1))
|
||||
|
@ -384,20 +387,20 @@ class BLP2Decoder(_BLPBaseDecoder):
|
|||
linesize = (self.size[0] + 3) // 4 * 8
|
||||
for yb in range((self.size[1] + 3) // 4):
|
||||
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
|
||||
|
||||
elif self._blp_alpha_encoding == BLP_ALPHA_ENCODING_DXT3:
|
||||
linesize = (self.size[0] + 3) // 4 * 16
|
||||
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
|
||||
|
||||
elif self._blp_alpha_encoding == BLP_ALPHA_ENCODING_DXT5:
|
||||
linesize = (self.size[0] + 3) // 4 * 16
|
||||
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
|
||||
else:
|
||||
raise BLPFormatError(
|
||||
|
|
|
@ -170,12 +170,12 @@ class PSFile:
|
|||
self.fp.seek(offset, whence)
|
||||
|
||||
def readline(self):
|
||||
s = self.char or b""
|
||||
s = [self.char or b""]
|
||||
self.char = None
|
||||
|
||||
c = self.fp.read(1)
|
||||
while c not in b"\r\n":
|
||||
s = s + c
|
||||
while (c not in b"\r\n") and len(c):
|
||||
s.append(c)
|
||||
c = self.fp.read(1)
|
||||
|
||||
self.char = self.fp.read(1)
|
||||
|
@ -183,7 +183,7 @@ class PSFile:
|
|||
if self.char in b"\r\n":
|
||||
self.char = None
|
||||
|
||||
return s.decode("latin-1")
|
||||
return b"".join(s).decode("latin-1")
|
||||
|
||||
|
||||
def _accept(prefix):
|
||||
|
|
|
@ -545,12 +545,18 @@ def _safe_read(fp, size):
|
|||
|
||||
:param fp: File handle. Must implement a <b>read</b> method.
|
||||
: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:
|
||||
return b""
|
||||
if size <= SAFEBLOCK:
|
||||
return fp.read(size)
|
||||
data = fp.read(size)
|
||||
if len(data) < size:
|
||||
raise OSError("Truncated File Read")
|
||||
return data
|
||||
data = []
|
||||
while size > 0:
|
||||
block = fp.read(min(size, SAFEBLOCK))
|
||||
|
@ -558,6 +564,8 @@ def _safe_read(fp, size):
|
|||
break
|
||||
data.append(block)
|
||||
size -= len(block)
|
||||
if sum(len(d) for d in data) < size:
|
||||
raise OSError("Truncated File Read")
|
||||
return b"".join(data)
|
||||
|
||||
|
||||
|
|
|
@ -669,6 +669,7 @@ class FreeTypeFont:
|
|||
)
|
||||
size = size[0] + stroke_width * 2, size[1] + stroke_width * 2
|
||||
offset = offset[0] - stroke_width, offset[1] - stroke_width
|
||||
Image._decompression_bomb_check(size)
|
||||
im = fill("RGBA" if mode == "RGBA" else "L", size, 0)
|
||||
self.font.render(
|
||||
text, im.id, mode, direction, features, language, stroke_width, ink
|
||||
|
|
|
@ -119,7 +119,8 @@ class PsdImageFile(ImageFile.ImageFile):
|
|||
end = self.fp.tell() + size
|
||||
size = i32(read(4))
|
||||
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.n_frames = len(self.layers)
|
||||
self.is_animated = self.n_frames > 1
|
||||
|
@ -171,11 +172,20 @@ class PsdImageFile(ImageFile.ImageFile):
|
|||
self.__fp = None
|
||||
|
||||
|
||||
def _layerinfo(file):
|
||||
def _layerinfo(fp, ct_bytes):
|
||||
# read layerinfo block
|
||||
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
|
||||
y0 = i32(read(4))
|
||||
|
@ -186,7 +196,8 @@ def _layerinfo(file):
|
|||
# image info
|
||||
info = []
|
||||
mode = []
|
||||
types = list(range(i16(read(2))))
|
||||
ct_types = i16(read(2))
|
||||
types = list(range(ct_types))
|
||||
if len(types) > 4:
|
||||
continue
|
||||
|
||||
|
@ -219,16 +230,16 @@ def _layerinfo(file):
|
|||
size = i32(read(4)) # length of the extra data field
|
||||
combined = 0
|
||||
if size:
|
||||
data_end = file.tell() + size
|
||||
data_end = fp.tell() + size
|
||||
|
||||
length = i32(read(4))
|
||||
if length:
|
||||
file.seek(length - 16, io.SEEK_CUR)
|
||||
fp.seek(length - 16, io.SEEK_CUR)
|
||||
combined += length + 4
|
||||
|
||||
length = i32(read(4))
|
||||
if length:
|
||||
file.seek(length, io.SEEK_CUR)
|
||||
fp.seek(length, io.SEEK_CUR)
|
||||
combined += length + 4
|
||||
|
||||
length = i8(read(1))
|
||||
|
@ -238,7 +249,7 @@ def _layerinfo(file):
|
|||
name = read(length).decode("latin-1", "replace")
|
||||
combined += length + 1
|
||||
|
||||
file.seek(data_end)
|
||||
fp.seek(data_end)
|
||||
layers.append((name, mode, (x0, y0, x1, y1)))
|
||||
|
||||
# get tiles
|
||||
|
@ -246,7 +257,7 @@ def _layerinfo(file):
|
|||
for name, mode, bbox in layers:
|
||||
tile = []
|
||||
for m in mode:
|
||||
t = _maketile(file, m, bbox, 1)
|
||||
t = _maketile(fp, m, bbox, 1)
|
||||
if t:
|
||||
tile.extend(t)
|
||||
layers[i] = name, mode, bbox, tile
|
||||
|
|
|
@ -243,6 +243,11 @@ ImagingFliDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t byt
|
|||
return -1;
|
||||
}
|
||||
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) {
|
||||
state->errcode = IMAGING_CODEC_OVERRUN;
|
||||
return -1;
|
||||
|
|
|
@ -644,7 +644,7 @@ j2k_decode_entry(Imaging im, ImagingCodecState state) {
|
|||
j2k_unpacker_t unpack = NULL;
|
||||
size_t buffer_size = 0, tile_bytes = 0;
|
||||
unsigned n, tile_height, tile_width;
|
||||
int components;
|
||||
int total_component_width = 0;
|
||||
|
||||
stream = opj_stream_create(BUFFER_SIZE, OPJ_TRUE);
|
||||
|
||||
|
@ -814,23 +814,40 @@ j2k_decode_entry(Imaging im, ImagingCodecState state) {
|
|||
goto quick_exit;
|
||||
}
|
||||
|
||||
/* Sometimes the tile_info.datasize we get back from openjpeg
|
||||
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))) {
|
||||
if (tile_info.nb_comps != image->numcomps) {
|
||||
state->errcode = IMAGING_CODEC_BROKEN;
|
||||
state->state = J2K_STATE_FAILED;
|
||||
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) {
|
||||
tile_info.data_size = tile_bytes;
|
||||
|
|
Loading…
Reference in New Issue
Block a user