Pillow tiled-TIFF decoder reads past tile buffer in copy4 (Unpack.c:1290)
### Summary Pillow's libImaging `_decodeTile` decoder, used to decode tiled TIFF images, allocates a per-tile buffer using `realloc(state->buffer, TIFFTileSize(tiff))`. After each `TIFFReadTile` it then iterates `tile_y` from 0 up to `current_tile_length` and calls the configured row unpacker (here `copy4`) with the source pointer `state->buffer + tile_y * row_byte_size`. When libtiff returns a `tile_bytes_size` smaller than the worst-case `row_byte_size * tile_length`, the inner stride-and-copy loop walks past the realloc'd region and `copy4` performs a 1024-byte `memcpy` that reads up to `(tile_length-1) * row_byte_size + row_byte_size` bytes from a buffer sized only to `tile_bytes_size`. The supplied PoC TIFF triggers a 1024-byte over-read exactly 0 bytes past a 196608-byte allocation. An attacker who can convince a Pillow-using application to decode a crafted TIFF can trigger a process crash (DoS) and may cause adjacent heap memory to be copied into the decoded pixel buffer. ### Details Pillow's TIFF decoder, when libtiff reports the file is tiled, takes the `_decodeTile` path. The function: 1. Asks libtiff for `TIFFTileSize(tiff)` (the bytes-per-tile) and `TIFFTileRowSize(tiff)` (the bytes per row *as stored by libtiff*). 2. Reallocates `state->buffer` to exactly `tile_bytes_size`. 3. For each tile origin `(x, y)` and each plane, calls `TIFFReadTile` into `state->buffer`, then walks the tile row by row, calling the configured `ImagingShuffler` (here `copy4`) on each row'
- Products
- pillow
- Affected 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, 8.2.0, 8.3.0, 8.3.1, 8.3.2, 8.4.0, 9.0.0, 9.0.1, 9.1.0, 9.1.1, 9.2.0, 9.3.0, 9.4.0, 9.5.0
- Fixed versions
- 13.0.0
- CVEs
- CVE-2026-112395
- Confirmed fixed CVEs
- Not specified
- Updated in this feed
- 1 Nov 2026, 10:37 UTC · Revision 1
Source evidence
Pillow tiled-TIFF decoder reads past tile buffer in copy4 (Unpack.c:1290)
core-osv · Original sourcePublished 20 Oct 2026, 20:49 UTC
### Summary Pillow's libImaging `_decodeTile` decoder, used to decode tiled TIFF images, allocates a per-tile buffer using `realloc(state->buffer, TIFFTileSize(tiff))`. After each `TIFFReadTile` it then iterates `tile_y` from 0 up to `current_tile_length` and calls the configured row unpacker (here `copy4`) with the source pointer `state->buffer + tile_y * row_byte_size`. When libtiff returns a `tile_bytes_size` smaller than the worst-case `row_byte_size * tile_length`, the inner stride-and-copy loop walks past the realloc'd region and `copy4` performs a 1024-byte `memcpy` that reads up to `(tile_length-1) * row_byte_size + row_byte_size` bytes from a buffer sized only to `tile_bytes_size`. The supplied PoC TIFF triggers a 1024-byte over-read exactly 0 bytes past a 196608-byte allocation. An attacker who can convince a Pillow-using application to decode a crafted TIFF can trigger a process crash (DoS) and may cause adjacent heap memory to be copied into the decoded pixel buffer. ### Details Pillow's TIFF decoder, when libtiff reports the file is tiled, takes the `_decodeTile` path. The function: 1. Asks libtiff for `TIFFTileSize(tiff)` (the bytes-per-tile) and `TIFFTileRowSize(tiff)` (the bytes per row *as stored by libtiff*). 2. Reallocates `state->buffer` to exactly `tile_bytes_size`. 3. For each tile origin `(x, y)` and each plane, calls `TIFFReadTile` into `state->buffer`, then walks the tile row by row, calling the configured `ImagingShuffler` (here `copy4`) on each row'
osv:GHSA-462x-pgg6-55fp
pillow
Fixed versions: 13.0.0
Complete source record
{
"id": "72c41622dc6d61a554fd9fe50df7465ee0d0030d765485d48ecfef165a86e17e",
"url": "https://osv.dev/vulnerability/GHSA-462x-pgg6-55fp",
"method": "structured",
"payload": {
"kind": "advisory",
"title": "Pillow tiled-TIFF decoder reads past tile buffer in copy4 (Unpack.c:1290)",
"summary": "### Summary Pillow's libImaging `_decodeTile` decoder, used to decode tiled TIFF images, allocates a per-tile buffer using `realloc(state->buffer, TIFFTileSize(tiff))`. After each `TIFFReadTile` it then iterates `tile_y` from 0 up to `current_tile_length` and calls the configured row unpacker (here `copy4`) with the source pointer `state->buffer + tile_y * row_byte_size`. When libtiff returns a `tile_bytes_size` smaller than the worst-case `row_byte_size * tile_length`, the inner stride-and-copy loop walks past the realloc'd region and `copy4` performs a 1024-byte `memcpy` that reads up to `(tile_length-1) * row_byte_size + row_byte_size` bytes from a buffer sized only to `tile_bytes_size`. The supplied PoC TIFF triggers a 1024-byte over-read exactly 0 bytes past a 196608-byte allocation. An attacker who can convince a Pillow-using application to decode a crafted TIFF can trigger a process crash (DoS) and may cause adjacent heap memory to be copied into the decoded pixel buffer. ### Details Pillow's TIFF decoder, when libtiff reports the file is tiled, takes the `_decodeTile` path. The function: 1. Asks libtiff for `TIFFTileSize(tiff)` (the bytes-per-tile) and `TIFFTileRowSize(tiff)` (the bytes per row *as stored by libtiff*). 2. Reallocates `state->buffer` to exactly `tile_bytes_size`. 3. For each tile origin `(x, y)` and each plane, calls `TIFFReadTile` into `state->buffer`, then walks the tile row by row, calling the configured `ImagingShuffler` (here `copy4`) on each row'",
"evidence": "osv:GHSA-462x-pgg6-55fp",
"products": [
{
"name": {
"value": "pillow",
"evidence": "/affected/0/package/name"
},
"prerequisites": [],
"fixed_versions": [
{
"value": "13.0.0",
"evidence": "/affected/0/ranges"
}
],
"affected_versions": [
{
"value": "10.0.0",
"evidence": "/affected/0/versions"
},
{
"value": "10.0.1",
"evidence": "/affected/0/versions"
},
{
"value": "10.1.0",
"evidence": "/affected/0/versions"
},
{
"value": "10.2.0",
"evidence": "/affected/0/versions"
},
{
"value": "10.3.0",
"evidence": "/affected/0/versions"
},
{
"value": "10.4.0",
"evidence": "/affected/0/versions"
},
{
"value": "11.0.0",
"evidence": "/affected/0/versions"
},
{
"value": "11.1.0",
"evidence": "/affected/0/versions"
},
{
"value": "11.2.1",
"evidence": "/affected/0/versions"
},
{
"value": "11.3.0",
"evidence": "/affected/0/versions"
},
{
"value": "12.0.0",
"evidence": "/affected/0/versions"
},
{
"value": "12.1.0",
"evidence": "/affected/0/versions"
},
{
"value": "12.1.1",
"evidence": "/affected/0/versions"
},
{
"value": "12.2.0",
"evidence": "/affected/0/versions"
},
{
"value": "12.3.0",
"evidence": "/affected/0/versions"
},
{
"value": "8.2.0",
"evidence": "/affected/0/versions"
},
{
"value": "8.3.0",
"evidence": "/affected/0/versions"
},
{
"value": "8.3.1",
"evidence": "/affected/0/versions"
},
{
"value": "8.3.2",
"evidence": "/affected/0/versions"
},
{
"value": "8.4.0",
"evidence": "/affected/0/versions"
},
{
"value": "9.0.0",
"evidence": "/affected/0/versions"
},
{
"value": "9.0.1",
"evidence": "/affected/0/versions"
},
{
"value": "9.1.0",
"evidence": "/affected/0/versions"
},
{
"value": "9.1.1",
"evidence": "/affected/0/versions"
},
{
"value": "9.2.0",
"evidence": "/affected/0/versions"
},
{
"value": "9.3.0",
"evidence": "/affected/0/versions"
},
{
"value": "9.4.0",
"evidence": "/affected/0/versions"
},
{
"value": "9.5.0",
"evidence": "/affected/0/versions"
}
]
}
],
"severity": "medium",
"structured": {
"id": "GHSA-462x-pgg6-55fp",
"aliases": [
"BIT-pillow-2026-112395",
"CVE-2026-112395",
"GHSA-462x-pgg6-55fp"
],
"metrics": [
{
"type": "CVSS_V3",
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:L"
}
],
"affected": [
{
"ranges": [
{
"type": "ECOSYSTEM",
"events": [
{
"introduced": "8.2.0"
},
{
"fixed": "13.0.0"
}
]
}
],
"package": {
"name": "pillow",
"purl": "pkg:pypi/pillow",
"ecosystem": "PyPI"
},
"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",
"8.2.0",
"8.3.0",
"8.3.1",
"8.3.2",
"8.4.0",
"9.0.0",
"9.0.1",
"9.1.0",
"9.1.1",
"9.2.0",
"9.3.0",
"9.4.0",
"9.5.0"
],
"database_specific": {
"source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/10/GHSA-462x-pgg6-55fp/GHSA-462x-pgg6-55fp.json"
}
}
],
"modified": "2026-10-26T08:56:01.770928143Z",
"provider": "osv",
"references": [
"https://github.com/python-pillow/Pillow/security/advisories/GHSA-462x-pgg6-55fp",
"https://nvd.nist.gov/vuln/detail/CVE-2026-112395",
"https://github.com/python-pillow/Pillow/commit/76869efdbbf2ad7dccd7536520fb61b7a00ab61e",
"https://github.com/python-pillow/Pillow",
"https://github.com/python-pillow/Pillow/releases/tag/13.0.0"
],
"provider_metadata": {
"cwe_ids": [
"CWE-122"
],
"severity": "MODERATE",
"github_reviewed": true,
"nvd_published_at": "2026-10-19T21:17:07Z",
"github_reviewed_at": "2026-10-20T20:49:26Z"
}
},
"related_cves": [
{
"value": "CVE-2026-112395",
"evidence": "/aliases"
}
],
"security_relevant": true,
"severity_evidence": "/severity/0/score"
},
"source_id": "core-osv",
"document_id": 446808,
"published_at": "2026-10-20T20:49:26+00:00"
}