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:
- Asks libtiff for
TIFFTileSize(tiff)(the bytes-per-tile) andTIFFTileRowSize(tiff)(the bytes per row as stored by libtiff). - Reallocates
state->bufferto exactlytile_bytes_size. - For each tile origin
(x, y)and each plane, callsTIFFReadTileintostate->buffer, then walks the tile row by row, calling the configuredImagingShuffler(herecopy4) on each row's worth of source bytes.
The validation done before the loop is:
if (tile_bytes_size > ((tile_length * state->bits / planes + 7) / 8) * tile_width) {
state->errcode = IMAGING_CODEC_BROKEN;
return -1;
}
This rejects oversized libtiff tiles, but it does not reject undersized ones — i.e. the case where libtiff returns a tile_bytes_size strictly less than row_byte_size * tile_length. The loop body, however, indexes into the buffer assuming the buffer is at least row_byte_size * tile_length bytes long.
Vulnerable Code
// src/libImaging/TiffDecode.c:446-490 — _decodeTile
new_data = realloc(state->buffer, state->bytes); // bytes = tile_bytes_size
if (!new_data) { ... }
state->buffer = new_data;
for (y = state->yoff; y < state->ysize; y += tile_length) {
for (plane = 0; plane < planes; plane++) {
ImagingShuffler shuffler = unpackers[plane];
for (x = state->xoff; x < state->xsize; x += tile_width) {
if (TIFFReadTile(tiff, (tdata_t)state->buffer, x, y, 0, plane) == -1) { ... }
current_tile_width = min((INT32)tile_width, state->xsize - x);
current_tile_length = min((INT32)tile_length, state->ysize - y);
for (tile_y = 0; tile_y < current_tile_length; tile_y++) {
shuffler(
(UINT8 *)im->image[tile_y + y] + x * im->pixelsize,
state->buffer + tile_y * row_byte_size, //