OpenPrinting / OpenPrinting/libcupsfilters

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

Open
#198 3 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
C
Stars
17
Forks
71
Avg merge
2d 17h
Merged PRs (30d)
13

Description

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.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by running ./reproduce.sh with the supplied poc/document.txt and inspect cfFilterTextToText() in texttotext.c, especially the calculations at lines 774 and 1088. Compare UBSan and ASan output before and after the change; done means the provided dimensions no longer produce signed overflow or heap-buffer-overflow, including safe handling of allocation failure.

Written by the indexing model from the issue text.

Assessment

Tech stack
c
Domain
security
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
62/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.