Repository navigation
Take libtiff scanline geometry tags from the image, not tiffinfo/exif - #10226
Merged
Merged
Conversation
In TiffImagePlugin._save's libtiff branch, only BITSPERSAMPLE was
pinned to the real image's value when building the encoder's tag
list. IMAGEWIDTH, IMAGELENGTH and PLANAR_CONFIGURATION were set from
im.size but then overwritten by the tiffinfo=/exif= merge loop, and
SAMPLESPERPIXEL was never set at all for single-band modes, letting
a value from the caller's tags -- or from a re-saved source TIFF's
own IFD -- reach TIFFSetField unfiltered.
ImagingLibTiffEncode hands TIFFWriteScanline a row buffer sized from
the real image, with no check against libtiff's own scanline size
(computed from the tag-described geometry). Depending on which tag
was overridden, this let libtiff read (SamplesPerPixel/ImageWidth
too large) or, with Predictor=2, write (horizontal differencing
rewrites the scanline in place) past the end of that buffer.
Confirmed live on this build:
- Re-saving a JPEG with exif={SamplesPerPixel: 3, Photometric: RGB}
via `im.save(out, "TIFF", exif=exif, compression="tiff_lzw")`: on
the unfixed baseline, 80 of the 128 heap bytes trailing the row
buffer leaked verbatim into the saved file's row 0. On the fixed
build the real 1-sample-per-pixel geometry is used regardless of
the supplied tags, so the (now self-inconsistent, Photometric=RGB
but 1 sample) output is rejected as unreadable rather than leaking
heap -- matching the reporter's documented expected outcome.
- Re-saving the same JPEG with an oversized ImageWidth tag: the
unfixed baseline crashed with a bus error (this platform's
equivalent of the report's SIGSEGV) trying to read a huge scanline
out of a 64-byte buffer. The fixed build ignores the malicious
width entirely (geometry always comes from the real image) and
completes the save normally with exit code 0.
Fix (reporter's patch 1 of 2, chosen over the Python-only patch 2 for
defense in depth):
- src/PIL/TiffImagePlugin.py: pre-seed `atts` with SAMPLESPERPIXEL,
IMAGEWIDTH, IMAGELENGTH and PLANAR_CONFIGURATION taken from the
image being saved, alongside the existing BITSPERSAMPLE, so no
tiffinfo/exif/source-IFD tag can override the geometry libtiff
sizes its scanline from. This closes the tiffinfo=, exif= and
re-saved-source-IFD routes alike, without touching the raw-writer
behaviour relied on by test_tiff_chunks.
- src/libImaging/TiffDecode.c, ImagingLibTiffEncode: defense in depth
-- reject any remaining configuration where TIFFScanlineSize64()
exceeds the row buffer before the encode loop runs, raising
IMAGING_CODEC_CONFIG instead of over-reading/over-writing. Reuses
ImagingLibTiffEncodeCleanup() on both this new path and the
existing TIFFWriteScanline error path so the TIFF handle is always
closed before its buffer is freed (the same use-after-free class
fixed for GHSA-vvxp-fm22-fchw).
Adds test_geometry_tags to Tests/test_file_libtiff.py, parametrised
over oversized IMAGEWIDTH/IMAGELENGTH, SAMPLESPERPIXEL=3 and
PLANAR_CONFIGURATION=2 via tiffinfo=.
Reported-by: carlini
Fixes: GHSA-xcvg-w56q-r89m
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
In TiffImagePlugin._save's libtiff branch, only BITSPERSAMPLE was pinned to the real image's value when building the encoder's tag list. IMAGEWIDTH, IMAGELENGTH and PLANAR_CONFIGURATION were set from im.size but then overwritten by the tiffinfo=/exif= merge loop, and SAMPLESPERPIXEL was never set at all for single-band modes, letting a value from the caller's tags -- or from a re-saved source TIFF's own IFD -- reach TIFFSetField unfiltered.
ImagingLibTiffEncode hands TIFFWriteScanline a row buffer sized from the real image, with no check against libtiff's own scanline size (computed from the tag-described geometry). Depending on which tag was overridden, this let libtiff read (SamplesPerPixel/ImageWidth too large) or, with Predictor=2, write (horizontal differencing rewrites the scanline in place) past the end of that buffer.
Confirmed live on this build:
im.save(out, "TIFF", exif=exif, compression="tiff_lzw"): on the unfixed baseline, 80 of the 128 heap bytes trailing the row buffer leaked verbatim into the saved file's row 0. On the fixed build the real 1-sample-per-pixel geometry is used regardless of the supplied tags, so the (now self-inconsistent, Photometric=RGB but 1 sample) output is rejected as unreadable rather than leaking heap -- matching the reporter's documented expected outcome.Fix (reporter's patch 1 of 2, chosen over the Python-only patch 2 for defense in depth):
attswith SAMPLESPERPIXEL, IMAGEWIDTH, IMAGELENGTH and PLANAR_CONFIGURATION taken from the image being saved, alongside the existing BITSPERSAMPLE, so no tiffinfo/exif/source-IFD tag can override the geometry libtiff sizes its scanline from. This closes the tiffinfo=, exif= and re-saved-source-IFD routes alike, without touching the raw-writer behaviour relied on by test_tiff_chunks.Adds test_geometry_tags to Tests/test_file_libtiff.py, parametrised over oversized IMAGEWIDTH/IMAGELENGTH, SAMPLESPERPIXEL=3 and PLANAR_CONFIGURATION=2 via tiffinfo=.