Skip to content

ppd-generator: escape control chars in IPP string attributes emitted into PPD - #89

Open
Pst-itsl1 wants to merge 1 commit into
OpenPrinting:masterfrom
Pst-itsl1:harden-ppd-generator-string-escape
Open

Pst-itsl1 wants to merge 1 commit into
OpenPrinting:masterfrom
Pst-itsl1:harden-ppd-generator-string-escape

Conversation

@Pst-itsl1

Copy link
Copy Markdown

What

Route printer-make-and-model through a new ppd_escape_string() helper before embedding into the *Manufacturer, *ModelName, *Product, *NickName, and *ShortNickName directives in ppd/ppd-generator.c.

Adds regression tests T08b / T08c / T08d in ppd/test_ppd_generator.c that assert newline, double-quote, and backslash in printer-make-and-model are neutralised before being written into the generated PPD.

Why

printer-make-and-model is a network-influenceable IPP string attribute: on an LAN with cups-browsed and avahi-daemon running (default on Debian task-desktop and most desktop installs), an mDNS-advertised printer's IPP Get-Printer-Attributes response feeds directly into ppdCreatePPDFromIPP2(), which today emits the value verbatim into a quoted PPD directive.

A \n in that value breaks out of the quoted string context and the following bytes are parsed as new PPD directives by the resulting file. I captured the intermediate PPD via LD_PRELOAD on Debian 12 bookworm (cups-filters 1.28.17-3+deb12u2) and confirmed that a payload of the form

Acme\n*FoomaticRIPCommandLine: "..."

yields:

*Manufacturer: "Acme
*FoomaticRIPCommandLine: "..."
*% ..."

in the generated PPD.

What this PR is NOT claiming

I do not claim reachable RCE on shipping distros:

  • On the specific cups-filters 1.28.17 + Debian 12 combination I tested, foomatic-rip uses first-declaration-wins semantics and the baseline PPD template already emits *FoomaticRIPCommandLine:"" (empty) before the make/model line. The injected directive is present in the PPD but is ignored by the downstream parser.
  • On 2.x this ordering may be identical or may differ per distro / packaging / parser version. I did not verify all combinations.

The point of this PR is defense-in-depth: emitter escape discipline should not depend on any downstream parser happening to be lenient in a specific way. The class of bug is the same "incomplete blacklist" pattern that was previously fixed piecewise in filter/foomatic-rip/util.c (CVE-2015-8327 backtick, CVE-2015-8560 semicolon).

What the tests assert

The Group 2b tests deliberately only assert emitter behaviour (escape discipline in the resulting PPD text), not downstream parser behaviour:

  • T08b — \n → single space; no \n* sequence appears in the output.
  • T08c — " → \" in the emitted value.
  • T08d — \ → \\ in the emitted value.

They do not assert any specific parser refuses execution, because that guarantee is a parser concern, not a generator concern.

Prior disclosure context

Originally filed as private GitHub Security Advisory GHSA-5wv7-gqmh-2pf2 against OpenPrinting/cups-filters on 2026-09-22. Closed the same day by @zdohnal with routing feedback:

libppd is not part of cups-filters project, please report to the correct project.

Resubmitting the fix here — as a plain hardening PR without CVE framing — per that routing feedback. If the maintainers judge this warrants a CVE-ID, that decision is theirs to make; I am not requesting one.

Testing

  • Local: gcc -fsyntax-only on ppd/ppd-generator.c after the change produces no new warnings/errors related to the new function or its call site. (Full build against libcupsfilters 2.x was not possible in my lab — the Debian 12 host only ships 1.x.)
  • CI: relying on the project's existing ci/autopkgtest (Debian) and cppcheck static-analysis workflows.
  • Regression coverage: three new assertions in ppd/test_ppd_generator.c (Group 2b, T08b–T08d), bringing the file's stated total to 48 assertions across 13 groups (was 45/12).

Credit

…into PPD

Route printer-make-and-model through a new ppd_escape_string() helper
before embedding into *Manufacturer, *ModelName, *Product, *NickName,
and *ShortNickName. This prevents an attacker-influenceable IPP
attribute (from an LAN mDNS-discovered printer picked up by
cups-browsed) that contains a raw newline, double-quote, or backslash
from breaking out of its quoted PPD string context and appearing as
additional PPD directives in the generated file.

This is a defense-in-depth fix. No downstream execution of injected
directives is demonstrated on the current cups-filters 1.28.17
(Debian bookworm) foomatic-rip parser, because its
first-declaration-wins semantics keep the injected directive dormant
behind the empty baseline template entry. The escape discipline
belongs in the emitter regardless: parser semantics are not a
contract the PPD generator can rely on across distros or future
refactors.

Add regression tests T08b-T08d in ppd/test_ppd_generator.c that
assert:

  T08b  \n in printer-make-and-model is squashed to a space; no
        second '*directive' line appears in the output.
  T08c  " in printer-make-and-model is backslash-escaped.
  T08d  \ in printer-make-and-model is doubled.

The tests deliberately do NOT assert any specific downstream parser
behaviour; they lock in the emitter's escape discipline so future
refactors do not silently reopen the gap.

Context: reported as GHSA-5wv7-gqmh-2pf2 on the cups-filters
repository (routed here by upstream per zdohnal's feedback). No CVE
is requested; the disclosure is a defense-in-depth hardening PR
against the class of missing-blacklist-char bugs previously fixed
piecewise in filter/foomatic-rip/util.c
(CVE-2015-8327 backtick, CVE-2015-8560 semicolon).

Reporter: Pongsathon Sirithanyakul <pongsathon@itselectlab.com>
Co-credit: Warunyou Sunpachit, Khamolwan Hnunainam
           (IT SELECT LAB Co., Ltd., Thailand)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant