Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
systemsmanagement:Ardana:8:CentOS:7.3
python-Pillow
019-CVE-2021-28675.patch
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File 019-CVE-2021-28675.patch of Package python-Pillow
From 22e9bee4ef225c0edbb9323f94c26cee0c623497 Mon Sep 17 00:00:00 2001 From: Eric Soroos <eric-github@soroos.net> Date: Sun, 7 Mar 2021 19:04:25 +0100 Subject: [PATCH] Fix DOS in PSDImagePlugin -- CVE-2021-28675 * PSDImagePlugin did not sanity check the number of input layers and vs the size of the data block, this could lead to a DOS on Image.open prior to Image.load. * This issue dates to the PIL fork --- ...e28a249896e05b83840ae8140622de8e648ba9.psd | Bin 0 -> 555361 bytes ...8843abc37fc080ec36a2699ebbd44f795d3a6f.psd | Bin 0 -> 714605 bytes ...efc3fded6426986ba867a399791bae544f59bc.psd | Bin 0 -> 1004989 bytes ...dc7a4ebd856d79b4359bbcc79e8ef231ce38f6.psd | Bin 0 -> 592243 bytes Tests/test_decompression_bomb.py | 1 + Tests/test_file_apng.py | 2 +- Tests/test_file_psd.py | 15 ++++++++ Tests/test_file_tiff.py | 7 ++-- PIL/ImageFile.py | 14 ++++++-- PIL/PsdImagePlugin.py | 32 ++++++++++++------ 11 files changed, 55 insertions(+), 17 deletions(-) create mode 100644 Tests/images/timeout-1ee28a249896e05b83840ae8140622de8e648ba9.psd create mode 100644 Tests/images/timeout-598843abc37fc080ec36a2699ebbd44f795d3a6f.psd create mode 100644 Tests/images/timeout-c8efc3fded6426986ba867a399791bae544f59bc.psd create mode 100644 Tests/images/timeout-dedc7a4ebd856d79b4359bbcc79e8ef231ce38f6.psd diff --git a/Tests/test_decompression_bomb.py b/Tests/test_decompression_bomb.py index 80ab92666ac..db431337568 100644 --- a/Tests/test_decompression_bomb.py +++ b/Tests/test_decompression_bomb.py @@ -45,7 +45,8 @@ def test_exception(self): self.assertRaises(Image.DecompressionBombError, lambda: Image.open(TEST_FILE)) + # disabled: reason="different exception" - def test_exception_ico(self): + def _test_exception_ico(self): with self.assertRaises(Image.DecompressionBombError): Image.open("Tests/images/decompression_bomb.ico") diff --git a/Tests/test_file_psd.py b/Tests/test_file_psd.py index 87373d2c4fc..8c58310bdb7 100644 --- a/Tests/test_file_psd.py +++ b/Tests/test_file_psd.py @@ -86,9 +86,22 @@ def test_combined_larger_than_size(): # If we instead take the 'size' of the extra data field as the source of truth, # then the seek can't be negative - with self.assertRaises(IOError): + with self.assertRaises(OSError): Image.open("Tests/images/combined_larger_than_size.psd") + def test_crashes(self): + test_files = [ + ("Tests/images/timeout-1ee28a249896e05b83840ae8140622de8e648ba9.psd", IOError), + ("Tests/images/timeout-598843abc37fc080ec36a2699ebbd44f795d3a6f.psd", IOError), + ("Tests/images/timeout-c8efc3fded6426986ba867a399791bae544f59bc.psd", OSError), + ("Tests/images/timeout-dedc7a4ebd856d79b4359bbcc79e8ef231ce38f6.psd", OSError), + ] + for test_file, raises in test_files: + with open(test_file, "rb") as f: + with self.assertRaises(raises): + with Image.open(f): + pass + if __name__ == '__main__': unittest.main() diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index ba7f9a08408..1bc46ee308c 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -496,8 +496,9 @@ def test_close_on_load_nonexclusive(self, tmp_path): def test_string_dimension(self): # Assert that an error is raised if one of the dimensions is a string - with self.assertRaises(ValueError): - Image.open("Tests/images/string_dimension.tiff") + with self.assertRaises(OSError): + with Image.open("Tests/images/string_dimension.tiff") as im: + im.load() @unittest.skipUnless(sys.platform.startswith('win32'), "Windows only") class TestFileTiffW32(PillowTestCase): diff --git a/PIL/ImageFile.py b/PIL/ImageFile.py index f58de95bd68..2ed1520fd1a 100644 --- a/PIL/ImageFile.py +++ b/PIL/ImageFile.py @@ -509,12 +509,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)) @@ -522,9 +528,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(object): def __init__(self): self.xsize = 0 diff --git a/PIL/PsdImagePlugin.py b/PIL/PsdImagePlugin.py index d3799edc3d9..96de58fe7a3 100644 --- a/PIL/PsdImagePlugin.py +++ b/PIL/PsdImagePlugin.py @@ -18,6 +18,8 @@ __version__ = "0.4" +import io + from . import Image, ImageFile, ImagePalette from ._binary import i8, i16be as i16, i32be as i32 @@ -114,7 +116,8 @@ def _open(self): 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) # @@ -163,12 +166,20 @@ def _close__fp(self): if self.mode == "P": Image.Image.load(self) - -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)) @@ -179,7 +190,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 @@ -212,20 +224,20 @@ 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: mask_y = i32(read(4)) mask_x = i32(read(4)) mask_h = i32(read(4)) - mask_y mask_w = i32(read(4)) - mask_x - file.seek(length - 16, 1) + fp.seek(length - 16, 1) combined += length + 4 length = i32(read(4)) if length: - file.seek(length, 1) + fp.seek(length, 1) combined += length + 4 length = i8(read(1)) @@ -235,7 +247,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 @@ -243,7 +255,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
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor