GHSA-7hqf-mfhx-7r3v
CVE Information
Summary
A malicious or attacker-controlled IPP printer can return a crafted text printer-status attribute, such as
marker-message, containing embedded newline characters. The CUPSippbackend reports this remote printer attribute to the scheduler through the backend stderr/status stream. The current quoting helper escapes quotes and backslashes, but it does not reject or encode LF/CR. As a result, attacker-controlled printer status text can create a new backend status line beginning withPPD:.The scheduler treats backend status lines beginning with
PPD:as requests to update the local queue PPD. In my lab validation, a remotemarker-messageinjected:
PPD: FoomaticRIPCommandLine=...This was written into:
/etc/cups/ppd/<queue>.ppdWhen the queue used a Foomatic-backed PPD/filter profile, a later print job caused
foomatic-ripto read the modified PPD and execute the injected harmless marker command as the CUPS filter userlp.This report does not claim default unauthenticated root RCE. The confirmed impact is conditional code execution as user
lp.Affected Code Locations
The issue appears to involve the CUPS
ippbackend status reporting path, the scheduler backend-status parser, and the PPD update path.In the reviewed source snapshot, the relevant locations are:
backend/ipp.cThe
ippbackend requestsmarker-messagefrom the remote IPP printer:
backend/ipp.c:89-105backend/ipp.c:100:marker-messageThe backend later reads and reports
marker-message:
backend/ipp.c:3298-3318backend/ipp.c:3310-3312:
ippFindAttribute(ipp, "marker-message", IPP_TAG_TEXT)
report_attr(marker)The vulnerable quoting/reporting path is:
backend/ipp.c:3068-3114:quote_string()backend/ipp.c:3094-3106: only backslash, double quote, and single quote are specially escaped before copying bytes into the output buffer.backend/ipp.c:3106:*qptr++ = *s++;There is no rejection or encoding of LF/CR before the string is returned.
The attribute reporting path is:
backend/ipp.c:3121-3184:report_attr()backend/ipp.c:3149-3155:IPP_TAG_TEXT,IPP_TAG_TEXTLANG,IPP_TAG_NAME,IPP_TAG_NAMELANG, andIPP_TAG_KEYWORDvalues are passed throughquote_string(...).backend/ipp.c:3178-3180: the final value is emitted to backend stderr/status as:
fprintf(stderr, "ATTR: %s=%s\n", attr->name, value);Because LF/CR inside the quoted value are preserved, a remote text attribute can split the backend status stream into multiple logical scheduler lines.
scheduler/statbuf.cThe scheduler status buffer parses backend stderr/status output line by line:
scheduler/statbuf.c:110-145:cupsdStatBufUpdate(...)reads from the backend status pipe and searches for newline usingstrchr(sb->buffer, '\n').scheduler/statbuf.c:262-266: lines beginning withPPD:are classified asCUPSD_LOG_PPD.Relevant code:
else if (!strncmp(sb->buffer, "PPD:", 4))
*loglevel = CUPSD_LOG_PPD;
message = sb->buffer + 4;This means a newline injected into an
ATTR:value can create a later independentPPD:line.
scheduler/job.cThe scheduler handles
CUPSD_LOG_PPDmessages as PPD keyword updates:
scheduler/job.c:5436-5461scheduler/job.c:5445: logsPPD: %sscheduler/job.c:5448: parses the message usingcupsParseOptions(...)scheduler/job.c:5456-5457: filters some CUPS-specific special PPD keywords, then adds all other keys tojob->keywords.The current blacklist includes keys such as:
cupsFiltercupsFilter2cupsPortMonitorcupsPreFilterAPPrinterPresetHowever, the reproduced chain uses the third-party Foomatic key:
FoomaticRIPCommandLineThis key is not filtered by this CUPS-specific blacklist and is therefore preserved in
job->keywords.
scheduler/job.cjob completion pathThe collected PPD updates are applied later:
scheduler/job.c:3624-3636scheduler/job.c:3628-3631:
if (job->num_keywords)
cupsdUpdatePrinterPPD(job->printer, job->num_keywords, job->keywords)
scheduler/printers.cThe PPD update function writes accepted keyword/value pairs into the local queue PPD:
scheduler/printers.c:2838-2862:cupsdUpdatePrinterPPD(...)scheduler/printers.c:2862: builds/etc/cups/ppd/<queue>.ppdscheduler/printers.c:2891-2895: writes each accepted keyword into the PPD:
cupsFilePrintf(dst, "*%s: %s\n", keyword->name, keyword->value);This is where the attacker-controlled
FoomaticRIPCommandLinevalue is persisted into the local queue PPD.
- Downstream Foomatic execution path
The modified PPD is later consumed by
foomatic-rip.Relevant downstream locations:
cups-filters/filter/foomatic-rip/options.cFoomaticRIPCommandLineis read as a renderer command line.cups-filters/filter/foomatic-rip/postscript.c- the renderer command line is built and started during the filter path.
In my previous source review, the downstream execution evidence maps to the
foomatic-riprenderer command path where the command line is logged as:
Starting renderer with command: "..."Root Cause
The root cause is a status-stream injection across trust boundaries.
The remote IPP printer controls a text attribute value. That value is written by the CUPS
ippbackend into a scheduler-consumed backend status stream. The backend quoting helper escapes quotes and backslashes, but it preserves LF/CR. The scheduler then interprets the injected newline as a new backend status message.The attacker-controlled value has this logical shape:
marker-message = "status-line-before\nPPD: FoomaticRIPCommandLine=cat;/usr/bin/id>/tmp/<marker>;/bin/chmod${IFS}0644${IFS}/tmp/<marker>;#\nDEBUG: status-line-after"The backend status stream is then interpreted as separate lines:
ATTR: marker-message='"status-line-before
PPD: FoomaticRIPCommandLine=cat;/usr/bin/id>/tmp/<marker>;...
DEBUG: status-line-after"'The injected
PPD:line is treated as a PPD update instruction. The scheduler filters several CUPS-specific dangerous PPD keys, but it does not blockFoomaticRIPCommandLine. The accepted key/value is stored injob->keywordsand later written into the local queue PPD bycupsdUpdatePrinterPPD().When a later print job uses the modified PPD,
foomatic-ripreadsFoomaticRIPCommandLineand executes the renderer command line. In my lab validation, the command only wrote a harmless/tmpmarker containing the execution UID.Reproduced Call Chain
- Target CUPS host has an IPP queue pointing to an attacker-controlled or compromised IPP printer.
- A print job causes the CUPS
ippbackend to query the remote printer.- The malicious printer returns a
marker-messagetext value containing LF-separated status lines.backend/ipp.creports the attribute throughreport_attr().quote_string()preserves LF/CR in the attribute value.fprintf(stderr, "ATTR: %s=%s\n", ...)emits attacker-controlled newlines into the backend status stream.scheduler/statbuf.cparses the injected newline as a separate backend status line.- The injected
PPD: FoomaticRIPCommandLine=...line becomesCUPSD_LOG_PPD.scheduler/job.cparses the PPD update and keepsFoomaticRIPCommandLinebecause it is not in the CUPS-specific blacklist.- On job completion,
cupsdUpdatePrinterPPD()writes the key/value into/etc/cups/ppd/<queue>.ppd.- A later print job loads the modified PPD.
foomatic-ripreadsFoomaticRIPCommandLine.- The harmless marker command executes as user
lp.PoC
I validated this in an isolated Ubuntu 24.04 VM using a harmless marker command only.
Tested environment:
- Ubuntu 24.04
- CUPS 2.4.7
- cups-browsed 2.0.0
- cups-filters 2.0.0
- libcupsfilters2t64 2.0.0
- libppd2 2.0.0
- foomatic-rip 2.0.0
The PoC is split into three scripts:
setup_cups_backend_status_foomatic_lab.sh: creates a temporary CUPS IPP queue with an initial safe Foomatic-backed PPD.attack_cups_backend_status_foomatic_chain.py: starts a fake IPP printer, returns the craftedmarker-message, submits two local print jobs, and checks the marker.cleanup_cups_backend_status_foomatic_lab.sh: removes the temporary queue, lab files, and marker files where permitted.Reproduction command pattern:
QUEUE=codex_backend_status_24_<id> PRINTER_HOST=127.0.0.1 PRINTER_PORT=18888 LAB_DIR=/tmp/codex-cups-backend-status-24-<id> sh poc/setup_cups_backend_status_foomatic_lab.sh
python3 poc/attack_cups_backend_status_foomatic_chain.py --listen-host 127.0.0.1 --listen-port 18888 --queue codex_backend_status_24_<id> --input /tmp/codex-cups-backend-status-24-<id>/input.ps --marker /tmp/codex-cups-backend-status-24-<id>-marker --timeout 24Expected successful output:
marker exists: /tmp/codex-cups-backend-status-24-<id>-marker
uid=7(lp) gid=7(lp) groups=7(lp)I also confirmed that the local queue PPD was modified:
*FoomaticRIPCommandLine: cat;/usr/bin/id>/tmp/codex-cups-backend-status-24-<id>-marker;/bin/chmod${IFS}0644${IFS}/tmp/codex-cups-backend-status-24-<id>-marker;#The important security boundary is that the marker is written as user
lp, showing execution through the CUPS filter path rather than a direct local shell command.The PoC only writes harmless marker files under
/tmp; it does not open a reverse shell, download payloads, or persist on the system.Impact proposed by reporter
A malicious or compromised IPP printer may be able to modify the local PPD of a CUPS queue that points to it. If that queue uses a Foomatic-backed PPD/filter profile, the modified PPD can lead to command execution in the CUPS filter context as user
lp.This affects the trust boundary between a remote IPP printer and the local CUPS host. A printer status attribute should not be able to create scheduler control lines or modify local queue PPD command fields.
The impact is conditional:
- The target must have an IPP/IPPS queue pointing to an attacker-controlled or compromised printer.
- The queue must use a PPD/filter profile that invokes
foomatic-rip.- A print job or monitoring/status path must consume the malicious printer status.
- A later print job must trigger the modified Foomatic command line.
This is not demonstrated as default unauthenticated root RCE. It is validated as conditional RCE as user
lp.Impact explanation by OpenPrinting
AV is L (local) since the printer needs to be locally accessible from the system - you can't just send a packet to cupsd from any address.
Integrity is N (none) since nothing in CUPS will cause an integrity issue (foomatic is patched in cups-filters repo) - the PPD is affected by that doesn't affect cupsd or the system's integrity, just the (already bogus) queue which just makes it an availability issue.
Fix
[master 806e1b483] Sanitize IPP attribute strings.
[2.4.x d41cf0616] Sanitize IPP attribute strings.