OpenPrinting / OpenPrinting/libcupsfilters

texttopdf: comment token across a page boundary writes before the page buffer

Open Beginner friendly
#196 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() writes two bytes before its heap-allocated page buffer
when C/C++ pretty printing recognizes /* at the exact automatic page
boundary. The syntax state is updated before wrapping, but the first comment
character is highlighted after wrapping with column - 1, when column has
already been reset to zero.

The failure is reachable through the normal standalone texttopdf filter
without a fuzzing harness, a PPD, or malformed text.

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

The 6,469-byte PoC is:

6467 bytes of "A" + "/*"

Its SHA-256 is
1f34d3d7cc9a19bd4b543ab3e64102ab9a738bc91737ee818d2cff6138b906ed.
With the fallback Letter page, cpi=12, lpi=8, and pretty-print header,
the page contains 84 columns by 77 lines. The / occupies the final cell and
the * crosses onto a freshly cleared page.

Run:

./reproduce.sh

This invokes the standard seven-argument CUPS filter interface through
ppdFilterCUPSWrapper() and then cfFilterTextToPDF().

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: comment token across a page boundary writes before the page buffer
# Finding ID: texttopdf-comment-page-boundary-oob
# Trigger: cfFilterTextToPDF() writes two bytes before its heap-allocated page
# buffer when C/C++ pretty printing recognizes /* at the exact automatic page
# boundary. The syntax state is updated before wrapping, but the first comment
# character is highlighted after wrapping with column - 1, when column has
# already been reset to zero.
#
# 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 (6469 bytes)
write_file document.txt 1f34d3d7cc9a19bd4b543ab3e64102ab9a738bc91737ee818d2cff6138b906ed <<'POC_PAYLOAD_0'
H4sIAAAAAAACA+3BMQEAAAwCoD4+ZrB/qf3LAGwAAAAAAAA8zQExUFYzRRkAAA==
POC_PAYLOAD_0

Result

ASan reports:

ERROR: AddressSanitizer: heap-buffer-overflow
WRITE of size 2
cfFilterTextToPDF .../cupsfilters/texttopdf.c:1433:40
0x52b0000001fe is located 2 bytes before 25872-byte region

The complete report is in asan.txt. The unsanitized build exits with status
134 after glibc reports double free or corruption (out); see baseline.txt
and baseline-stderr.txt.

The two UBSan diagnostics in sfnt-private.h occur earlier during font
embedding and are independent of this page-buffer write.

Cause and expected behavior

At texttopdf.c:1325-1334, the * and preceding / enter comment state.
At lines 1366-1380, wrapping resets column, line, and the page when the
current column is full. At lines 1425-1433, the comment highlighter then writes
doc.Page[line][column - 1].attr, which addresses the element before the new
page buffer.

The highlighter must retain the pre-wrap location of /, highlight it before
the cursor transition, or guard the previous-cell access across line and page
boundaries. A valid syntax token crossing a layout boundary must not access
outside doc.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 with cupsfilters/texttopdf.c at lines 1325-1433 and run reproduce.sh with the supplied 6469-byte document and sanitizer configuration. Trace the comment-state transition, page wrapping, and highlighter access across the boundary. Done means the standalone filter no longer reports the heap-buffer-overflow and preserves valid comment highlighting at the page boundary.

Written by the indexing model from the issue text.

Assessment

Tech stack
c
Domain
cli
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
76/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.