Back Heyitsas.Im Spooler Alert: Remote Unauth'd RCE-to-root Chain in CUPS - Hey, it's Asim
TLDR: my self-orchestrating team of vulnerability hunting agents discovered two issues in CUPS, CVE-2026-34980 and CVE-2026-34990 , chainable into unauthenticated remote attacker -> unprivileged RCE -> root file (over)write . See below for the prerequisites, details, and mitigation options.
CUPS is the standard way to do printing on Linux and other Unix(-like) systems. It’s been on my mind as a research target ever since doing incident response to Simone Margaritelli’s 2024 unauth’d RCE finding , where he chained several CUPS vulnerabilities into an unauth’d RCE as lp , the default CUPS service user.
CUPS is complex – there are:
Most of the filters run as lp , but the scheduler normally runs as root – and so do some of the backends. This makes for a rich attack surface.
At a high level, in the first vulnerability, the attacker:
And in the second vulnerability, the attacker:
* any unprivileged local user that can bind on some TCP port and reach the local CUPS listener.
The unauth’d RCE as lp ( CVE-2026-34980 ) requires the CUPS server to be reachable over the network and expose a shared PostScript queue (these are legacy, but still used). This would be a deliberate config choice – realistic for, say, networked printing servers in your corporate environment, but not for your desktop (unless you for some reason set it up to be a remote printing server).
The LPE to root file (over)write ( CVE-2026-34990 ), on the other hand, works on the stock CUPS config.
For both issues, the harm can be limited by a security module that confines CUPS (e.g., SELinux, AppArmor, etc.). So, if you run CUPS under a sane security policy (default on some distributions), the impact of both vulnerabilities is much less severe – e.g., no rootful file writes outside the paths CUPS is constrained to touch.
As of 4/5/2026, there are public commits with fixes to both issues but no fixed release (latest being 2.4.16 ). So, your best mitigations are:
The advisories ( CVE-2026-34980 , CVE-2026-34990 ) and the PoCs in them go into full detail. Below is a summary of the particularly interesting pieces.
Under the default policy , CUPS will accept anonymous Print-Job requests, and it only blocks remote printing when the queue is not shared:
This gives us the ability to target all the rich escaping/parsing logic on a shared queue without any auth layer by default.
When CUPS serializes job attributes for filters, it escapes newlines by prefixing them with a backslash . Later, when it parses that option string back, it strips the backslash back out. So, an embedded newline survives the round trip.
This matters when using a PostScript queue. pstops logs an invalid page-border value if it encounters one, and the helper it uses prefixes only the first line . So if the attacker smuggles a newline into the value, the second line can begin with PPD: (think HTTP PPD request smuggling):
And since CUPS treats PPD: as a trusted control record, it reparses the remainder into queue options , which get added to the queue’s PPD :
At this point, the attacker is able to modify queue configuration. The practical payload is to inject a malicious cupsFilter2 entry into the PPD, then send a second raw job so CUPS will launch an attacker-chosen existing binary as a filter (the PoC in the report uses vim).
This is the LPE to file (over)write as root. Any low-privilege account ( lp used just to demonstrate the chain) that can talk to CUPS on localhost can issue a CUPS-Create-Local-Printer command; it is not an admin-authenticated operation in the default policy when coming from localhost. So, the attacker can stand up a fake printer on localhost: and trigger CUPS to set it up.
During the printer setup/validation, CUPS will authenticate to the target printer using its Local auth scheme. An attacker listening on localhost can then with 401 Unauthorized and WWW-Authenticate: Local trc="y" , mirroring the “try root certificate” auth option that CUPS itself would normally use for privileged localhost auth. CUPS then uses certs/0 (“cert” here is really a “token”) and presents the admin token ( Authorization: Local ... ) to the attacker. Finally, cupsd accepts the replayed Authorization: Local ... token on loopback :
That token is enough to issue /admin/ requests on localhost. Normally, CUPS does have a guardrail here: file: device URIs are supposed to be blocked unless FileDevice is explicitly enabled . However, the temporary printer path stores the URI first and later lets printer-is-shared=true clear the temporary flag without revalidating the URI:
So, the attacker now needs to just create a second , temporary local queue (pointed at file:/// ) and immediately use the stolen token to set printer-is-shared=true so that the queue is persisted. This is racy as background setup/validation can remove the printer. However, once the race is won (single-digit attempts have sufficed in my testing), printing to it is just a root file (over)write on whatever file:///... path the attacker chose:
At this point, the target can be /etc/sudoers.d/pwn . Note: open(..., 0600) sets 0600 on new files, but existing files’ bits – e.g., exec – would be preserved.
I will eventually cover the self-orchestrating setup – with its strengths and limitations – in a dedicated post but it’s worth calling out immediately how this chain showcases LLMs’ prowess in vulnerability research.
You may not vibe-discover the whole chain with a single “find me a remote RCE to root, make no mistakes” prompt. But tasking them with a) a for a remote code exec as anything and b) anything -> a useful root primitive allows the agents to greatly narrow the space and not burn as many tokens.
At that point, their relentlessness is unleashed in very tight directions, finding all the low-hanging fruit much quicker – something they are now very good at doing.
The full story
This article is one source in a clustered incident — the cluster page carries the summary, timeline and every other outlet covering it.
