mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-01-26 17:24:31 +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
|
from __future__ import annotations
|
||||||
|
import struct
|
||||||
import pytest
|
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
|
from .helper import assert_image_equal_tofile
|
||||||
|
|
||||||
pytestmark = pytest.mark.skipif(
|
original_core = ImageFont.core
|
||||||
features.check_module("freetype2"),
|
|
||||||
reason="PILfont superseded if FreeType is supported",
|
|
||||||
)
|
def setup_module():
|
||||||
|
if features.check_module("freetype2"):
|
||||||
|
ImageFont.core = _util.DeferredError(ImportError)
|
||||||
|
|
||||||
|
|
||||||
|
def teardown_module():
|
||||||
|
ImageFont.core = original_core
|
||||||
|
|
||||||
|
|
||||||
def test_default_font():
|
def test_default_font():
|
||||||
|
@ -44,3 +52,23 @@ def test_textbbox():
|
||||||
default_font = ImageFont.load_default()
|
default_font = ImageFont.load_default()
|
||||||
assert d.textlength("test", font=default_font) == 24
|
assert d.textlength("test", font=default_font) == 24
|
||||||
assert d.textbbox((0, 0), "test", font=default_font) == (0, 0, 24, 11)
|
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
|
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``.
|
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
|
ImageMath.eval: Restricted environment keys
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
|
|
@ -150,6 +150,7 @@ class ImageFont:
|
||||||
:py:mod:`PIL.Image.core` interface module.
|
:py:mod:`PIL.Image.core` interface module.
|
||||||
"""
|
"""
|
||||||
_string_length_check(text)
|
_string_length_check(text)
|
||||||
|
Image._decompression_bomb_check(self.font.getsize(text))
|
||||||
return self.font.getmask(text, mode)
|
return self.font.getmask(text, mode)
|
||||||
|
|
||||||
def getbbox(self, text, *args, **kwargs):
|
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].sy0 = S16(B16(glyphdata, 14));
|
||||||
self->glyphs[i].sx1 = S16(B16(glyphdata, 16));
|
self->glyphs[i].sx1 = S16(B16(glyphdata, 16));
|
||||||
self->glyphs[i].sy1 = S16(B16(glyphdata, 18));
|
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) {
|
if (self->glyphs[i].dy0 < y0) {
|
||||||
y0 = self->glyphs[i].dy0;
|
y0 = self->glyphs[i].dy0;
|
||||||
}
|
}
|
||||||
|
@ -2721,7 +2733,7 @@ _font_text_asBytes(PyObject *encoded_string, unsigned char **text) {
|
||||||
static PyObject *
|
static PyObject *
|
||||||
_font_getmask(ImagingFontObject *self, PyObject *args) {
|
_font_getmask(ImagingFontObject *self, PyObject *args) {
|
||||||
Imaging im;
|
Imaging im;
|
||||||
Imaging bitmap;
|
Imaging bitmap = NULL;
|
||||||
int x, b;
|
int x, b;
|
||||||
int i = 0;
|
int i = 0;
|
||||||
int status;
|
int status;
|
||||||
|
@ -2730,7 +2742,7 @@ _font_getmask(ImagingFontObject *self, PyObject *args) {
|
||||||
PyObject *encoded_string;
|
PyObject *encoded_string;
|
||||||
|
|
||||||
unsigned char *text;
|
unsigned char *text;
|
||||||
char *mode = "";
|
char *mode;
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "O|s:getmask", &encoded_string, &mode)) {
|
if (!PyArg_ParseTuple(args, "O|s:getmask", &encoded_string, &mode)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
|
@ -2753,10 +2765,13 @@ _font_getmask(ImagingFontObject *self, PyObject *args) {
|
||||||
b = self->baseline;
|
b = self->baseline;
|
||||||
for (x = 0; text[i]; i++) {
|
for (x = 0; text[i]; i++) {
|
||||||
glyph = &self->glyphs[text[i]];
|
glyph = &self->glyphs[text[i]];
|
||||||
bitmap =
|
if (i == 0 || text[i] != text[i - 1]) {
|
||||||
ImagingCrop(self->bitmap, glyph->sx0, glyph->sy0, glyph->sx1, glyph->sy1);
|
ImagingDelete(bitmap);
|
||||||
if (!bitmap) {
|
bitmap =
|
||||||
goto failed;
|
ImagingCrop(self->bitmap, glyph->sx0, glyph->sy0, glyph->sx1, glyph->sy1);
|
||||||
|
if (!bitmap) {
|
||||||
|
goto failed;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
status = ImagingPaste(
|
status = ImagingPaste(
|
||||||
im,
|
im,
|
||||||
|
@ -2766,17 +2781,18 @@ _font_getmask(ImagingFontObject *self, PyObject *args) {
|
||||||
glyph->dy0 + b,
|
glyph->dy0 + b,
|
||||||
glyph->dx1 + x,
|
glyph->dx1 + x,
|
||||||
glyph->dy1 + b);
|
glyph->dy1 + b);
|
||||||
ImagingDelete(bitmap);
|
|
||||||
if (status < 0) {
|
if (status < 0) {
|
||||||
goto failed;
|
goto failed;
|
||||||
}
|
}
|
||||||
x = x + glyph->dx;
|
x = x + glyph->dx;
|
||||||
b = b + glyph->dy;
|
b = b + glyph->dy;
|
||||||
}
|
}
|
||||||
|
ImagingDelete(bitmap);
|
||||||
free(text);
|
free(text);
|
||||||
return PyImagingNew(im);
|
return PyImagingNew(im);
|
||||||
|
|
||||||
failed:
|
failed:
|
||||||
|
ImagingDelete(bitmap);
|
||||||
free(text);
|
free(text);
|
||||||
ImagingDelete(im);
|
ImagingDelete(im);
|
||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
|
|
Loading…
Reference in New Issue
Block a user