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
35 changes: 35 additions & 0 deletions Tests/test_file_libtiff.py
Original file line number Diff line number Diff line change
Expand Up @@ -805,6 +805,41 @@ def test_save_bytesio_encoder_error(self) -> None:
tiffinfo={TiffImagePlugin.PREDICTOR: 3},
)

def test_save_bytesio_seek_past_end(self, tmp_path: Path) -> None:
# When writing the directory, libtiff seeks past the end of the data
# written so far, and then writes the tag values there. Check that the
# in-memory write buffer grows to fit, and that the bytes that are
# skipped over are zero, as they would be when writing to a file.

# 7 distinct bytes per row: PackBits emits 8 bytes per row, so the strip
# plus the header are exactly 64 KiB, the initial size of the buffer
im = Image.new("L", (7, 8191))
im.putdata([(i * 37) & 0xFF for i in range(7 * 8191)])
kwargs: dict[str, Any] = {
"compression": "packbits",
"description": "D" * 65535,
}

outfile = tmp_path / "temp.tif"
im.save(outfile, **kwargs)
buffer_io = io.BytesIO()
im.save(buffer_io, format="tiff", **kwargs)
assert buffer_io.getvalue() == outfile.read_bytes()

# odd-length tag values, so that libtiff pads between them. Large enough
# that the buffer has to be reallocated
im = hopper().resize((300, 300))
kwargs = {
"compression": "tiff_lzw",
"description": "abcdef",
"software": "xyz0",
"tiffinfo": {315: "artist", 33432: "copyright!"},
}
im.save(outfile, **kwargs)
buffer_io = io.BytesIO()
im.save(buffer_io, format="tiff", **kwargs)
assert buffer_io.getvalue() == outfile.read_bytes()

def test_save_ycbcr(self, tmp_path: Path) -> None:
im = hopper("YCbCr")
outfile = tmp_path / "temp.tif"
Expand Down
15 changes: 14 additions & 1 deletion src/libImaging/TiffDecode.c
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,13 @@ _tiffWriteProc(thandle_t hdata, tdata_t buf, tsize_t size) {
TIFFSTATE *state = (TIFFSTATE *)hdata;
tsize_t to_write;

// libtiff may have seeked past the end of the buffer, e.g. when writing the
// directory, so the required size is based on the current location.
to_write = MIN(size, state->size - (tsize_t)state->loc);
if (state->flrealloc && size > to_write) {
tdata_t new_data;
tsize_t newsize = state->size;
while (newsize < (size + state->size)) {
while (newsize < (size + (tsize_t)state->loc)) {
if (newsize > INT_MAX - 64 * 1024) {
return 0;
}
Expand All @@ -84,9 +86,20 @@ _tiffWriteProc(thandle_t hdata, tdata_t buf, tsize_t size) {
// fail out
return 0;
}
// zero the new space, so that any bytes libtiff seeks over without
// writing are 0 in the output, as they would be in a file
memset((UINT8 *)new_data + state->size, 0, newsize - state->size);
state->data = new_data;
state->size = newsize;
to_write = size;
} else if (to_write < 0) {
TIFFError(
"_tiffWriteProc",
"Invalid Write at loc %" PRIu64 ", size: %" PRIu64,
state->loc,
(toff_t)state->size
);
return 0;
}

_TIFFmemcpy((UINT8 *)state->data + state->loc, buf, to_write);
Expand Down
Loading