Skip to content

Commit c3f57e3

Browse files
committed
Protect against deep collection values (Issue #1539)
1 parent 840417a commit c3f57e3

File tree

3 files changed

+121
-73
lines changed

3 files changed

+121
-73
lines changed

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 © 2020-2025 by OpenPrinting.
4+
// Copyright © 2020-2026 by OpenPrinting.
55
// Copyright © 2007-2019 by Apple Inc.
66
// Copyright © 1997-2007 by Easy Software Products, all rights reserved.
77
//
@@ -32,6 +32,13 @@ typedef int mode_t; // Windows doesn't support mode_t type @private@
3232
# include <regex.h>
3333

3434

35+
//
36+
// Constants...
37+
//
38+
39+
# define _CUPS_MAX_OPTION_DEPTH 4 // Maximum depth of nested options/collections
40+
41+
3542
//
3643
// Types...
3744
//
@@ -268,7 +275,7 @@ extern void _cupsBufferRelease(char *b) _CUPS_PRIVATE;
268275
extern http_t *_cupsConnect(void) _CUPS_PRIVATE;
269276
extern char *_cupsCreateDest(const char *name, const char *info, const char *device_id, const char *device_uri, char *uri, size_t urisize) _CUPS_PRIVATE;
270277
extern bool _cupsDirCreate(const char *path, mode_t mode) _CUPS_PRIVATE;
271-
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;
278+
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;
272279
extern int _cupsGet1284Values(const char *device_id, cups_option_t **values) _CUPS_PRIVATE;
273280
extern const char *_cupsGetDestResource(cups_dest_t *dest, unsigned flags, char *resource, size_t resourcesize) _CUPS_PRIVATE;
274281
extern int _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;

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 © 2020-2025 by OpenPrinting.
4+
* Copyright © 2020-2026 by OpenPrinting.
55
* Copyright © 2012-2019 by Apple Inc.
66
*
77
* Licensed under Apache License v2.0. See the file "LICENSE" for more
@@ -3202,7 +3202,7 @@ cups_test_constraints(
32023202

32033203
case IPP_TAG_BEGIN_COLLECTION :
32043204
col = ippNew();
3205-
_cupsEncodeOption(col, IPP_TAG_ZERO, NULL, ippGetName(attr), value);
3205+
_cupsEncodeOption(col, IPP_TAG_ZERO, NULL, ippGetName(attr), value, /*depth*/0);
32063206

32073207
for (i = 0, count = ippGetCount(attr); i < count; i ++)
32083208
{

cups/encode.c

Lines changed: 110 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,7 @@ static const _ipp_option_t ipp_options[] =
376376
*/
377377

378378
static int compare_ipp_options(_ipp_option_t *a, _ipp_option_t *b);
379+
static void encode_options(ipp_t *ipp, int num_options, cups_option_t *options, ipp_tag_t group_tag, int depth);
379380

380381

381382
/*
@@ -388,7 +389,8 @@ _cupsEncodeOption(
388389
ipp_tag_t group_tag, /* I - Group tag */
389390
_ipp_option_t *map, /* I - Option mapping, if any */
390391
const char *name, /* I - Attribute name */
391-
const char *value) /* I - Value */
392+
const char *value, /* I - Value */
393+
int depth) /* I - Depth of values */
392394
{
393395
int i, /* Looping var */
394396
count; /* Number of values */
@@ -659,6 +661,19 @@ _cupsEncodeOption(
659661
* Collection value
660662
*/
661663

664+
if (depth >= _CUPS_MAX_OPTION_DEPTH)
665+
{
666+
/*
667+
* Don't allow "infinite" recursion of collection values...
668+
*/
669+
670+
if (copy)
671+
free(copy);
672+
673+
ippDeleteAttribute(ipp, attr);
674+
return (NULL);
675+
}
676+
662677
num_cols = cupsParseOptions(val, 0, &cols);
663678
if ((collection = ippNew()) == NULL)
664679
{
@@ -672,7 +687,7 @@ _cupsEncodeOption(
672687
}
673688

674689
ippSetCollection(ipp, &attr, i, collection);
675-
cupsEncodeOptions2(collection, num_cols, cols, IPP_TAG_JOB);
690+
encode_options(collection, num_cols, cols, IPP_TAG_JOB, depth + 1);
676691
cupsFreeOptions(num_cols, cols);
677692
ippDelete(collection);
678693
break;
@@ -701,7 +716,7 @@ cupsEncodeOption(ipp_t *ipp, /* I - IPP request/response */
701716
const char *name, /* I - Option name */
702717
const char *value) /* I - Option string value */
703718
{
704-
return (_cupsEncodeOption(ipp, group_tag, _ippFindOption(name), name, value));
719+
return (_cupsEncodeOption(ipp, group_tag, _ippFindOption(name), name, value, /*depth*/0));
705720
}
706721

707722

@@ -747,11 +762,8 @@ cupsEncodeOptions2(
747762
cups_option_t *options, /* I - Options */
748763
ipp_tag_t group_tag) /* I - Group to encode */
749764
{
750-
int i; /* Looping var */
751765
char *val; /* Pointer to option value */
752-
cups_option_t *option; /* Current option */
753766
ipp_op_t op; /* Operation for this request */
754-
const ipp_op_t *ops; /* List of allowed operations */
755767

756768

757769
DEBUG_printf("cupsEncodeOptions2(ipp=%p(%s), num_options=%d, options=%p, group_tag=%x)", (void *)ipp, ipp ? ippOpString(ippGetOperation(ipp)) : "", num_options, (void *)options, group_tag);
@@ -784,15 +796,100 @@ cupsEncodeOptions2(
784796
}
785797

786798
/*
787-
* Then loop through the options...
799+
* Then encode the options...
800+
*/
801+
802+
encode_options(ipp, num_options, options, group_tag, /*depth*/0);
803+
}
804+
805+
806+
#ifdef DEBUG
807+
/*
808+
* '_ippCheckOptions()' - Validate that the option array is sorted properly.
809+
*/
810+
811+
const char * /* O - First out-of-order option or NULL */
812+
_ippCheckOptions(void)
813+
{
814+
int i; /* Looping var */
815+
816+
817+
for (i = 0; i < (int)(sizeof(ipp_options) / sizeof(ipp_options[0]) - 1); i ++)
818+
if (strcmp(ipp_options[i].name, ipp_options[i + 1].name) >= 0)
819+
return (ipp_options[i + 1].name);
820+
821+
return (NULL);
822+
}
823+
#endif /* DEBUG */
824+
825+
826+
/*
827+
* '_ippFindOption()' - Find the attribute information for an option.
828+
*/
829+
830+
_ipp_option_t * /* O - Attribute information */
831+
_ippFindOption(const char *name) /* I - Option/attribute name */
832+
{
833+
_ipp_option_t key; /* Search key */
834+
835+
836+
/*
837+
* Lookup the proper value and group tags for this option...
838+
*/
839+
840+
key.name = name;
841+
842+
return ((_ipp_option_t *)bsearch(&key, ipp_options,
843+
sizeof(ipp_options) / sizeof(ipp_options[0]),
844+
sizeof(ipp_options[0]),
845+
(int (*)(const void *, const void *))
846+
compare_ipp_options));
847+
}
848+
849+
850+
/*
851+
* 'compare_ipp_options()' - Compare two IPP options.
852+
*/
853+
854+
static int /* O - Result of comparison */
855+
compare_ipp_options(_ipp_option_t *a, /* I - First option */
856+
_ipp_option_t *b) /* I - Second option */
857+
{
858+
return (strcmp(a->name, b->name));
859+
}
860+
861+
862+
/*
863+
* 'encode_options()' - Encode options to the specified depth.
864+
*/
865+
866+
void
867+
encode_options(
868+
ipp_t *ipp, /* I - IPP request/response */
869+
int num_options, /* I - Number of options */
870+
cups_option_t *options, /* I - Options */
871+
ipp_tag_t group_tag, /* I - Group to encode */
872+
int depth) /* I - Depth of options/collections */
873+
{
874+
int i; /* Looping var */
875+
cups_option_t *option; /* Current option */
876+
ipp_op_t op = ippGetOperation(ipp);
877+
/* Operation for this request */
878+
const ipp_op_t *ops; /* List of allowed operations */
879+
880+
881+
DEBUG_printf("4encode_options(ipp=%p(%s), num_options=%d, options=%p, group_tag=%x, depth=%d)", (void *)ipp, ipp ? ippOpString(ippGetOperation(ipp)) : "", num_options, (void *)options, group_tag, depth);
882+
883+
/*
884+
* Loop through the options...
788885
*/
789886

790887
for (i = num_options, option = options; i > 0; i --, option ++)
791888
{
792889
_ipp_option_t *match; /* Matching attribute */
793890

794891
/*
795-
* Skip document format options that are handled above...
892+
* Skip document format options that are handled in cupsEncodeOptions2...
796893
*/
797894

798895
if (!_cups_strcasecmp(option->name, "raw") || !_cups_strcasecmp(option->name, "document-format") || !option->name[0])
@@ -819,7 +916,7 @@ cupsEncodeOptions2(
819916
ops = ipp_set_printer;
820917
else
821918
{
822-
DEBUG_printf("2cupsEncodeOptions2: Skipping \"%s\".", option->name);
919+
DEBUG_printf("5encode_options: Skipping \"%s\".", option->name);
823920
continue;
824921
}
825922
}
@@ -833,13 +930,13 @@ cupsEncodeOptions2(
833930
{
834931
if (group_tag != IPP_TAG_JOB && group_tag != IPP_TAG_DOCUMENT)
835932
{
836-
DEBUG_printf("2cupsEncodeOptions2: Skipping \"%s\".", option->name);
933+
DEBUG_printf("5encode_options: Skipping \"%s\".", option->name);
837934
continue;
838935
}
839936
}
840937
else if (group_tag != IPP_TAG_PRINTER)
841938
{
842-
DEBUG_printf("2cupsEncodeOptions2: Skipping \"%s\".", option->name);
939+
DEBUG_printf("5encode_options: Skipping \"%s\".", option->name);
843940
continue;
844941
}
845942

@@ -863,66 +960,10 @@ cupsEncodeOptions2(
863960

864961
if (*ops == IPP_OP_CUPS_NONE && op != IPP_OP_CUPS_NONE)
865962
{
866-
DEBUG_printf("2cupsEncodeOptions2: Skipping \"%s\".", option->name);
963+
DEBUG_printf("5encode_options: Skipping \"%s\".", option->name);
867964
continue;
868965
}
869966

870-
_cupsEncodeOption(ipp, group_tag, match, option->name, option->value);
967+
_cupsEncodeOption(ipp, group_tag, match, option->name, option->value, depth);
871968
}
872969
}
873-
874-
875-
#ifdef DEBUG
876-
/*
877-
* '_ippCheckOptions()' - Validate that the option array is sorted properly.
878-
*/
879-
880-
const char * /* O - First out-of-order option or NULL */
881-
_ippCheckOptions(void)
882-
{
883-
int i; /* Looping var */
884-
885-
886-
for (i = 0; i < (int)(sizeof(ipp_options) / sizeof(ipp_options[0]) - 1); i ++)
887-
if (strcmp(ipp_options[i].name, ipp_options[i + 1].name) >= 0)
888-
return (ipp_options[i + 1].name);
889-
890-
return (NULL);
891-
}
892-
#endif /* DEBUG */
893-
894-
895-
/*
896-
* '_ippFindOption()' - Find the attribute information for an option.
897-
*/
898-
899-
_ipp_option_t * /* O - Attribute information */
900-
_ippFindOption(const char *name) /* I - Option/attribute name */
901-
{
902-
_ipp_option_t key; /* Search key */
903-
904-
905-
/*
906-
* Lookup the proper value and group tags for this option...
907-
*/
908-
909-
key.name = name;
910-
911-
return ((_ipp_option_t *)bsearch(&key, ipp_options,
912-
sizeof(ipp_options) / sizeof(ipp_options[0]),
913-
sizeof(ipp_options[0]),
914-
(int (*)(const void *, const void *))
915-
compare_ipp_options));
916-
}
917-
918-
919-
/*
920-
* 'compare_ipp_options()' - Compare two IPP options.
921-
*/
922-
923-
static int /* O - Result of comparison */
924-
compare_ipp_options(_ipp_option_t *a, /* I - First option */
925-
_ipp_option_t *b) /* I - Second option */
926-
{
927-
return (strcmp(a->name, b->name));
928-
}

0 commit comments

Comments
 (0)