GHSA-69qc-prxg-h2c7
CVE Information
Summary
cupsdCreateJob's option-string builder inscheduler/job.csanitizes every normal IPP job-attribute value (converting whitespace to spaces, stripping control characters, and escaping\ ' ") before writing it into the filteroptionsstring 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 thelpuser). However, forCUPS_PTYPE_FAXprinters the daemon maps two attacker-controlled job attributes —destination-uri(tel:…) →phoneandpre-dial-string→faxPrefix— into the PWG→PPD option array, and that array is emitted in a separate loop that uses a rawcupsCopyString()with no sanitization at all. Because thedestination-uriscollection 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-lpclass of the CVE this incompletely fixes. If the downstream fax filter does not passfaxPrefix/phoneto 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:
cupsdCreateJoboption-string builder (theset_hold_until/ options assembly block returning theoptionsbuffer)- Lines: ingestion
3923–3934; sanitizing main loop4147–4167; vulnerable raw emission loop4182–4190; collection-skip4008.
Vulnerability Details
Root Cause
Fax option ingestion (
scheduler/job.c:3923-3934) — values come straight from the job'sdestination-uriscollection: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-urisattribute is anIPP_TAG_BEGIN_COLLECTION, which the main loop explicitlycontinues 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-urisurvives verbatim into theoptionsstring — 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 whichphone/faxPrefixreach 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 thecupsCopyStringsemantics. It feeds a single attackerpre-dial-stringthrough both and prints the bytes each produces.payload.ipptest— anipptoolPrint-Job request delivering the injection to a real fax queue.Steps to Reproduce
End-to-end on a built cupsd with a fax queue:cc -O2 -o poc poc.c ./pocipptool -tv ipp://localhost/printers/FAXQ payload.ipptest # inspect the filter `options` (argv[5]) / cupsd debug log for the raw newlineExpected (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_COLLECTIONskip atjob.c:4008, the sanitizer at4147-4167, and the rawcupsCopyStringat4185/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/phonejob.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
optionsstring is argv[5] to every print/fax filter. Injected newlines/option tokens infaxPrefix/phoneallow forging additional options or records that a downstream fax filter re-parses;faxPrefix/phoneare historically consumed in AT/dial-command contexts, so a fax filter that passes them to a shell or modem command yields code execution as thelpuser (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