Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
SUSE:SLE-15-SP3:Update
python-Pillow
security-fixes-820.patch
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File security-fixes-820.patch of Package python-Pillow
Index: Pillow-7.2.0/Tests/test_decompression_bomb.py =================================================================== --- Pillow-7.2.0.orig/Tests/test_decompression_bomb.py +++ Pillow-7.2.0/Tests/test_decompression_bomb.py @@ -51,6 +51,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): Image.open("Tests/images/decompression_bomb.ico") Index: Pillow-7.2.0/Tests/test_file_apng.py =================================================================== --- Pillow-7.2.0.orig/Tests/test_file_apng.py +++ Pillow-7.2.0/Tests/test_file_apng.py @@ -286,7 +286,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() Index: Pillow-7.2.0/Tests/test_file_blp.py =================================================================== --- Pillow-7.2.0.orig/Tests/test_file_blp.py +++ Pillow-7.2.0/Tests/test_file_blp.py @@ -1,3 +1,5 @@ +import pytest + from PIL import Image from .helper import assert_image_equal @@ -19,3 +21,22 @@ def test_load_blp2_dxt1a(): with Image.open("Tests/images/blp/blp2_dxt1a.blp") as im: with Image.open("Tests/images/blp/blp2_dxt1a.png") as target: assert_image_equal(im, target) + + +@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() Index: Pillow-7.2.0/Tests/test_file_eps.py =================================================================== --- Pillow-7.2.0.orig/Tests/test_file_eps.py +++ Pillow-7.2.0/Tests/test_file_eps.py @@ -256,3 +256,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 Index: Pillow-7.2.0/Tests/test_file_jpeg2k.py =================================================================== --- Pillow-7.2.0.orig/Tests/test_file_jpeg2k.py +++ Pillow-7.2.0/Tests/test_file_jpeg2k.py @@ -233,3 +233,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() Index: Pillow-7.2.0/src/PIL/BlpImagePlugin.py =================================================================== --- Pillow-7.2.0.orig/src/PIL/BlpImagePlugin.py +++ Pillow-7.2.0/src/PIL/BlpImagePlugin.py @@ -286,33 +286,36 @@ class _BLPBaseDecoder(ImageFile.PyDecode 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) @@ -369,7 +372,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)) @@ -383,20 +386,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( Index: Pillow-7.2.0/src/PIL/EpsImagePlugin.py =================================================================== --- Pillow-7.2.0.orig/src/PIL/EpsImagePlugin.py +++ Pillow-7.2.0/src/PIL/EpsImagePlugin.py @@ -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): Index: Pillow-7.2.0/src/PIL/ImageFile.py =================================================================== --- Pillow-7.2.0.orig/src/PIL/ImageFile.py +++ Pillow-7.2.0/src/PIL/ImageFile.py @@ -551,12 +551,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)) @@ -564,6 +570,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) Index: Pillow-7.2.0/src/PIL/ImageFont.py =================================================================== --- Pillow-7.2.0.orig/src/PIL/ImageFont.py +++ Pillow-7.2.0/src/PIL/ImageFont.py @@ -485,6 +485,7 @@ class FreeTypeFont: text, mode == "1", direction, features, language ) size = size[0] + stroke_width * 2, size[1] + stroke_width * 2 + Image._decompression_bomb_check(size) im = fill("L", size, 0) self.font.render( text, im.id, mode == "1", direction, features, language, stroke_width Index: Pillow-7.2.0/src/PIL/PsdImagePlugin.py =================================================================== --- Pillow-7.2.0.orig/src/PIL/PsdImagePlugin.py +++ Pillow-7.2.0/src/PIL/PsdImagePlugin.py @@ -117,7 +117,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 @@ -169,11 +170,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)) @@ -184,7 +194,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 @@ -217,16 +228,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)) @@ -236,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 @@ -244,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 Index: Pillow-7.2.0/src/libImaging/FliDecode.c =================================================================== --- Pillow-7.2.0.orig/src/libImaging/FliDecode.c +++ Pillow-7.2.0/src/libImaging/FliDecode.c @@ -242,6 +242,11 @@ ImagingFliDecode(Imaging im, ImagingCode 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; Index: Pillow-7.2.0/src/libImaging/Jpeg2KDecode.c =================================================================== --- Pillow-7.2.0.orig/src/libImaging/Jpeg2KDecode.c +++ Pillow-7.2.0/src/libImaging/Jpeg2KDecode.c @@ -589,8 +589,7 @@ j2k_decode_entry(Imaging im, ImagingCode 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); @@ -751,23 +750,40 @@ j2k_decode_entry(Imaging im, ImagingCode goto quick_exit; } + if (tile_info.nb_comps != image->numcomps) { + state->errcode = IMAGING_CODEC_BROKEN; + state->state = J2K_STATE_FAILED; + goto quick_exit; + } + /* Sometimes the tile_info.datasize we get back from openjpeg - is less than numcomps*w*h, and we overflow in the + 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; - 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 ))) { + + /* 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 * components; + tile_bytes = tile_width * tile_height * total_component_width; if (tile_bytes > tile_info.data_size) { tile_info.data_size = tile_bytes; Index: Pillow-7.2.0/Tests/test_file_fli.py =================================================================== --- Pillow-7.2.0.orig/Tests/test_file_fli.py +++ Pillow-7.2.0/Tests/test_file_fli.py @@ -123,3 +123,18 @@ def test_seek(): with Image.open("Tests/images/a_fli.png") as expected: assert_image_equal(im, expected) + + +@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() Index: Pillow-7.2.0/Tests/test_file_psd.py =================================================================== --- Pillow-7.2.0.orig/Tests/test_file_psd.py +++ Pillow-7.2.0/Tests/test_file_psd.py @@ -127,3 +127,25 @@ def test_combined_larger_than_size(): # then the seek can't be negative with pytest.raises(OSError): Image.open("Tests/images/combined_larger_than_size.psd") + + +@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 Index: Pillow-7.2.0/Tests/test_file_tiff.py =================================================================== --- Pillow-7.2.0.orig/Tests/test_file_tiff.py +++ Pillow-7.2.0/Tests/test_file_tiff.py @@ -598,8 +598,9 @@ class TestFileTiff: @pytest.mark.filterwarnings("ignore:Possibly corrupt EXIF data") def test_string_dimension(self): # Assert that an error is raised if one of the dimensions is a string - with pytest.raises(ValueError): - Image.open("Tests/images/string_dimension.tiff") + 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") Index: Pillow-7.2.0/Tests/test_imagefont.py =================================================================== --- Pillow-7.2.0.orig/Tests/test_imagefont.py +++ Pillow-7.2.0/Tests/test_imagefont.py @@ -768,3 +768,16 @@ def test_render_mono_size(): draw.text((10, 10), "r" * 10, "black", ttf) assert_image_equal_tofile(im, "Tests/images/text_mono.gif") + + +@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")
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