Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions Tests/test_file_libtiff.py
Original file line number Diff line number Diff line change
Expand Up @@ -1323,6 +1323,62 @@ def test_realloc_overflow(self, monkeypatch: pytest.MonkeyPatch) -> None:
with pytest.raises(OSError, match="decoder error -9"):
im.load()

@pytest.mark.parametrize(
"data",
(
# 16x16 8-bit greyscale tile, with a PhotometricInterpretation tag that
# has two values. Pillow reads it as YCbCr (rawmode RGBX, 4 bytes/pixel)
# while libtiff ignores it and decodes 1 byte/pixel.
(
b"SUkqAAgAAAAKAAABBAABAAAAEAAAAAEBBAABAAAAEAAAAAIBAwABAAAACAAA"
b"AAMBAwABAAAACAAAAAYBAwACAAAABgAGABUBAwABAAAAAQAAAEIBBAABAAAA"
b"EAAAAEMBBAABAAAAEAAAAEQBBAABAAAAhgAAAEUBBAABAAAACwEAAAAAAAB4"
b"nAEAAf/+AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUm"
b"JygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJT"
b"VFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+A"
b"gYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6yt"
b"rq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna"
b"29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+foAAQIDBJ9Bepo="
),
# 16x16 strip with duplicated BitsPerSample and PhotometricInterpretation
# tags. libtiff keeps the first (8-bit YCbCr, read as 4 bytes/pixel RGBA)
# while Pillow keeps the last (16-bit RGB, rawmode RGB;16N, 6 bytes/pixel).
(
b"SUkqAAgAAAAMAAABAwABAAAAEAAAAAEBAwABAAAAEAAAAAIBAwADAAAAugEA"
b"AAIBAwADAAAAwAEAAAMBAwABAAAACAAAAAYBAwABAAAABgAAAAYBAwABAAAA"
b"AgAAABEBBAABAAAAngAAABUBAwABAAAAAwAAABYBAwABAAAAEAAAABcBBAAB"
b"AAAAHAEAABICAwACAAAAAQABAAAAAAB4nGNgZGJmYWVj5+Dk4ubh5eMXEBQS"
b"FhEVE5eQlJKWkZWTV1BUUlZRVVPX0NTS1tHV0zcwNDI2MTUzt7C0sraxtbN3"
b"cHRydnF1c/fw9PL28fXzDwgMCg4JDQuPiIyKjomNi09ITEpOSU1Lz8jMys7J"
b"zcsvKCwqLiktK6+orKquqa2rb2hsam5pbWvv6Ozq7unt658wcdLkKVOnTZ8x"
b"c9bsOXPnzV+wcNHiJUuXLV+xctXqNWvXrd+wcdPmLVu3bd+xc9fuPXv37T9w"
b"8NDhI0ePHT9x8tTpM2fPnb9w8dLlK1evXb9x89btO3fv3X/w8NHjJ0+fPX/x"
b"8tXrN2/fvf/w8dPnL1+/ff/x89fvP3///WcY9f+I9j8AoGJ+kAgACAAIABAA"
b"EAAQAA=="
),
# 4x2 PackBits strip with duplicated BitsPerSample, Photometric
# Interpretation and SamplesPerPixel tags. libtiff keeps the first
# (8-bit YCbCr, read as 4 bytes/pixel RGBA) while Pillow keeps the last
# (6 channel RGB, rawmode RGBAXX, 6 bytes/pixel).
(
b"SUkqAAgAAAAPAAABBAABAAAABAAAAAEBBAABAAAAAgAAAAIBAwADAAAAwgAA"
b"AAIBAwAGAAAAyAAAAAMBAwABAAAABYAAAAYBAwABAAAABgAAAAYBAwABAAAA"
b"AgAAABEBBAABAAAA2gAAABUBAwABAAAAAwAAABUBAwABAAAABgAAABYBBAAB"
b"AAAAAgAAABcBBAABAAAAGQAAABwBAwABAAAAAQAAAFIBAwADAAAA1AAAABIC"
b"AwACAAAAAQABAAAAAAAIAAgACAAIAAgACAAIAAgACAACAAAAAAAXAAECAwQF"
b"BgcICQoLDA0ODxAREhMUFRYX"
),
),
)
@pytest.mark.filterwarnings("ignore:Metadata Warning")
def test_unpacker_row_wider_than_libtiff_row(self, data: bytes) -> None:
# The unpacker chosen by Pillow reads more bytes per row than libtiff
# decoded, so the decoder must reject the image rather than read past
# the end of its buffer
with Image.open(io.BytesIO(base64.b64decode(data))) as im:
with pytest.raises(OSError, match="decoder error -2"):
im.load()

@pytest.mark.parametrize("compression", ("tiff_adobe_deflate", "jpeg"))
def test_save_multistrip(self, compression: str, tmp_path: Path) -> None:
im = hopper("RGB").resize((256, 256))
Expand Down
21 changes: 18 additions & 3 deletions src/libImaging/TiffDecode.c
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,14 @@ _decodeAsRGBA(Imaging im, ImagingCodecState state, TIFF *tiff) {
rows_per_block = state->ysize;
}

// TiffRGBAImages are 32bits/pixel. If the unpacker reads more bits per pixel
// than that, (at least) the last row of the block will have a read buffer
// overflow.
if (state->bits != 32) {
state->errcode = IMAGING_CODEC_BROKEN;
return -1;
}

if (!(TIFFRGBAImageOK(tiff, emsg) && TIFFRGBAImageBegin(&img, tiff, 0, emsg))) {
state->errcode = IMAGING_CODEC_BROKEN;
// nothing to clean up, just return
Expand Down Expand Up @@ -320,7 +328,7 @@ _decodeTile(
) {
INT32 x, y, tile_y, current_tile_length, current_tile_width;
UINT32 tile_width, tile_length;
tsize_t tile_bytes_size, row_byte_size;
tsize_t tile_bytes_size, row_byte_size, unpacker_row_byte_size;
UINT8 *new_data;

tile_bytes_size = TIFFTileSize(tiff);
Expand Down Expand Up @@ -352,15 +360,22 @@ _decodeTile(
return -1;
}

if (tile_bytes_size !=
((tile_length * state->bits / planes + 7) / 8) * tile_width) {
unpacker_row_byte_size = ((tsize_t)tile_width * state->bits / planes + 7) / 8;
if (tile_bytes_size != unpacker_row_byte_size * tile_length) {
// If the tile size as expected by LibTiff isn't what we're expecting, abort.
// man: TIFFTileSize returns the equivalent size for a tile of data as it
// would be returned in a call to TIFFReadTile ...
state->errcode = IMAGING_CODEC_BROKEN;
return -1;
}

// if the unpacker calculated row size is > row byte size, (at least) the last
// row of the tile will have a read buffer overflow.
if (unpacker_row_byte_size > row_byte_size) {
state->errcode = IMAGING_CODEC_BROKEN;
return -1;
}

/* realloc to fit whole tile */
/* malloc check above */
new_data = realloc(state->buffer, tile_bytes_size);
Expand Down
Loading