Skip to content

Possible memory leak and new/free mismatch when releasing the cups_dest_t from getPrinter #41

Description

@OvOhao

Possible memory leak and new/free mismatch when releasing the cups_dest_t from getPrinter

I found a possible per-operation heap leak (and a new/free mismatch) in the printer-info
callbacks. getPrinter returns a deep copy of a cups_dest_t: the struct itself is allocated with
new, and its name/instance are strdup'd while its options array is allocated with
new[] and each option's name/value is strdup'd. Every N-API callback that consumes this
copy releases it with a bare free(dest), which (1) only frees the top-level struct — leaking the
strdup'd name, instance, the new[] options array, and all 2 * num_options strdup'd
option strings — and (2) frees memory obtained from new with free, which is undefined behaviour.

File: lib/unix/methods.cpp (allocation) and lib/unix/API.cpp (release)

Function: methods::getPrinter / methods::copyDest (alloc) and API::printerInfo /
API::printerOptions / API::defaultPrinterName / API::print (release)

// methods.cpp — getPrinter builds a deep copy
cups_dest_t* result = new cups_dest_t;          // struct via new
...
// copyDest fills it:
dest->instance = strdup(source->instance);      // strdup
dest->name     = strdup(source->name);          // strdup
dest->options  = new cups_option_t[source->num_options];   // new[]
// copyOptions: dest[i].name = strdup(...); dest[i].value = strdup(...);
// API.cpp — every consumer releases with a bare free()
cups_dest_t* dest = getPrinter(*printer);
...
cupsFreeJobs(num_jobs, printerJobs);
free(dest);                                      // frees only the struct; UB (new vs free)
  1. getPrinter (methods.cpp:25) allocates the destination struct with new cups_dest_t and calls
    copyDest, which strdups instance and name, allocates dest->options with
    new cups_option_t[num_options], and copyOptions strdups each option name and value.
  2. Each callback in API.cpp (printerInfo line 80, printerOptions line 142,
    defaultPrinterName line 155, print line 179) releases the returned pointer with a single
    free(dest).
  3. free(dest) releases only the top-level block. It never frees dest->name, dest->instance,
    the dest->options array, or the num_options pairs of strdup'd strings — so all of those
    leak on every call. Because these callbacks are the normal per-request API surface, the leak
    accumulates with each printer query.
  4. Additionally, dest was created with new (not malloc), so passing it to free is a
    new/free mismatch (CWE-762) — undefined behaviour on top of the leak.

JS trigger (if applicable):

const printer = require('node-native-printer');
for (let i = 0; i < 100000; i++) printer.printerInfo('SomePrinter'); // leaks strings + options each call

Suggested fix: give the deep copy a matching destructor/free helper that mirrors copyDest
free/delete[] the strdup'd name/instance, delete[] each option's strdup'd
name/value and the options array, then delete dest (matching new). Replace every
free(dest) call with that helper. (CUPS also offers no public free for a hand-rolled copy, so a
dedicated helper is required.)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions