From 87d1770c182bb3e19229f1a652cabedce29b891e Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Wed, 2 Nov 2022 23:11:57 +0100 Subject: [PATCH 1/5] Fix null pointer dereference crash with malformed font --- Tests/fonts/fuzz_font-5203009437302784 | 10 ++++++++++ Tests/test_font_crash.py | 21 +++++++++++++++++++++ src/_imagingft.c | 6 ++++++ 3 files changed, 37 insertions(+) create mode 100644 Tests/fonts/fuzz_font-5203009437302784 create mode 100644 Tests/test_font_crash.py diff --git a/Tests/fonts/fuzz_font-5203009437302784 b/Tests/fonts/fuzz_font-5203009437302784 new file mode 100644 index 00000000000..0465e48c204 --- /dev/null +++ b/Tests/fonts/fuzz_font-5203009437302784 @@ -0,0 +1,10 @@ +STARTFONT +FONT ÿ +SIZE 10 +FONTBOUNDINGBOX +CHARS +STARTCHAR +ENCODING +BBX 2 5 +ENDCHAR +ENDFONT diff --git a/Tests/test_font_crash.py b/Tests/test_font_crash.py new file mode 100644 index 00000000000..020ddfcd973 --- /dev/null +++ b/Tests/test_font_crash.py @@ -0,0 +1,21 @@ +from PIL import Image, ImageDraw, ImageFont + +import pytest + +from .helper import skip_unless_feature + +class TestFontCrash: + def _fuzz_font(self, font): + # from fuzzers.fuzz_font + font.getbbox("ABC") + font.getmask("test text") + with Image.new(mode="RGBA", size=(200, 200)) as im: + draw = ImageDraw.Draw(im) + draw.multiline_textbbox((10, 10), "ABC\nAaaa", font, stroke_width=2) + draw.text((10, 10), "Test Text", font=font, fill="#000") + + @skip_unless_feature("freetype2") + def test_segfault(self): + with pytest.raises(OSError): + font= ImageFont.truetype('Tests/fonts/fuzz_font-5203009437302784') + self._fuzz_font(font) diff --git a/src/_imagingft.c b/src/_imagingft.c index b52d6353ebc..3190988972d 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -921,6 +921,12 @@ font_render(FontObject *self, PyObject *args) { yy = -(py + glyph_slot->bitmap_top); } + // Null buffer, is dereferenced in FT_Bitmap_Convert + if (!bitmap.buffer && bitmap.rows) { + return geterror(0x9D); // Bitmap missing + goto glyph_error; + } + /* convert non-8bpp bitmaps */ switch (bitmap.pixel_mode) { case FT_PIXEL_MODE_MONO: From f2b36a1833f1b95d7a8d336432170cad091c6236 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 3 Nov 2022 09:18:47 +1100 Subject: [PATCH 2/5] Lint fixes --- Tests/test_font_crash.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Tests/test_font_crash.py b/Tests/test_font_crash.py index 020ddfcd973..e8d612a7fc8 100644 --- a/Tests/test_font_crash.py +++ b/Tests/test_font_crash.py @@ -4,6 +4,7 @@ from .helper import skip_unless_feature + class TestFontCrash: def _fuzz_font(self, font): # from fuzzers.fuzz_font @@ -17,5 +18,5 @@ def _fuzz_font(self, font): @skip_unless_feature("freetype2") def test_segfault(self): with pytest.raises(OSError): - font= ImageFont.truetype('Tests/fonts/fuzz_font-5203009437302784') + font = ImageFont.truetype("Tests/fonts/fuzz_font-5203009437302784") self._fuzz_font(font) From 1c57ab84294845a49b4f5dc2b2444a8eaff70110 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Thu, 3 Nov 2022 21:26:17 +0100 Subject: [PATCH 3/5] Return a PyError instead of a fake fterror. * Update Tests to IOError rather than OSError --- Tests/oss-fuzz/test_fuzzers.py | 4 +++- Tests/test_font_crash.py | 2 +- src/_imagingft.c | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Tests/oss-fuzz/test_fuzzers.py b/Tests/oss-fuzz/test_fuzzers.py index 629e9ac00d4..1b0b1d3dc03 100644 --- a/Tests/oss-fuzz/test_fuzzers.py +++ b/Tests/oss-fuzz/test_fuzzers.py @@ -57,6 +57,8 @@ def test_fuzz_fonts(path): with open(path, "rb") as f: try: fuzzers.fuzz_font(f.read()) - except (Image.DecompressionBombError, Image.DecompressionBombWarning): + except (Image.DecompressionBombError, + Image.DecompressionBombWarning, + IOError): pass assert True diff --git a/Tests/test_font_crash.py b/Tests/test_font_crash.py index e8d612a7fc8..9a2110c0c68 100644 --- a/Tests/test_font_crash.py +++ b/Tests/test_font_crash.py @@ -17,6 +17,6 @@ def _fuzz_font(self, font): @skip_unless_feature("freetype2") def test_segfault(self): - with pytest.raises(OSError): + with pytest.raises(IOError): font = ImageFont.truetype("Tests/fonts/fuzz_font-5203009437302784") self._fuzz_font(font) diff --git a/src/_imagingft.c b/src/_imagingft.c index 3190988972d..053ef1e7d5d 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -923,7 +923,7 @@ font_render(FontObject *self, PyObject *args) { // Null buffer, is dereferenced in FT_Bitmap_Convert if (!bitmap.buffer && bitmap.rows) { - return geterror(0x9D); // Bitmap missing + PyErr_SetString(PyExc_IOError, "Bitmap missing for glyph"); goto glyph_error; } From 51d95add6a306ea9dc1b0e2dacc202f69e4565e0 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 5 Nov 2022 15:41:17 +1100 Subject: [PATCH 4/5] Replaced IOError with OSError --- Tests/oss-fuzz/test_fuzzers.py | 2 +- Tests/test_font_crash.py | 2 +- src/_imagingft.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Tests/oss-fuzz/test_fuzzers.py b/Tests/oss-fuzz/test_fuzzers.py index 1b0b1d3dc03..fb8f87e8658 100644 --- a/Tests/oss-fuzz/test_fuzzers.py +++ b/Tests/oss-fuzz/test_fuzzers.py @@ -59,6 +59,6 @@ def test_fuzz_fonts(path): fuzzers.fuzz_font(f.read()) except (Image.DecompressionBombError, Image.DecompressionBombWarning, - IOError): + OSError): pass assert True diff --git a/Tests/test_font_crash.py b/Tests/test_font_crash.py index 9a2110c0c68..e8d612a7fc8 100644 --- a/Tests/test_font_crash.py +++ b/Tests/test_font_crash.py @@ -17,6 +17,6 @@ def _fuzz_font(self, font): @skip_unless_feature("freetype2") def test_segfault(self): - with pytest.raises(IOError): + with pytest.raises(OSError): font = ImageFont.truetype("Tests/fonts/fuzz_font-5203009437302784") self._fuzz_font(font) diff --git a/src/_imagingft.c b/src/_imagingft.c index 053ef1e7d5d..0db17a5a6db 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -923,7 +923,7 @@ font_render(FontObject *self, PyObject *args) { // Null buffer, is dereferenced in FT_Bitmap_Convert if (!bitmap.buffer && bitmap.rows) { - PyErr_SetString(PyExc_IOError, "Bitmap missing for glyph"); + PyErr_SetString(PyExc_OSError, "Bitmap missing for glyph"); goto glyph_error; } From c977526cfeda89e86d0144f5f8dca06cd05dbef5 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 31 Dec 2022 17:45:09 +1100 Subject: [PATCH 5/5] Lint fixes --- Tests/oss-fuzz/test_fuzzers.py | 4 +--- Tests/test_font_crash.py | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/Tests/oss-fuzz/test_fuzzers.py b/Tests/oss-fuzz/test_fuzzers.py index fb8f87e8658..dc111c38b36 100644 --- a/Tests/oss-fuzz/test_fuzzers.py +++ b/Tests/oss-fuzz/test_fuzzers.py @@ -57,8 +57,6 @@ def test_fuzz_fonts(path): with open(path, "rb") as f: try: fuzzers.fuzz_font(f.read()) - except (Image.DecompressionBombError, - Image.DecompressionBombWarning, - OSError): + except (Image.DecompressionBombError, Image.DecompressionBombWarning, OSError): pass assert True diff --git a/Tests/test_font_crash.py b/Tests/test_font_crash.py index e8d612a7fc8..27663f396ea 100644 --- a/Tests/test_font_crash.py +++ b/Tests/test_font_crash.py @@ -1,7 +1,7 @@ -from PIL import Image, ImageDraw, ImageFont - import pytest +from PIL import Image, ImageDraw, ImageFont + from .helper import skip_unless_feature