OpenPrinting / OpenPrinting/libcupsfilters
texttotext: first-word wrapping reads before the page buffer
Nobody has claimed this yet.
- Dominant language
- C
- Stars
- 17
- Forks
- 71
- Avg merge
- 2d 17h
- Merged PRs (30d)
- 13
Description
Summary
cfFilterTextToText() reads one byte before its heap-allocated output page
while looking backward for a space in a full first-line word. The loop
dereferences the candidate byte before checking whether its logical index is
still non-negative.
The failure is reached by a normal texttotext invocation with an 81-byte
ASCII document and ordinary job options. No malformed container or fuzzing
harness is involved.
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 81 A bytes and has SHA-256
9feacd760dfda20e5e0accf9ddeb8b5c01276a56dc3518046a26f5276fe15041.
The options select word wrapping, an 80-column page, and a zero-column left
margin:
./reproduce.sh
The first 80 bytes fill the first line. Processing byte 81 enters the
backward delimiter scan with no space anywhere in the line.
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: texttotext: first-word wrapping reads before the page buffer
# Finding ID: texttotext-first-word-wordwrap-oob
# Trigger: cfFilterTextToText() reads one byte before its heap-allocated
# output page while looking backward for a space in a full first-line word.
# The loop dereferences the candidate byte before checking whether its logical
# index is still non-negative.
#
# 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 (81 bytes)
write_file document.txt 9feacd760dfda20e5e0accf9ddeb8b5c01276a56dc3518046a26f5276fe15041 <<'POC_PAYLOAD_0'
H4sIAAAAAAACA3N0pDIAAHjsAwVRAAAA
POC_PAYLOAD_0
Result
ASan reports:
ERROR: AddressSanitizer: heap-buffer-overflow
READ of size 1
cfFilterTextToText .../cupsfilters/texttotext.c:909:46
0x52a0000001ff is located 1 bytes before 21656-byte region
The complete report is in asan.txt. The unsanitized build exits normally
with status 0 and produces a wrapped two-line page, so this is a silent
out-of-bounds read in a release-style build; see baseline.txt.
Cause and expected behavior
At texttotext.c:909-910, the loop condition is:
*p != ' ' && i >= 0
When a full line contains no space, p eventually becomes
out_page - 1 while i becomes -1. C left-to-right && evaluation reads
*p before testing i >= 0.
The bounds test must precede the dereference, for example by checking
i >= 0 first. A delimiter-free word that occupies the first complete line
must wrap without reading before the output page.
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 cupsfilters/texttotext.c around lines 909-910 and inspect the backward delimiter scan in cfFilterTextToText(). Run reproduce.sh with the 81-byte document and, if available, the ASan build to confirm the read before the page buffer. Done means a delimiter-free first-line word wraps without an out-of-bounds read and the normal output remains unchanged.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- c
- Domain
- backend
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 78/100