Skip to content

Commit b2200a8

Browse files
committed
Protect against deep collection values.
1 parent af26b78 commit b2200a8

4 files changed

Lines changed: 122 additions & 83 deletions

File tree

CHANGES.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
Changes in libcups
22
==================
33

4+
v3.0.2 - YYYY-MM-DD
5+
-------------------
6+
7+
- Fixed a recursion issue with encoding of nested collections.
8+
9+
410
v3.0.1 - 2026-04-09
511
-------------------
612

cups/cups-private.h

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//
22
// Private definitions for CUPS.
33
//
4-
// Copyright © 2021-2025 by OpenPrinting.
4+
// Copyright © 2021-2026 by OpenPrinting.
55
// Copyright © 2007-2019 by Apple Inc.
66
// Copyright © 1997-2007 by Easy Software Products, all rights reserved.
77
//
@@ -40,6 +40,13 @@ typedef int mode_t; // Windows doesn't support mode_t type @private@
4040
# define _(x) x
4141

4242

43+
//
44+
// Constants...
45+
//
46+
47+
# define _CUPS_MAX_OPTION_DEPTH 4 // Maximum depth of nested options/collections
48+
49+
4350
//
4451
// Types...
4552
//
@@ -241,7 +248,7 @@ extern void _cupsBufferRelease(char *b) _CUPS_PRIVATE;
241248
extern http_t *_cupsConnect(void) _CUPS_PRIVATE;
242249
extern char *_cupsCreateDest(const char *name, const char *info, const char *device_id, const char *device_uri, char *uri, size_t urisize) _CUPS_PRIVATE;
243250
extern bool _cupsDirCreate(const char *path, mode_t mode) _CUPS_PRIVATE;
244-
extern ipp_attribute_t *_cupsEncodeOption(ipp_t *ipp, ipp_tag_t group_tag, _ipp_option_t *map, const char *name, const char *value) _CUPS_PRIVATE;
251+
extern ipp_attribute_t *_cupsEncodeOption(ipp_t *ipp, ipp_tag_t group_tag, _ipp_option_t *map, const char *name, const char *value, int depth) _CUPS_PRIVATE;
245252
extern const char *_cupsGetDestResource(cups_dest_t *dest, unsigned flags, char *resource, size_t resourcesize) _CUPS_PRIVATE;
246253
extern size_t _cupsGetDests(http_t *http, ipp_op_t op, const char *name, cups_dest_t **dests, cups_ptype_t type, cups_ptype_t mask) _CUPS_PRIVATE;
247254
extern const char *_cupsGetPassword(const char *prompt) _CUPS_PRIVATE;

