OpenPrinting / OpenPrinting/libcupsfilters

texttotext: accepted zero tab width causes division by zero

Open Beginner friendly
#180 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

cfFilterTextToText() accepts TabWidth=0, logs that the value is invalid and
the default will be used, but leaves the internal tab_width set to zero. A
single tab in the document then reaches a modulo operation with a zero divisor
and terminates the filter with SIGFPE.

The failure is deterministic through the normal standalone filter pipeline and
requires only a one-byte text document.

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 is one tab byte (0x09) and has SHA-256
2b4c342f5433ebe591a1da77e013d1b72475562d48578dca8b84bac6651c3cb9.
Run:

./reproduce.sh

This supplies TabWidth=0 through the standard CUPS job-options argument.

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: accepted zero tab width causes division by zero
# Finding ID: texttotext-zero-tab-width-fpe
# Trigger: cfFilterTextToText() accepts TabWidth=0, logs that the value is
# invalid and the default will be used, but leaves the internal tab_width set
# to zero. A single tab in the document then reaches a modulo operation with a
# zero divisor and terminates the filter with SIGFPE.
#
# 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 (1 bytes)
write_file document.txt 2b4c342f5433ebe591a1da77e013d1b72475562d48578dca8b84bac6651c3cb9 <<'POC_PAYLOAD_0'
H4sIAAAAAAACA+MEAClX3qsBAAAA
POC_PAYLOAD_0

Result

UBSan and ASan report:

texttotext.c:1055:20: runtime error: division by zero
ERROR: AddressSanitizer: FPE
cfFilterTextToText .../cupsfilters/texttotext.c:1055:20

The complete diagnostic is in asan.txt. The unsanitized build also receives
SIGFPE and exits with status 136. Its log first says
Tab width not set or invalid, use default, then reports Tab width: 0.

Cause and expected behavior

texttotext.c:551-570 parses the option directly into tab_width.
Lines 573-575 detect that zero is invalid but only log a message; they do not
restore the initialized default value of eight. At line 1055, processing a tab
evaluates column % tab_width.

An invalid option must either be rejected or replaced with the documented
default before document processing. No job option should leave a zero divisor
in the formatter.

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 texttotext.c around lines 551-575 to trace TabWidth parsing and the invalid-value handling, then inspect line 1055 where the tab is processed. Run reproduce.sh with poc/document.txt and TabWidth=0. Done means invalid input cannot leave a zero divisor, the documented default is used or the option is rejected, and the reproducer no longer produces SIGFPE.

Written by the indexing model from the issue text.

Assessment

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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.