diff --git a/lib/matplotlib/tests/test_ft2font.py b/lib/matplotlib/tests/test_ft2font.py index ee7c7cea3aac..105feb37fa2f 100644 --- a/lib/matplotlib/tests/test_ft2font.py +++ b/lib/matplotlib/tests/test_ft2font.py @@ -203,6 +203,31 @@ def test_ft2font_invalid_args(tmp_path): ft2font.FT2Font(file, _kerning_factor=123) +def test_ft2font_oversized_read(): + # A file object whose read() returns *more* bytes than requested is + # misbehaving: FreeType only sizes its buffer for the requested count, so + # an over-long read must be rejected rather than overflow the buffer or be + # silently truncated. Verify that such an object causes construction to + # fail loudly instead. + data = Path(fm.findfont('DejaVu Sans')).read_bytes() + + class OversizedReader: + def __init__(self, data): + self._data = data + + def seek(self, offset): + pass + + def read(self, size): + # Ignore the requested size and hand back the whole file, which is + # far more than FreeType ever asks for. The initial read(0) probe + # in the constructor still gets an empty bytes object. + return self._data if size else b'' + + with pytest.raises(RuntimeError): + ft2font.FT2Font(OversizedReader(data)) + + @pytest.mark.parametrize('name, size, skippable', [('DejaVu Sans', 1, False), ('WenQuanYi Zen Hei', 3, True)]) def test_ft2font_face_index(name, size, skippable): diff --git a/src/ft2font_wrapper.cpp b/src/ft2font_wrapper.cpp index 771f1db5a191..a8654ba1d8a3 100644 --- a/src/ft2font_wrapper.cpp +++ b/src/ft2font_wrapper.cpp @@ -453,6 +453,15 @@ read_from_file_callback(FT_Stream stream, unsigned long offset, unsigned char *b if (PyBytes_AsStringAndSize(read_result.ptr(), &tmpbuf, &n_read) == -1) { throw py::error_already_set(); } + if ((unsigned long)n_read > count) { + // A well-behaved read() never returns more than the requested + // number of bytes. FreeType only ever sized `buffer` for `count` + // bytes, so honoring an over-long read would overflow it. Rather + // than silently truncate (which would feed FreeType corrupt data), + // signal a failed read so that FT_Open_Face -- and in turn the + // FT2Font constructor -- raises. + n_read = 0; + } memcpy(buffer, tmpbuf, n_read); } catch (py::error_already_set &eas) { eas.discard_as_unraisable(__func__);