cups/dest-options.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//
22
// Destination option/media support for CUPS.
33
//
4-
// Copyright © 2021-2024 by OpenPrinting.
4+
// Copyright © 2021-2026 by OpenPrinting.
55
// Copyright © 2012-2019 by Apple Inc.
66
//
77
// Licensed under Apache License v2.0. See the file "LICENSE" for more
@@ -2338,7 +2338,7 @@ cups_test_constraints(
23382338

23392339
case IPP_TAG_BEGIN_COLLECTION :
23402340
col = ippNew();
2341-
_cupsEncodeOption(col, IPP_TAG_ZERO, NULL, ippGetName(attr), value);
2341+
_cupsEncodeOption(col, IPP_TAG_ZERO, NULL, ippGetName(attr), value, /*depth*/0);
23422342

23432343
for (i = 0, count = ippGetCount(attr); i < count; i ++)
23442344
{

cups/encode.c

Lines changed: 105 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//
22
// Option encoding routines for CUPS.
33
//
4-
// Copyright © 2021-2025 by OpenPrinting.
4+
// Copyright © 2021-2026 by OpenPrinting.
55
// Copyright © 2007-2019 by Apple Inc.
66
// Copyright © 1997-2007 by Easy Software Products.
77
//
@@ -278,6 +278,7 @@ static const _ipp_option_t ipp_options[] =
278278
//
279279

280280
static int compare_ipp_options(_ipp_option_t *a, _ipp_option_t *b);
281+
static void encode_options(ipp_t *ipp, size_t num_options, cups_option_t *options, ipp_tag_t group_tag, int depth);
281282

282283

283284
//
@@ -290,7 +291,8 @@ _cupsEncodeOption(
290291
ipp_tag_t group_tag, // I - Group tag
291292
_ipp_option_t *map, // I - Option mapping, if any
292293
const char *name, // I - Attribute name
293-
const char *value) // I - Value
294+
const char *value, // I - Value
295+
int depth) // I - Depth of values
294296
{
295297
size_t i, // Looping var
296298
count; // Number of values
@@ -498,6 +500,16 @@ _cupsEncodeOption(
498500

499501
case IPP_TAG_BEGIN_COLLECTION :
500502
// Collection value
503+
if (depth >= _CUPS_MAX_OPTION_DEPTH)
504+
{
505+
// Don't allow "infinite" recursion of collection values...
506+
if (copy)
507+
free(copy);
508+
509+
ippDeleteAttribute(ipp, attr);
510+
return (NULL);
511+
}
512+
501513
num_cols = cupsParseOptions(val, /*end*/NULL, 0, &cols);
502514
if ((collection = ippNew()) == NULL)
503515
{
@@ -510,8 +522,9 @@ _cupsEncodeOption(
510522
}
511523

512524
ippSetCollection(ipp, &attr, i, collection);
513-
cupsEncodeOptions(collection, num_cols, cols, IPP_TAG_JOB);
525+
encode_options(collection, num_cols, cols, IPP_TAG_JOB, depth + 1);
514526
cupsFreeOptions(num_cols, cols);
527+
ippDelete(collection);
515528
break;
516529

517530
default :
@@ -538,7 +551,7 @@ cupsEncodeOption(ipp_t *ipp, // I - IPP request/response
538551
const char *name, // I - Option name
539552
const char *value) // I - Option string value
540553
{
541-
return (_cupsEncodeOption(ipp, group_tag, _ippFindOption(name), name, value));
554+
return (_cupsEncodeOption(ipp, group_tag, _ippFindOption(name), name, value, /*depth*/0));
542555
}
543556

544557

@@ -556,11 +569,8 @@ cupsEncodeOptions(
556569
cups_option_t *options, // I - Options
557570
ipp_tag_t group_tag) // I - Group to encode
558571
{
559-
size_t i; // Looping var
560572
char *val; // Pointer to option value
561-
cups_option_t *option; // Current option
562573
ipp_op_t op; // Operation for this request
563-
const ipp_op_t *ops; // List of allowed operations
564574

565575

566576
DEBUG_printf("cupsEncodeOptions(ipp=%p(%s), num_options=%u, options=%p, group_tag=%x)", (void *)ipp, ipp ? ippOpString(ippGetOperation(ipp)) : "", (unsigned)num_options, (void *)options, group_tag);
@@ -583,12 +593,91 @@ cupsEncodeOptions(
583593
ippAddString(ipp, IPP_TAG_OPERATION, IPP_TAG_MIMETYPE, "document-format", NULL, "application/octet-stream");
584594
}
585595

586-
// Then loop through the options...
596+
// Then encode the options...
597+
encode_options(ipp, num_options, options, group_tag, /*depth*/0);
598+
}
599+
600+
601+
#ifdef DEBUG
602+
//
603+
// '_ippCheckOptions()' - Validate that the option array is sorted properly.
604+
//
605+
606+
const char * // O - First out-of-order option or NULL
607+
_ippCheckOptions(void)
608+
{
609+
int i; // Looping var
610+
611+
612+
for (i = 0; i < (int)(sizeof(ipp_options) / sizeof(ipp_options[0]) - 1); i ++)
613+
if (strcmp(ipp_options[i].name, ipp_options[i + 1].name) >= 0)
614+
return (ipp_options[i + 1].name);
615+
616+
return (NULL);
617+
}
618+
#endif // DEBUG
619+
620+
621+
//
622+
// '_ippFindOption()' - Find the attribute information for an option.
623+
//
624+
625+
_ipp_option_t * // O - Attribute information
626+
_ippFindOption(const char *name) // I - Option/attribute name
627+
{
628+
_ipp_option_t key; // Search key
629+
630+
631+
// Lookup the proper value and group tags for this option...
632+
key.name = name;
633+
634+
return ((_ipp_option_t *)bsearch(&key, ipp_options,
635+
sizeof(ipp_options) / sizeof(ipp_options[0]),
636+
sizeof(ipp_options[0]),
637+
(int (*)(const void *, const void *))
638+
compare_ipp_options));
639+
}
640+
641+
642+
//
643+
// 'compare_ipp_options()' - Compare two IPP options.
644+
//
645+
646+
static int // O - Result of comparison
647+
compare_ipp_options(_ipp_option_t *a, // I - First option
648+
_ipp_option_t *b) // I - Second option
649+
{
650+
return (strcmp(a->name, b->name));
651+
}
652+
653+
654+
/*
655+
* 'encode_options()' - Encode options to the specified depth.
656+
*/
657+
658+
void
659+
encode_options(
660+
ipp_t *ipp, /* I - IPP request/response */
661+
size_t num_options, /* I - Number of options */
662+
cups_option_t *options, /* I - Options */
663+
ipp_tag_t group_tag, /* I - Group to encode */
664+
int depth) /* I - Depth of options/collections */
665+
{
666+
size_t i; /* Looping var */
667+
cups_option_t *option; /* Current option */
668+
ipp_op_t op = ippGetOperation(ipp);
669+
/* Operation for this request */
670+
const ipp_op_t *ops; /* List of allowed operations */
671+
672+
673+
DEBUG_printf("4encode_options(ipp=%p(%s), num_options=%u, options=%p, group_tag=%x, depth=%d)", (void *)ipp, ipp ? ippOpString(ippGetOperation(ipp)) : "", (unsigned)num_options, (void *)options, group_tag, depth);
674+
675+
// Loop through the options...
587676
for (i = num_options, option = options; i > 0; i --, option ++)
588677
{
589-
_ipp_option_t *match; // Matching attribute
678+
_ipp_option_t *match; /* Matching attribute */
590679

591-
// Skip document format options that are handled above...
680+
// Skip document format options that are handled in cupsEncodeOptions2...
592681
if (!_cups_strcasecmp(option->name, "raw") || !_cups_strcasecmp(option->name, "document-format") || !option->name[0])
593682
continue;
594683

@@ -599,48 +688,38 @@ cupsEncodeOptions(
599688
continue;
600689

601690
if (match->operations)
602-
{
603691
ops = match->operations;
604-
}
605692
else if (group_tag == IPP_TAG_JOB)
606-
{
607693
ops = ipp_job_creation;
608-
}
609694
else if (group_tag == IPP_TAG_DOCUMENT)
610-
{
611695
ops = ipp_doc_creation;
612-
}
613696
else if (group_tag == IPP_TAG_SUBSCRIPTION)
614-
{
615697
ops = ipp_sub_creation;
616-
}
617698
else if (group_tag == IPP_TAG_PRINTER)
618-
{
619699
ops = ipp_set_printer;
620-
}
621700
else
622701
{
623-
DEBUG_printf("2cupsEncodeOptions: Skipping \"%s\".", option->name);
702+
DEBUG_printf("5encode_options: Skipping \"%s\".", option->name);
624703
continue;
625704
}
626705
}
627706
else
628707
{
629-
int namelen; // Length of name
708+
int namelen; /* Length of name */
630709

631710
namelen = (int)strlen(option->name);
632711

633712
if (namelen < 10 || (strcmp(option->name + namelen - 8, "-default") && strcmp(option->name + namelen - 10, "-supported")))
634713
{
635714
if (group_tag != IPP_TAG_JOB && group_tag != IPP_TAG_DOCUMENT)
636715
{
637-
DEBUG_printf("2cupsEncodeOptions: Skipping \"%s\".", option->name);
716+
DEBUG_printf("5encode_options: Skipping \"%s\".", option->name);
638717
continue;
639718
}
640719
}
641720
else if (group_tag != IPP_TAG_PRINTER)
642721
{
643-
DEBUG_printf("2cupsEncodeOptions: Skipping \"%s\".", option->name);
722+
DEBUG_printf("5encode_options: Skipping \"%s\".", option->name);
644723
continue;
645724
}
646725

@@ -663,63 +742,10 @@ cupsEncodeOptions(
663742

664743
if (*ops == IPP_OP_CUPS_NONE && op != IPP_OP_CUPS_NONE)
665744
{
666-
DEBUG_printf("2cupsEncodeOptions: Skipping \"%s\".", option->name);
745+
DEBUG_printf("5encode_options: Skipping \"%s\".", option->name);
667746
continue;
668747
}
669748

670-
_cupsEncodeOption(ipp, group_tag, match, option->name, option->value);
749+
_cupsEncodeOption(ipp, group_tag, match, option->name, option->value, depth);
671750
}
672751
}
673-
674-
675-
#ifdef DEBUG
676-
//
677-
// '_ippCheckOptions()' - Validate that the option array is sorted properly.
678-
//
679-
680-
const char * // O - First out-of-order option or NULL
681-
_ippCheckOptions(void)
682-
{
683-
int i; // Looping var
684-
685-
686-
for (i = 0; i < (int)(sizeof(ipp_options) / sizeof(ipp_options[0]) - 1); i ++)
687-
if (strcmp(ipp_options[i].name, ipp_options[i + 1].name) >= 0)
688-
return (ipp_options[i + 1].name);
689-
690-
return (NULL);
691-
}
692-
#endif // DEBUG
693-
694-
695-
//
696-
// '_ippFindOption()' - Find the attribute information for an option.
697-
//
698-
699-
_ipp_option_t * // O - Attribute information
700-
_ippFindOption(const char *name) // I - Option/attribute name
701-
{
702-
_ipp_option_t key; // Search key
703-
704-
705-
// Lookup the proper value and group tags for this option...
706-
key.name = name;
707-
708-
return ((_ipp_option_t *)bsearch(&key, ipp_options,
709-
sizeof(ipp_options) / sizeof(ipp_options[0]),
710-
sizeof(ipp_options[0]),
711-
(int (*)(const void *, const void *))
712-
compare_ipp_options));
713-
}
714-
715-
716-
//
717-
// 'compare_ipp_options()' - Compare two IPP options.
718-
//
719-
720-
static int // O - Result of comparison
721-
compare_ipp_options(_ipp_option_t *a, // I - First option
722-
_ipp_option_t *b) // I - Second option
723-
{
724-
return (strcmp(a->name, b->name));
725-
}

0 commit comments

Comments
 (0)