GHSA-jj94-x3qh-ffp9
CVE Information
Summary
When a printer is added or modified with a model PPD, the cupsd scheduler (running as root) calls
copy_model()inscheduler/ipp.c, which writes the driver-generated PPD to a temporary file. The tempfile path is fully predictable (<TempDir>/<con->number>.ppd, wherecon->numberis the sequential client connection id) and is opened withopen(tempfile, O_WRONLY | O_CREAT | O_TRUNC, 0600)— withoutO_EXCLand withoutO_NOFOLLOW.TempDir(/var/spool/cups/tmp) is mode01770 root:lp, i.e. writable by thelpgroup that print filters run under. Anlp-group process can pre-plant a symlink at the predicted path pointing at any root-owned file; root follows the symlink and truncates/overwrites the target, giving an lp → root arbitrary-file-write primitive. The codebase's owncups/tempfile.copens temporaries safely withO_RDWR|O_CREAT|O_EXCL|O_NOFOLLOW;copy_model()is the inconsistent outlier.
Severity
- CVSS: 5.7 (Medium) —
CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:U/C:N/I:H/A:H(PR:L = anlp-group foothold, e.g. a print filter; AC:H = winning the create/symlink race and predictingcon->number; I/A:H = arbitrary root-owned-file truncation/overwrite.)- CWE: CWE-59 (Improper Link Resolution Before File Access — symlink following) / CWE-377 (Insecure Temporary File).
Reviewed by Mike Sweet, OpenPrinting:
Rescored privileges - they are high since you need to be root/print admin to install the malicious filter in the first place.
Affected Version
- Package: https://github.com/OpenPrinting/cups
- Version: 2.5.0 (HEAD
dc9dea0)- File:
scheduler/ipp.c- Function:
copy_model()- Lines: 4356–4357 (tempfile name + unsafe
open)
Vulnerability Details
Root Cause
scheduler/ipp.c:4356-4357(cupsd runs as root):snprintf(buffer, sizeof(buffer), "%s/daemon/cups-driverd", ServerBin); snprintf(tempfile, sizeof(tempfile), "%s/%d.ppd", TempDir, con->number); if ((tempfd = open(tempfile, O_WRONLY | O_CREAT | O_TRUNC, 0600)) < 0) return (-1);Three properties combine into an exploitable TOCTOU:
- No
O_NOFOLLOW— iftempfileis a symlink,open()follows it and operates on the link target.- No
O_EXCL—open()happily reuses a pre-existing path instead of failing, so a planted symlink (or file) is accepted.- Predictable name in a group-writable directory —
con->numberis the monotonic client connection counter (easily predicted/forced), andTempDiris01770 root:lp(scheduler/conf.c), writable by grouplp.The same source tree demonstrates the correct pattern in
cups/tempfile.c:114:fd = open(tmpdir_path, O_RDWR | O_CREAT | O_EXCL | O_NOFOLLOW, 0600);
copy_model()does not use it.Reachability
copy_model()is invoked from the CUPS-Add/Modify-Printer path (admin-gated to add the printer), but the file-write primitive itself is the race: a process in grouplp— the privilege level every CUPS filter already runs at, and a realistic foothold (e.g. via the fax-option injection issue, or any filter bug) — pre-createsTempDir/<N>.ppdas a symlink to a root-owned target before/at the moment cupsd opens it. Root'sO_TRUNCthen truncates the target, and the PPD bytes are written into it.
Proof of Concept
Necessary Scripts
poc.sh— reproduces the exact unsafeopen(O_WRONLY|O_CREAT|O_TRUNC, 0600)flags fromipp.c:4357in a small C tool, plants a symlink at the predicted tempfile name pointing at a stand-in "root victim" file, and shows the victim overwritten through the symlink. It then runs the same path with the fixed flags (O_EXCL|O_NOFOLLOW) to show they reject the planted symlink.Steps to Reproduce
./poc.shExpected (correct) Output
The privileged
open()should refuse to follow a pre-planted symlink (and/or fail because the path already exists), leaving the victim file untouched — the behavior of theO_EXCL|O_NOFOLLOWvariant.Actual Output (vulnerable)
[*] attacker planted symlink: <spool>/42.ppd -> <victim_root_file> [*] cupsd opens <spool>/42.ppd with O_WRONLY|O_CREAT|O_TRUNC (no O_EXCL/O_NOFOLLOW) [*] victim after : PWNED-BY-cupsd-following-attacker-symlink [CONFIRMED] symlink followed -> arbitrary root-owned file overwritten. [*] same path with the fix (O_EXCL|O_NOFOLLOW, as cups/tempfile.c:114 uses): safe open (rejected as expected): File existsManual Verification
Confirm in source: predictable name + unsafe flags at
ipp.c:4356-4357; the group-writable01770 root:lpTempDirinscheduler/conf.c; and the safe counter-example incups/tempfile.c:114.Output Analysis
Open flags Pre-planted symlink Result O_WRONLY\|O_CREAT\|O_TRUNC(copy_model)followed root truncates/writes the symlink target O_WRONLY\|O_CREAT\|O_TRUNC\|O_EXCL\|O_NOFOLLOW(fix)rejected ( EEXIST)victim untouched
Impact
- Integrity: Arbitrary truncation/overwrite of any root-writable file as the cupsd root process (the PPD content is written into the symlink target). Chained to a config or credential file, this is a clean lp → root escalation.
- Availability: Truncation of critical root-owned files.
- Confidentiality: Not directly (write primitive).
- Attack vector: Local process with
lp-group membership (CUPS filters) able to win the create race on the predictable tempfile name.
Fix
[master f6a11dae0] Open temporary PPD files more securely.
[2.4.x 98adfd855] Open temporary PPD files more securely.
References
scheduler/ipp.c(OpenPrinting CUPS 2.5.0):copy_model(), lines 4356-4357.cups/tempfile.c:114— the safeO_EXCL|O_NOFOLLOWpattern in the same tree.scheduler/conf.c—TempDircreated01770 root:lp.- CWE-59, CWE-377.
Attachments