Skip to content

GHSA-69qc-prxg-h2c7 on CTRL-OS 26.05

Aliases: GHSA-69qc-prxg-h2c7

Packages: cups

Status: Plausible

Advisory Information

Summary

cupsdCreateJob's option-string builder in scheduler/job.c sanitizes every normal IPP job-attribute value (converting whitespace to spaces, stripping control characters, and escaping \ ' ") before writing it into the filter options string that is handed to print filters as argv[5]. This sanitizer was added/strengthened to fix CVE-2026-34980 (newline-in-option-value reparsed as a PPD/option record → code execution as the lp user). However, for CUPS_PTYPE_FAX printers the daemon maps two attacker-controlled job attributes — destination-uri (tel:…) → phone and pre-dial-stringfaxPrefix — into the PWG→PPD option array, and that array is emitted in a separate loop that uses a raw cupsCopyString() with no sanitization at all. Because the destination-uris collection attribute is explicitly skipped by the main (sanitizing) loop, these fax values reach the filter options string only through the unsanitized path. The exact injection primitive CVE-2026-34980 fixed is therefore still reachable on fax queues.


Severity

  • CVSS: 7.5 (High) — CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H (AC:H reflects the precondition that a FAX-type queue is configured; impact is the code-exec-as-lp class of the CVE this incompletely fixes. If the downstream fax filter does not pass faxPrefix/phone to a shell/AT-command context, the realized impact is option-stream injection only — see Impact.)
  • CWE: CWE-93 (Improper Neutralization of CRLF Sequences) / CWE-94 (Code Injection); incomplete fix of CWE-150-class neutralization.

Affected Version

  • Package: https://github.com/OpenPrinting/cups
  • Version: 2.5.0 (HEAD dc9dea0); any build that carries the CVE-2026-34980 fix in the main loop but not in the fax mapped-option loop.
  • File: scheduler/job.c
  • Function: cupsdCreateJob option-string builder (the set_hold_until/ options assembly block returning the options buffer)
  • Lines: ingestion 3923–3934; sanitizing main loop 4147–4167; vulnerable raw emission loop 4182–4190; collection-skip 4008.

Vulnerability Details

Root Cause

Fax option ingestion (scheduler/job.c:3923-3934) — values come straight from the job's destination-uris collection:

if ((job->printer->type & CUPS_PTYPE_FAX) &&
    (attr = ippFindAttribute(job->attrs, "destination-uris", IPP_TAG_BEGIN_COLLECTION)) != NULL)
{
    ipp_t *ipp = ippGetCollection(attr, 0);
    const char *destination_uri = ippGetString(ippFindAttribute(ipp, "destination-uri", IPP_TAG_URI), 0, NULL);
    const char *pre_dial_string  = ippGetString(ippFindAttribute(ipp, "pre-dial-string", IPP_TAG_TEXT), 0, NULL);

    if (destination_uri && !strncmp(destination_uri, "tel:", 4))
        num_pwgppds = cupsAddOption("phone", destination_uri + 4, num_pwgppds, &pwgppds);
    if (pre_dial_string)
        num_pwgppds = cupsAddOption("faxPrefix", pre_dial_string, num_pwgppds, &pwgppds);
}

The main attribute loop applies the CVE-2026-34980 sanitizer to every normal job-attribute value (scheduler/job.c:4147-4167):

case IPP_TAG_TEXT :
case IPP_TAG_NAME : ...
    for (valptr = attr->values[i].string.text; *valptr; valptr ++)
    {
        if (isspace(*valptr & 255))              *optptr++ = ' ';      // newline/tab -> space
        else if ((*valptr & 255) >= ' ' && *valptr != 0x7f) {         // drop control chars
            if (strchr("\\\'\"", *valptr)) *optptr++ = '\\';           // escape \ ' "
            *optptr++ = *valptr;
        }
    }

But the destination-uris attribute is an IPP_TAG_BEGIN_COLLECTION, which the main loop explicitly continues past (scheduler/job.c:4008), so the fax values never go through the sanitizer there. They are instead emitted by the PWG→PPD mapped-option loop with no sanitization (scheduler/job.c:4182-4190):

for (i = num_pwgppds, pwgppd = pwgppds; i > 0; i --, pwgppd ++)
{
    *optptr++ = ' ';
    cupsCopyString(optptr, pwgppd->name,  optlength - (size_t)(optptr - options));
    optptr += strlen(optptr);
    *optptr++ = '=';
    cupsCopyString(optptr, pwgppd->value, optlength - (size_t)(optptr - options));  // RAW
    optptr += strlen(optptr);
}

