mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-01-12 18:26:17 +03:00
Merge pull request #7669 from radarhere/imagefont_mask
Do not try and crop glyphs from outside of source ImageFont image
This commit is contained in:
commit
10c2df5430
|
@ -1,14 +1,22 @@
|
|||
from __future__ import annotations
|
||||
import struct
|
||||
import pytest
|
||||
from io import BytesIO
|
||||
|
||||
from PIL import Image, ImageDraw, ImageFont, features
|
||||
from PIL import Image, ImageDraw, ImageFont, features, _util
|
||||
|
||||
from .helper import assert_image_equal_tofile
|
||||
|
||||
pytestmark = pytest.mark.skipif(
|
||||
features.check_module("freetype2"),
|
||||
reason="PILfont superseded if FreeType is supported",
|
||||
)
|
||||
original_core = ImageFont.core
|
||||
|
||||
|
||||
def setup_module():
|
||||
if features.check_module("freetype2"):
|
||||
ImageFont.core = _util.DeferredError(ImportError)
|
||||
|
||||
|
||||
def teardown_module():
|
||||
ImageFont.core = original_core
|
||||
|
||||
|
||||
def test_default_font():
|
||||
|
@ -44,3 +52,23 @@ def test_textbbox():
|
|||
default_font = ImageFont.load_default()
|
||||
assert d.textlength("test", font=default_font) == 24
|
||||
assert d.textbbox((0, 0), "test", font=default_font) == (0, 0, 24, 11)
|
||||
|
||||
|
||||
def test_decompression_bomb():
|
||||
glyph = struct.pack(">hhhhhhhhhh", 1, 0, 0, 0, 256, 256, 0, 0, 256, 256)
|
||||
fp = BytesIO(b"PILfont\n\nDATA\n" + glyph * 256)
|
||||
|
||||
font = ImageFont.ImageFont()
|
||||
font._load_pilfont_data(fp, Image.new("L", (256, 256)))
|
||||
with pytest.raises(Image.DecompressionBombError):
|
||||
font.getmask("A" * 1_000_000)
|
||||
|
||||
|
||||
@pytest.mark.timeout(4)
|
||||
def test_oom():
|
||||
glyph = struct.pack(">hhhhhhhhhh", 1, 0, 0, 0, 32767, 32767, 0, 0, 32767, 32767)
|
||||
fp = BytesIO(b"PILfont\n\nDATA\n" + glyph * 256)
|
||||
|
||||
font = ImageFont.ImageFont()
|
||||
font._load_pilfont_data(fp, Image.new("L", (1, 1)))
|
||||
font.getmask("A" * 1_000_000)
|
||||
|
|
|
@ -77,6 +77,16 @@ Pillow will now raise a :py:exc:`ValueError` if the number of characters passed
|
|||
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``.
|
||||
|
||||
A decompression bomb check has also been added to
|
||||
:py:meth:`PIL.ImageFont.ImageFont.getmask`.
|
||||
|
||||
ImageFont.getmask: Trim glyph size
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
To protect against potential DOS attacks when using PIL fonts,
|
||||
:py:class:`PIL.ImageFont.ImageFont` now trims the size of individual glyphs so that
|
||||
they do not extend beyond the bitmap image.
|
||||
|
||||
ImageMath.eval: Restricted environment keys
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
|
|
|
@ -150,6 +150,7 @@ class ImageFont:
|
|||
:py:mod:`PIL.Image.core` interface module.
|
||||
"""
|
||||
_string_length_check(text)
|
||||
Image._decompression_bomb_check(self.font.getsize(text))
|
||||
return self.font.getmask(text, mode)
|
||||
|
||||
def getbbox(self, text, *args, **kwargs):
|
||||
|
|
|
@ -2649,6 +2649,18 @@ _font_new(PyObject *self_, PyObject *args) {
|
|||
self->glyphs[i].sy0 = S16(B16(glyphdata, 14));
|
||||
self->glyphs[i].sx1 = S16(B16(glyphdata, 16));
|
||||
self->glyphs[i].sy1 = S16(B16(glyphdata, 18));
|
||||
|
||||
// Do not allow glyphs to extend beyond bitmap image
|
||||
// Helps prevent DOS by stopping cropped images being larger than the original
|
||||
if (self->glyphs[i].sx1 > self->bitmap->xsize) {
|
||||
self->glyphs[i].dx1 -= self->glyphs[i].sx1 - self->bitmap->xsize;
|
||||
self->glyphs[i].sx1 = self->bitmap->xsize;
|
||||
}
|
||||
if (self->glyphs[i].sy1 > self->bitmap->ysize) {
|
||||
self->glyphs[i].dy1 -= self->glyphs[i].sy1 - self->bitmap->ysize;
|
||||
self->glyphs[i].sy1 = self->bitmap->ysize;
|
||||
}
|
||||
|
||||
if (self->glyphs[i].dy0 < y0) {
|
||||
y0 = self->glyphs[i].dy0;
|
||||
}
|
||||
|
@ -2721,7 +2733,7 @@ _font_text_asBytes(PyObject *encoded_string, unsigned char **text) {
|
|||
static PyObject *
|
||||
_font_getmask(ImagingFontObject *self, PyObject *args) {
|
||||
Imaging im;
|
||||
Imaging bitmap;
|
||||
Imaging bitmap = NULL;
|
||||
int x, b;
|
||||
int i = 0;
|
||||
int status;
|
||||
|
@ -2730,7 +2742,7 @@ _font_getmask(ImagingFontObject *self, PyObject *args) {
|
|||
PyObject *encoded_string;
|
||||
|
||||
unsigned char *text;
|
||||
char *mode = "";
|
||||
char *mode;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "O|s:getmask", &encoded_string, &mode)) {
|
||||
return NULL;
|
||||
|
@ -2753,10 +2765,13 @@ _font_getmask(ImagingFontObject *self, PyObject *args) {
|
|||
b = self->baseline;
|
||||
for (x = 0; text[i]; i++) {
|
||||
glyph = &self->glyphs[text[i]];
|
||||
bitmap =
|
||||
ImagingCrop(self->bitmap, glyph->sx0, glyph->sy0, glyph->sx1, glyph->sy1);
|
||||
if (!bitmap) {
|
||||
goto failed;
|
||||
if (i == 0 || text[i] != text[i - 1]) {
|
||||
ImagingDelete(bitmap);
|
||||
bitmap =
|
||||
ImagingCrop(self->bitmap, glyph->sx0, glyph->sy0, glyph->sx1, glyph->sy1);
|
||||
if (!bitmap) {
|
||||
goto failed;
|
||||
}
|
||||
}
|
||||
status = ImagingPaste(
|
||||
im,
|
||||
|
@ -2766,17 +2781,18 @@ _font_getmask(ImagingFontObject *self, PyObject *args) {
|
|||
glyph->dy0 + b,
|
||||
glyph->dx1 + x,
|
||||
glyph->dy1 + b);
|
||||
ImagingDelete(bitmap);
|
||||
if (status < 0) {
|
||||
goto failed;
|
||||
}
|
||||
x = x + glyph->dx;
|
||||
b = b + glyph->dy;
|
||||
}
|
||||
ImagingDelete(bitmap);
|
||||
free(text);
|
||||
return PyImagingNew(im);
|
||||
|
||||
failed:
|
||||
ImagingDelete(bitmap);
|
||||
free(text);
|
||||
ImagingDelete(im);
|
||||
Py_RETURN_NONE;
|
||||
|
|
Loading…
Reference in New Issue
Block a user