加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
克隆/下载
CVE-2021-28675.patch 15.34 KB
一键复制 编辑 原始数据 按行查看 历史
diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py
index c8d6b6b..033c170 100644
--- a/src/PIL/ImageFile.py
+++ b/src/PIL/ImageFile.py
@@ -30,6 +30,7 @@
import io
import struct
import sys
+import warnings
from . import Image
from ._util import isPath
@@ -39,6 +40,7 @@ MAXBLOCK = 65536
SAFEBLOCK = 1024 * 1024
LOAD_TRUNCATED_IMAGES = False
+"""Whether or not to load truncated image files. User code may change this."""
ERRORS = {
-1: "image buffer overrun error",
@@ -47,21 +49,31 @@ ERRORS = {
-8: "bad configuration",
-9: "out of memory error",
}
+"""Dict of known error codes returned from :meth:`.PyDecoder.decode`."""
-def raise_ioerror(error):
+#
+# --------------------------------------------------------------------
+# Helpers
+
+
+def raise_oserror(error):
try:
message = Image.core.getcodecstatus(error)
except AttributeError:
message = ERRORS.get(error)
if not message:
- message = "decoder error %d" % error
+ message = f"decoder error {error}"
raise OSError(message + " when reading image file")
-#
-# --------------------------------------------------------------------
-# Helpers
+def raise_ioerror(error):
+ warnings.warn(
+ "raise_ioerror is deprecated and will be removed in Pillow 9 (2022-01-02). "
+ "Use raise_oserror instead.",
+ DeprecationWarning,
+ )
+ return raise_oserror(error)
def _tilesort(t):
@@ -75,7 +87,7 @@ def _tilesort(t):
class ImageFile(Image.Image):
- "Base class for image file format handlers."
+ """Base class for image file format handlers."""
def __init__(self, fp=None, filename=None):
super().__init__()
@@ -85,6 +97,8 @@ class ImageFile(Image.Image):
self.custom_mimetype = None
self.tile = None
+ """ A list of tile descriptors, or ``None`` """
+
self.readonly = 1 # until we know better
self.decoderconfig = ()
@@ -112,7 +126,7 @@ class ImageFile(Image.Image):
EOFError, # got header but not the first frame
struct.error,
) as v:
- raise SyntaxError(v)
+ raise SyntaxError(v) from v
if not self.mode or self.size[0] <= 0:
raise SyntaxError("not identified by this driver")
@@ -140,10 +154,10 @@ class ImageFile(Image.Image):
def load(self):
"""Load image data based on tile list"""
- pixel = Image.Image.load(self)
-
if self.tile is None:
raise OSError("cannot load this image")
+
+ pixel = Image.Image.load(self)
if not self.tile:
return pixel
@@ -178,24 +192,14 @@ class ImageFile(Image.Image):
and args[0] in Image._MAPMODES
):
try:
- if hasattr(Image.core, "map"):
- # use built-in mapper WIN32 only
- self.map = Image.core.map(self.filename)
- self.map.seek(offset)
- self.im = self.map.readimage(
- self.mode, self.size, args[1], args[2]
- )
- else:
- # use mmap, if possible
- import mmap
-
- with open(self.filename, "r") as fp:
- self.map = mmap.mmap(
- fp.fileno(), 0, access=mmap.ACCESS_READ
- )
- self.im = Image.core.map_buffer(
- self.map, self.size, decoder_name, extents, offset, args
- )
+ # use mmap, if possible
+ import mmap
+
+ with open(self.filename) as fp:
+ self.map = mmap.mmap(fp.fileno(), 0, access=mmap.ACCESS_READ)
+ self.im = Image.core.map_buffer(
+ self.map, self.size, decoder_name, offset, args
+ )
readonly = 1
# After trashing self.im,
# we might need to reload the palette data.
@@ -231,12 +235,12 @@ class ImageFile(Image.Image):
while True:
try:
s = read(self.decodermaxblock)
- except (IndexError, struct.error):
+ except (IndexError, struct.error) as e:
# truncated png/gif
if LOAD_TRUNCATED_IMAGES:
break
else:
- raise OSError("image file is truncated")
+ raise OSError("image file is truncated") from e
if not s: # truncated jpeg
if LOAD_TRUNCATED_IMAGES:
@@ -244,7 +248,7 @@ class ImageFile(Image.Image):
else:
raise OSError(
"image file is truncated "
- "(%d bytes not processed)" % len(b)
+ f"({len(b)} bytes not processed)"
)
b = b + s
@@ -267,7 +271,7 @@ class ImageFile(Image.Image):
if not self.map and not LOAD_TRUNCATED_IMAGES and err_code < 0:
# still raised if decoder fails to return anything
- raise_ioerror(err_code)
+ raise_oserror(err_code)
return Image.Image.load(self)
@@ -320,7 +324,7 @@ class StubImageFile(ImageFile):
def load(self):
loader = self._load()
if loader is None:
- raise OSError("cannot find loader for this %s file" % self.format)
+ raise OSError(f"cannot find loader for this {self.format} file")
image = loader.load(self)
assert image is not None
# become the other object (!)
@@ -358,7 +362,7 @@ class Parser:
(Consumer) Feed data to the parser.
:param data: A string buffer.
- :exception IOError: If the parser failed to parse the image file.
+ :exception OSError: If the parser failed to parse the image file.
"""
# collect data
@@ -390,7 +394,7 @@ class Parser:
if e < 0:
# decoding error
self.image = None
- raise_ioerror(e)
+ raise_oserror(e)
else:
# end of image
return
@@ -444,7 +448,7 @@ class Parser:
(Consumer) Close the stream.
:returns: An image object.
- :exception IOError: If the parser failed to parse the image file either
+ :exception OSError: If the parser failed to parse the image file either
because it cannot be identified or cannot be
decoded.
"""
@@ -495,7 +499,7 @@ def _save(im, fp, tile, bufsize=0):
try:
fh = fp.fileno()
fp.flush()
- except (AttributeError, io.UnsupportedOperation):
+ except (AttributeError, io.UnsupportedOperation) as exc:
# compress to Python file-compatible object
for e, b, o, a in tile:
e = Image._getencoder(im.mode, e, a, im.encoderconfig)
@@ -512,7 +516,7 @@ def _save(im, fp, tile, bufsize=0):
if s:
break
if s < 0:
- raise OSError("encoder error %d when writing image file" % s)
+ raise OSError(f"encoder error {s} when writing image file") from exc
e.cleanup()
else:
# slight speedup: compress to real file object
@@ -527,7 +531,7 @@ def _save(im, fp, tile, bufsize=0):
else:
s = e.encode_to_file(fh, bufsize)
if s < 0:
- raise OSError("encoder error %d when writing image file" % s)
+ raise OSError(f"encoder error {s} when writing image file")
e.cleanup()
if hasattr(fp, "flush"):
fp.flush()
@@ -541,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 can not 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))
@@ -554,9 +564,13 @@ 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)
+
+
class PyCodecState:
def __init__(self):
self.xsize = 0
@@ -571,7 +585,7 @@ class PyCodecState:
class PyDecoder:
"""
Python implementation of a format decoder. Override this class and
- add the decoding logic in the `decode` method.
+ add the decoding logic in the :meth:`decode` method.
See :ref:`Writing Your Own File Decoder in Python<file-decoders-py>`
"""
@@ -603,9 +617,9 @@ class PyDecoder:
Override to perform the decoding process.
:param buffer: A bytes object with the data to be decoded.
- :returns: A tuple of (bytes consumed, errcode).
+ :returns: A tuple of ``(bytes consumed, errcode)``.
If finished with decoding return <0 for the bytes consumed.
- Err codes are from `ERRORS`
+ Err codes are from :data:`.ImageFile.ERRORS`.
"""
raise NotImplementedError()
@@ -680,4 +694,4 @@ class PyDecoder:
if s[0] >= 0:
raise ValueError("not enough image data")
if s[1] != 0:
- raise ValueError("cannot decode image data")
+ raise ValueError("cannot decode image data")
\ No newline at end of file
diff --git a/src/PIL/PsdImagePlugin.py b/src/PIL/PsdImagePlugin.py
index cceb85c..9e2d2ab 100644
--- a/src/PIL/PsdImagePlugin.py
+++ b/src/PIL/PsdImagePlugin.py
@@ -19,7 +19,9 @@
import io
from . import Image, ImageFile, ImagePalette
-from ._binary import i8, i16be as i16, i32be as i32
+from ._binary import i8
+from ._binary import i16be as i16
+from ._binary import i32be as i32
MODES = {
# (photoshop mode, bits) -> (pil mode, required channels)
@@ -61,12 +63,12 @@ class PsdImageFile(ImageFile.ImageFile):
# header
s = read(26)
- if s[:4] != b"8BPS" or i16(s[4:]) != 1:
+ if not _accept(s) or i16(s, 4) != 1:
raise SyntaxError("not a PSD file")
- psd_bits = i16(s[22:])
- psd_channels = i16(s[12:])
- psd_mode = i16(s[24:])
+ psd_bits = i16(s, 22)
+ psd_channels = i16(s, 12)
+ psd_mode = i16(s, 24)
mode, channels = MODES[(psd_mode, psd_bits)]
@@ -74,7 +76,7 @@ class PsdImageFile(ImageFile.ImageFile):
raise OSError("not enough channels")
self.mode = mode
- self._size = i32(s[18:]), i32(s[14:])
+ self._size = i32(s, 18), i32(s, 14)
#
# color mode data
@@ -117,8 +119,11 @@ 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
#
# image descriptor
@@ -130,14 +135,6 @@ class PsdImageFile(ImageFile.ImageFile):
self.frame = 1
self._min_frame = 1
- @property
- def n_frames(self):
- return len(self.layers)
-
- @property
- def is_animated(self):
- return len(self.layers) > 1
-
def seek(self, layer):
if not self._seek_check(layer):
return
@@ -150,8 +147,8 @@ class PsdImageFile(ImageFile.ImageFile):
self.frame = layer
self.fp = self.__fp
return name, bbox
- except IndexError:
- raise EOFError("no such layer")
+ except IndexError as e:
+ raise EOFError("no such layer") from e
def tell(self):
# return layer number (0=image, 1..max=layers)
@@ -174,12 +171,20 @@ class PsdImageFile(ImageFile.ImageFile):
finally:
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))
@@ -190,7 +195,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
@@ -223,16 +229,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))
@@ -242,7 +248,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
@@ -250,7 +256,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
@@ -295,7 +301,7 @@ def _maketile(file, mode, bbox, channels):
layer += ";I"
tile.append(("packbits", bbox, offset, layer))
for y in range(ysize):
- offset = offset + i16(bytecount[i : i + 2])
+ offset = offset + i16(bytecount, i)
i += 2
file.seek(offset)
@@ -313,3 +319,5 @@ def _maketile(file, mode, bbox, channels):
Image.register_open(PsdImageFile.format, PsdImageFile, _accept)
Image.register_extension(PsdImageFile.format, ".psd")
+
+Image.register_mime(PsdImageFile.format, "image/vnd.adobe.photoshop")
\ No newline at end of file
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化