A newline or other control character in pre-dial-string / destination-uri survives verbatim into the options string — the precise condition the CVE-2026-34980 fix removes for ordinary attributes.

Why this is the only path

The fax values are sourced from a collection attribute that the sanitizing loop skips (:4008), so the unsanitized mapped-option loop is the sole route by which phone/faxPrefix reach the filter options string. There is no second, sanitizing emission of these values.


Proof of Concept

Necessary Scripts

  • poc.c — a self-contained harness that embeds the verbatim sanitizer loop (job.c:4147-4167) and the verbatim fax mapped-option loop (job.c:4182-4190), plus the cupsCopyString semantics. It feeds a single attacker pre-dial-string through both and prints the bytes each produces.
  • payload.ipptest — an ipptool Print-Job request delivering the injection to a real fax queue.

Steps to Reproduce

cc -O2 -o poc poc.c
./poc
End-to-end on a built cupsd with a fax queue:
ipptool -tv ipp://localhost/printers/FAXQ payload.ipptest
# inspect the filter `options` (argv[5]) / cupsd debug log for the raw newline

Expected (correct) Output

The fax value should be neutralized exactly like a normal attribute — the newline replaced by a space, no embedded control bytes in the options string.

Actual Output (vulnerable)

=== Path A: main-loop sanitizer (job.c:4147-4167) ===
  raw : 1234 *cupsFilter2: \"application/pdf application/vnd.cups-postscript 0 /tmp/pwn\"
  -> newline neutralized to space: yes (safe)

=== Path B: fax pwgppd loop (job.c:4182-4190) — ACTUAL fax path ===
  raw :  faxPrefix=1234\n*cupsFilter2: "application/pdf application/vnd.cups-postscript 0 /tmp/pwn"
  hex : ... 31 32 33 34 0A 2A 63 75 70 73 ...        <- 0x0A (newline) intact
  -> newline survives into filter options: YES  <-- INJECTION (sanitizer bypassed)

[CONFIRMED] The fax option path emits the attacker newline RAW.

Manual Verification

Confirm the divergence in source: the IPP_TAG_BEGIN_COLLECTION skip at job.c:4008, the sanitizer at 4147-4167, and the raw cupsCopyString at 4185/4188. The harness reproduces both loops byte-for-byte.

Output Analysis

Path Code Newline (0x0A) in result Safe?
Normal attribute job.c:4147-4167 (sanitizer) replaced with space yes
Fax faxPrefix/phone job.c:4182-4190 (raw copy) preserved no

Identical attacker input; only the fax path leaks the control byte.


Impact proposed by reporter

  • Confidentiality / Integrity / Availability: This is the same injection class as CVE-2026-34980. The filter options string is argv[5] to every print/fax filter. Injected newlines/option tokens in faxPrefix/phone allow forging additional options or records that a downstream fax filter re-parses; faxPrefix/phone are historically consumed in AT/dial-command contexts, so a fax filter that passes them to a shell or modem command yields code execution as the lp user (the uid filters run under). Where the specific fax filter does not reach such a sink, the realized impact is unsanitized injection into the filter option stream.
  • Attack vector: An unprivileged user who can submit a print job (default CUPS policy allows Create-Job/Print-Job) to a configured FAX-type queue.

Reviewed by Mike Sweet, OpenPrinting:

Rescored:

  • Vector is local
  • This issue does not pose a confidentiality risk (attributes do not disclose PII).
  • This issue does not pose an integrity risk - adding another root-owned/controlled filter to the PPD (which would not be incorporated until the next restart of cupsd) does not allow the attacker to do anything they weren't already able to do. The only known filter that supports code execution from the PPD is Foomatic which has its own CVE that has been fixed by not yet released for some reason (I have no control over that), and Foomatic is optional and not part of CUPS.
  • This issue does not affect availability of CUPS or cupsd, but might disable a queue, so re-scored as low.

Fix

[master a9e7e7a3a] Sanitizer destination-uri and pre-dial-string values.

[2.4.x 0cf4e7814] Sanitizer destination-uri and pre-dial-string values.


References

  • CVE-2026-34980 — CUPS IPP option newline → PPD record injection (the bug this incompletely fixes).
  • scheduler/job.c (OpenPrinting CUPS 2.5.0): lines 3923-3934, 4008, 4147-4167, 4182-4190.
  • OpenPrinting CUPS security policy: https://github.com/OpenPrinting/cups/blob/master/SECURITY.md

Attachments

poc.c

Updates

2026-07-10 17:54 CEST

Metadata changes:

  • Status for package cups: “Plausible

2026-07-07 22:44 CEST

Metadata changes:

  • Status for package cups: “New