Skip to content

Out-of-bounds heap read in cupsdSetPrinterAttr marker-types parsing

Moderate
michaelrsweet published GHSA-qfp8-9frx-5j48 Apr 5, 2026

Package

cups

Affected versions

<= 2.4.16

Patched versions

2.4.17

Description

Summary

An out-of-bounds heap read exists in cupsdSetPrinterAttr() in scheduler/printers.c when parsing marker-types attribute values that end with a hyphen character (-). The vulnerable code converts hyphenated marker type strings (e.g., ink-cartridge) into camelCase (e.g., inkCartridge) for the printer-supply IPP attribute, but fails to check for the null terminator after advancing past a trailing hyphen. This causes the loop to read one or more bytes beyond the end of the heap-allocated string.

Details

The vulnerability is in cupsdSetPrinterAttr() at approximately line 2199 of scheduler/printers.c. The function processes marker-types values to build printer-supply attribute strings. It converts hyphenated keywords like ink-cartridge into camelCase (inkCartridge) using the following loop:

for (psptr = pstype; *type && psptr < (pstype + sizeof(pstype) - 1); type ++)
    if (*type == '-')
    {
        type ++;                                    // (A) advance past '-'
        *psptr++ = (char)toupper(*type & 255);      // (B) capitalize next char
    }
    else
        *psptr++ = *type;

When a marker-types value ends with - (e.g., ink-):

  1. The outer for loop iteration reaches the - character and enters the if branch.
  2. At (A), type++ advances past the - to the null terminator \0.
  3. At (B), toupper('\0' & 255) writes \0 into pstype (harmless by itself).
  4. The outer loop's type++ in the for statement then increments type past the null terminator.
  5. On the next iteration, *type in the loop condition reads from heap memory beyond the string's allocation, constituting an out-of-bounds read.

The loop continues reading and copying heap bytes into pstype until it encounters a zero byte or fills the 63-byte buffer.

The type pointer originates from ippGetString(types, i, NULL), which returns a pointer to a string interned via _cupsStrAlloc(). That function allocates with calloc(1, sizeof(_cups_sp_item_t) + strlen(s)). Because the _cups_sp_item_t struct contains a char str[1] flexible array member, there are typically a few bytes of zero padding between the end of the string and the allocation boundary. This padding masks the bug in most production scenarios -- the OOB read hits zero bytes from calloc and the loop terminates -- but the read is still undefined behavior and constitutes a genuine out-of-bounds access.

The code runs in the cupsd scheduler process, which typically executes as root.

PoC

The following standalone C program reproduces the bug. It extracts the vulnerable loop verbatim from scheduler/printers.c and allocates the marker-types string with malloc(slen+1) (tight allocation, no struct padding) so that AddressSanitizer can detect the OOB read.

/*
 * Compile:
 *   gcc -fsanitize=address -fno-omit-frame-pointer -g -O1 \
 *       -o poc_marker_types_oob poc_marker_types_oob.c
 *
 * Expected: ASAN reports heap-buffer-overflow on READ of size 1.
 */

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void)
{
    const size_t slen = 4;
    char *type_str = (char *)malloc(slen + 1);
    if (!type_str)
        return 1;

    memcpy(type_str, "ink-", slen);
    type_str[slen] = '\0';

    char pstype[64];
    char *psptr;
    const char *type = type_str;

    for (psptr = pstype;
         *type && psptr < (pstype + sizeof(pstype) - 1);
         type++)
    {
        if (*type == '-')
        {
            type++;
            *psptr++ = (char)toupper(*type & 255);
        }
        else
        {
            *psptr++ = *type;
        }
    }
    *psptr = '\0';

    printf("pstype = \"%s\" (len=%zu)\n", pstype, strlen(pstype));

    free(type_str);
    return 0;
}

ASAN output (truncated):

==PID==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x...
READ of size 1 at 0x... thread T0
    #0 main poc_marker_types_oob.c:31
0x... is located 0 bytes after 5-byte region [0x..., 0x...)
allocated by thread T0 here:
    #0 malloc
    #1 main poc_marker_types_oob.c:18

To trigger this via the CUPS backend protocol, a backend program (running as root) sends:

ATTR: marker-types=ink-

The cupsd scheduler parses this in cupsdUpdatePrinterAttr() which calls cupsdSetPrinterAttr(), reaching the vulnerable loop.

Impact

Confidentiality: The OOB read copies heap bytes beyond the string allocation into the pstype buffer, which is then formatted into the printer-supply IPP attribute via snprintf(). This attribute value is subsequently available to authenticated users querying printer attributes, potentially leaking small amounts of heap data. In practice, the _cupsStrAlloc() calloc padding limits the leak to typically zero bytes, but this is an implementation detail that cannot be relied upon for security.

Availability: If heap memory beyond the string contains non-zero bytes (possible with different allocators, or after heap churn), the loop could copy up to 63 bytes of heap data into pstype, potentially producing garbled printer-supply values. This is unlikely to cause a crash but constitutes undefined behavior.

Integrity: No write-based corruption occurs.

Overall severity is Low because exploitation requires local access (a malicious backend or a compromised printer sending crafted IPP attributes), the read is small and typically masked by allocator padding, and the leaked data (if any) is limited to nearby heap contents.

Suggested Fix

Add a null-terminator check immediately after the inner type++ to prevent advancing past the end of the string:

for (psptr = pstype; *type && psptr < (pstype + sizeof(pstype) - 1); type ++)
    if (*type == '-')
    {
        type ++;
        if (!*type)
            break;
        *psptr++ = (char)toupper(*type & 255);
    }
    else
        *psptr++ = *type;

The added if (!*type) break; exits the loop when the string ends with -, preventing the outer loop's type++ from advancing past the null terminator.

Fixes:
master 5460a712f Protect against a driver reporting a supply type with a trailing '-'.

2.4.x ed6ac03 Protect against a driver reporting a supply type with a trailing '-'.

Severity

Moderate

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Local
Attack complexity
Low
Privileges required
None
User interaction
None
Scope
Unchanged
Confidentiality
None
Integrity
None
Availability
Low

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L

CVE ID

No known CVE

Weaknesses

Out-of-bounds Read

The product reads data past the end, or before the beginning, of the intended buffer. Learn more on MITRE.

Credits