From 1fe1bb49c452b0318cad12ea9d97c3bef188e9a7 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 30 Jun 2023 23:32:26 +1000 Subject: [PATCH 1/2] Added ImageFont.MAX_STRING_LENGTH --- Tests/test_imagefont.py | 19 +++++++++++++++++++ docs/reference/ImageFont.rst | 18 ++++++++++++++++++ docs/releasenotes/10.0.0.rst | 12 ++++++++++++ src/PIL/ImageFont.py | 15 +++++++++++++++ 4 files changed, 64 insertions(+) diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index 7fa8ff8cbfd..c50447a153d 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -1038,6 +1038,25 @@ def test_render_mono_size(): assert_image_equal_tofile(im, "Tests/images/text_mono.gif") +def test_too_many_characters(font): + with pytest.raises(ValueError): + font.getlength("A" * 1000001) + with pytest.raises(ValueError): + font.getbbox("A" * 1000001) + with pytest.raises(ValueError): + font.getmask2("A" * 1000001) + + transposed_font = ImageFont.TransposedFont(font) + with pytest.raises(ValueError): + transposed_font.getlength("A" * 1000001) + + default_font = ImageFont.load_default() + with pytest.raises(ValueError): + default_font.getlength("A" * 1000001) + with pytest.raises(ValueError): + default_font.getbbox("A" * 1000001) + + @pytest.mark.parametrize( "test_file", [ diff --git a/docs/reference/ImageFont.rst b/docs/reference/ImageFont.rst index 946bd3c4bed..2abfa0cc997 100644 --- a/docs/reference/ImageFont.rst +++ b/docs/reference/ImageFont.rst @@ -18,6 +18,15 @@ OpenType fonts (as well as other font formats supported by the FreeType library). For earlier versions, TrueType support is only available as part of the imToolkit package. +.. warning:: + To protect against potential DOS attacks when using arbitrary strings as + text input, Pillow will raise a ``ValueError`` if the number of characters + is over a certain limit, :py:data:`MAX_STRING_LENGTH`. + + This threshold can be changed by setting + :py:data:`MAX_STRING_LENGTH`. It can be disabled by setting + ``ImageFont.MAX_STRING_LENGTH = None``. + Example ------- @@ -73,3 +82,12 @@ Constants Requires Raqm, you can check support using :py:func:`PIL.features.check_feature` with ``feature="raqm"``. + +Constants +--------- + +.. data:: MAX_STRING_LENGTH + + Set to 1,000,000, to protect against potential DOS attacks. Pillow will + raise a ``ValueError`` if the number of characters is over this limit. The + check can be disabled by setting ``ImageFont.MAX_STRING_LENGTH = None``. diff --git a/docs/releasenotes/10.0.0.rst b/docs/releasenotes/10.0.0.rst index 94ff04d46a2..4cd6293229a 100644 --- a/docs/releasenotes/10.0.0.rst +++ b/docs/releasenotes/10.0.0.rst @@ -170,6 +170,18 @@ now been fixed. This effectively dates to the PIL fork, since problem images would still have been processed before Pillow started checking for decompression bombs. +Added ImageFont.MAX_STRING_LENGTH +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +To protect against potential DOS attacks when using arbitrary strings as text +input, Pillow will now raise a ``ValueError`` if the number of characters +passed into ImageFont methods is over a certain limit, +:py:data:`PIL.ImageFont.MAX_STRING_LENGTH`. + +This threshold can be changed by setting +:py:data:`PIL.ImageFont.MAX_STRING_LENGTH`. It can be disabled by setting +``ImageFont.MAX_STRING_LENGTH = None``. + Other Changes ============= diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py index 3ddc1aaad64..1030985ebc4 100644 --- a/src/PIL/ImageFont.py +++ b/src/PIL/ImageFont.py @@ -41,6 +41,9 @@ class Layout(IntEnum): RAQM = 1 +MAX_STRING_LENGTH = 1000000 + + try: from . import _imagingft as core except ImportError as ex: @@ -49,6 +52,12 @@ class Layout(IntEnum): core = DeferredError(ex) +def _string_length_check(text): + if MAX_STRING_LENGTH is not None and len(text) > MAX_STRING_LENGTH: + msg = "too many characters in string" + raise ValueError(msg) + + # FIXME: add support for pilfont2 format (see FontFile.py) # -------------------------------------------------------------------- @@ -152,6 +161,7 @@ def getbbox(self, text, *args, **kwargs): :return: ``(left, top, right, bottom)`` bounding box """ + _string_length_check(text) width, height = self.font.getsize(text) return 0, 0, width, height @@ -162,6 +172,7 @@ def getlength(self, text, *args, **kwargs): .. versionadded:: 9.2.0 """ + _string_length_check(text) width, height = self.font.getsize(text) return width @@ -309,6 +320,7 @@ def getlength(self, text, mode="", direction=None, features=None, language=None) :return: Width for horizontal, height for vertical text. """ + _string_length_check(text) return self.font.getlength(text, mode, direction, features, language) / 64 def getbbox( @@ -368,6 +380,7 @@ def getbbox( :return: ``(left, top, right, bottom)`` bounding box """ + _string_length_check(text) size, offset = self.font.getsize( text, mode, direction, features, language, anchor ) @@ -546,6 +559,7 @@ def getmask2( :py:mod:`PIL.Image.core` interface module, and the text offset, the gap between the starting coordinate and the first marking """ + _string_length_check(text) if start is None: start = (0, 0) im, size, offset = self.font.render( @@ -684,6 +698,7 @@ def getlength(self, text, *args, **kwargs): if self.orientation in (Image.Transpose.ROTATE_90, Image.Transpose.ROTATE_270): msg = "text length is undefined for text rotated by 90 or 270 degrees" raise ValueError(msg) + _string_length_check(text) return self.font.getlength(text, *args, **kwargs) From d398fedb9d5af22316c715d2066176d15031d439 Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Sat, 1 Jul 2023 07:25:18 +1000 Subject: [PATCH 2/2] Added underscores for readability Co-authored-by: Hugo van Kemenade --- Tests/test_imagefont.py | 12 ++++++------ src/PIL/ImageFont.py | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index c50447a153d..02622e72138 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -1040,21 +1040,21 @@ def test_render_mono_size(): def test_too_many_characters(font): with pytest.raises(ValueError): - font.getlength("A" * 1000001) + font.getlength("A" * 1_000_001) with pytest.raises(ValueError): - font.getbbox("A" * 1000001) + font.getbbox("A" * 1_000_001) with pytest.raises(ValueError): - font.getmask2("A" * 1000001) + font.getmask2("A" * 1_000_001) transposed_font = ImageFont.TransposedFont(font) with pytest.raises(ValueError): - transposed_font.getlength("A" * 1000001) + transposed_font.getlength("A" * 1_000_001) default_font = ImageFont.load_default() with pytest.raises(ValueError): - default_font.getlength("A" * 1000001) + default_font.getlength("A" * 1_000_001) with pytest.raises(ValueError): - default_font.getbbox("A" * 1000001) + default_font.getbbox("A" * 1_000_001) @pytest.mark.parametrize( diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py index 1030985ebc4..b7d40208c83 100644 --- a/src/PIL/ImageFont.py +++ b/src/PIL/ImageFont.py @@ -41,7 +41,7 @@ class Layout(IntEnum): RAQM = 1 -MAX_STRING_LENGTH = 1000000 +MAX_STRING_LENGTH = 1_000_000 try: