Skip to content

Commit e9ddf7b

Browse files
committed
Protect against deep collection values (Issue #1539)
1 parent c2ac810 commit e9ddf7b

4 files changed

Lines changed: 122 additions & 73 deletions

File tree

CHANGES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ Changes in CUPS v2.4.17 (YYYY-MM-DD)
3838
- Fixed a crash bug in the rastertoepson filter.
3939
- Fixed a bug in cgiCheckVariables.
4040
- Fixed a debug printf bug on Windows (Issue #1529)
41+
- Fixed a recursion issue with encoding of nested collections (Issue #1539)
4142

4243

4344
Changes in CUPS v2.4.16 (2025-12-04)

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
*
@@ -39,6 +39,13 @@ extern "C" {
3939
# endif /* __cplusplus */
4040

4141

42+
/*
43+
* Constants...
44+
*/
45+
46+
# define _CUPS_MAX_OPTION_DEPTH 4 /* Maximum depth of nested options/collections */
47+
48+
4249
/*
4350
* Types...
4451
*/
@@ -270,7 +277,7 @@ extern void _cupsBufferRelease(char *b) _CUPS_PRIVATE;
270277

271278
extern http_t *_cupsConnect(void) _CUPS_PRIVATE;
272279
extern char *_cupsCreateDest(const char *name, const char *info, const char *device_id, const char *device_uri, char *uri, size_t urisize) _CUPS_PRIVATE;
273-
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;
280+
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;
274281
extern int _cupsGet1284Values(const char *device_id, cups_option_t **values) _CUPS_PRIVATE;
275282
extern double _cupsGetClock(void) _CUPS_PRIVATE;
276283
extern const char *_cupsGetDestResource(cups_dest_t *dest, unsigned flags, char *resource, size_t resourcesize) _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-2024 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
@@ -2745,7 +2745,7 @@ cups_test_constraints(
27452745

27462746
case IPP_TAG_BEGIN_COLLECTION :
27472747
col = ippNew();
2748-
_cupsEncodeOption(col, IPP_TAG_ZERO, NULL, ippGetName(attr), value);
2748+
_cupsEncodeOption(col, IPP_TAG_ZERO, NULL, ippGetName(attr), value, /*depth*/0);
27492749

27502750
for (i = 0, count = ippGetCount(attr); i < count; i ++)
27512751
{

cups/encode.c

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

380380
static int compare_ipp_options(_ipp_option_t *a, _ipp_option_t *b);
381+
static void encode_options(ipp_t *ipp, int num_options, cups_option_t *options, ipp_tag_t group_tag, int depth);
381382

382383

383384
/*
@@ -390,7 +391,8 @@ _cupsEncodeOption(
390391
ipp_tag_t group_tag, /* I - Group tag */
391392
_ipp_option_t *map, /* I - Option mapping, if any */
392393
const char *name, /* I - Attribute name */
393-
const char *value) /* I - Value */
394+
const char *value, /* I - Value */
395+
int depth) /* I - Depth of values */
394396
{
395397
int i, /* Looping var */
396398
count; /* Number of values */
@@ -643,6 +645,19 @@ _cupsEncodeOption(
643645
* Collection value
644646
*/
645647

648+
if (depth >= _CUPS_MAX_OPTION_DEPTH)
649+
{
650+
/*
651+
* Don't allow "infinite" recursion of collection values...
652+
*/
653+
654+
if (copy)
655+
free(copy);
656+
657+
ippDeleteAttribute(ipp, attr);
658+
return (NULL);
659+
}
660+
646661
num_cols = cupsParseOptions(val, 0, &cols);
647662
if ((collection = ippNew()) == NULL)
648663
{
@@ -656,7 +671,7 @@ _cupsEncodeOption(
656671
}
657672

658673
ippSetCollection(ipp, &attr, i, collection);
659-
cupsEncodeOptions2(collection, num_cols, cols, IPP_TAG_JOB);
674+
encode_options(collection, num_cols, cols, IPP_TAG_JOB, depth + 1);
660675
cupsFreeOptions(num_cols, cols);
661676
ippDelete(collection);
662677
break;
@@ -686,7 +701,7 @@ cupsEncodeOption(ipp_t *ipp, /* I - IPP request/response */
686701
const char *name, /* I - Option name */
687702
const char *value) /* I - Option string value */
688703
{
689-
return (_cupsEncodeOption(ipp, group_tag, _ippFindOption(name), name, value));
704+
return (_cupsEncodeOption(ipp, group_tag, _ippFindOption(name), name, value, /*depth*/0));
690705
}
691706

692707

@@ -732,11 +747,8 @@ cupsEncodeOptions2(
732747
cups_option_t *options, /* I - Options */
733748
ipp_tag_t group_tag) /* I - Group to encode */
734749
{
735-
int i; /* Looping var */
736750
char *val; /* Pointer to option value */
737-
cups_option_t *option; /* Current option */
738751
ipp_op_t op; /* Operation for this request */
739-
const ipp_op_t *ops; /* List of allowed operations */
740752

741753

742754
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));
@@ -769,15 +781,100 @@ cupsEncodeOptions2(
769781
}
770782

771783
/*
772-
* Then loop through the options...
784+
* Then encode the options...
785+
*/
786+
787+
encode_options(ipp, num_options, options, group_tag, /*depth*/0);
788+
}
789+
790+
791+
#ifdef DEBUG
792+
/*
793+
* '_ippCheckOptions()' - Validate that the option array is sorted properly.
794+
*/
795+
796+
const char * /* O - First out-of-order option or NULL */
797+
_ippCheckOptions(void)
798+
{
799+
int i; /* Looping var */
800+
801+
802+
for (i = 0; i < (int)(sizeof(ipp_options) / sizeof(ipp_options[0]) - 1); i ++)
803+
if (strcmp(ipp_options[i].name, ipp_options[i + 1].name) >= 0)
804+
return (ipp_options[i + 1].name);
805+
806+
return (NULL);
807+
}
808+
#endif /* DEBUG */
809+
810+
811+
/*
812+
* '_ippFindOption()' - Find the attribute information for an option.
813+
*/
814+
815+
_ipp_option_t * /* O - Attribute information */
816+
_ippFindOption(const char *name) /* I - Option/attribute name */
817+
{
818+
_ipp_option_t key; /* Search key */
819+
820+
821+
/*
822+
* Lookup the proper value and group tags for this option...
823+
*/
824+
825+
key.name = name;
826+
827+
return ((_ipp_option_t *)bsearch(&key, ipp_options,
828+
sizeof(ipp_options) / sizeof(ipp_options[0]),
829+
sizeof(ipp_options[0]),
830+
(int (*)(const void *, const void *))
831+
compare_ipp_options));
832+
}
833+
834+
835+
/*
836+
* 'compare_ipp_options()' - Compare two IPP options.
837+
*/
838+
839+
static int /* O - Result of comparison */
840+
compare_ipp_options(_ipp_option_t *a, /* I - First option */
841+
_ipp_option_t *b) /* I - Second option */
842+
{
843+
return (strcmp(a->name, b->name));
844+
}
845+
846+
847+
/*
848+
* 'encode_options()' - Encode options to the specified depth.
849+
*/
850+
851+
void
852+
encode_options(
853+
ipp_t *ipp, /* I - IPP request/response */
854+
int num_options, /* I - Number of options */
855+
cups_option_t *options, /* I - Options */
856+
ipp_tag_t group_tag, /* I - Group to encode */
857+
int depth) /* I - Depth of options/collections */
858+
{
859+
int i; /* Looping var */
860+
cups_option_t *option; /* Current option */
861+
ipp_op_t op = ippGetOperation(ipp);
862+
/* Operation for this request */
863+
const ipp_op_t *ops; /* List of allowed operations */
864+
865+
866+
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));
867+
868+
/*
869+
* Loop through the options...
773870
*/
774871

775872
for (i = num_options, option = options; i > 0; i --, option ++)
776873
{
777874
_ipp_option_t *match; /* Matching attribute */
778875

779876
/*
780-
* Skip document format options that are handled above...
877+
* Skip document format options that are handled in cupsEncodeOptions2...
781878
*/
782879

783880
if (!_cups_strcasecmp(option->name, "raw") || !_cups_strcasecmp(option->name, "document-format") || !option->name[0])
@@ -804,7 +901,7 @@ cupsEncodeOptions2(
804901
ops = ipp_set_printer;
805902
else
806903
{
807-
DEBUG_printf(("2cupsEncodeOptions2: Skipping \"%s\".", option->name));
904+
DEBUG_printf(("5encode_options: Skipping \"%s\".", option->name));
808905
continue;
809906
}
810907
}
@@ -818,13 +915,13 @@ cupsEncodeOptions2(
818915
{
819916
if (group_tag != IPP_TAG_JOB && group_tag != IPP_TAG_DOCUMENT)
820917
{
821-
DEBUG_printf(("2cupsEncodeOptions2: Skipping \"%s\".", option->name));
918+
DEBUG_printf(("5encode_options: Skipping \"%s\".", option->name));
822919
continue;
823920
}
824921
}
825922
else if (group_tag != IPP_TAG_PRINTER)
826923
{
827-
DEBUG_printf(("2cupsEncodeOptions2: Skipping \"%s\".", option->name));
924+
DEBUG_printf(("5encode_options: Skipping \"%s\".", option->name));
828925
continue;
829926
}
830927

@@ -848,66 +945,10 @@ cupsEncodeOptions2(
848945

849946
if (*ops == IPP_OP_CUPS_NONE && op != IPP_OP_CUPS_NONE)
850947
{
851-
DEBUG_printf(("2cupsEncodeOptions2: Skipping \"%s\".", option->name));
948+
DEBUG_printf(("5encode_options: Skipping \"%s\".", option->name));
852949
continue;
853950
}
854951

855-
_cupsEncodeOption(ipp, group_tag, match, option->name, option->value);
952+
_cupsEncodeOption(ipp, group_tag, match, option->name, option->value, depth);
856953
}
857954
}
858-
859-
860-
#ifdef DEBUG
861-
/*
862-
* '_ippCheckOptions()' - Validate that the option array is sorted properly.
863-
*/
864-
865-
const char * /* O - First out-of-order option or NULL */
866-
_ippCheckOptions(void)
867-
{
868-
int i; /* Looping var */
869-
870-
871-
for (i = 0; i < (int)(sizeof(ipp_options) / sizeof(ipp_options[0]) - 1); i ++)
872-
if (strcmp(ipp_options[i].name, ipp_options[i + 1].name) >= 0)
873-
return (ipp_options[i + 1].name);
874-
875-
return (NULL);
876-
}
877-
#endif /* DEBUG */
878-
879-
880-
/*
881-
* '_ippFindOption()' - Find the attribute information for an option.
882-
*/
883-
884-
_ipp_option_t * /* O - Attribute information */
885-
_ippFindOption(const char *name) /* I - Option/attribute name */
886-
{
887-
_ipp_option_t key; /* Search key */
888-
889-
890-
/*
891-
* Lookup the proper value and group tags for this option...
892-
*/
893-
894-
key.name = name;
895-
896-
return ((_ipp_option_t *)bsearch(&key, ipp_options,
897-
sizeof(ipp_options) / sizeof(ipp_options[0]),
898-
sizeof(ipp_options[0]),
899-
(int (*)(const void *, const void *))
900-
compare_ipp_options));
901-
}
902-
903-
904-
/*
905-
* 'compare_ipp_options()' - Compare two IPP options.
906-
*/
907-
908-
static int /* O - Result of comparison */
909-
compare_ipp_options(_ipp_option_t *a, /* I - First option */
910-
_ipp_option_t *b) /* I - Second option */
911-
{
912-
return (strcmp(a->name, b->name));
913-
}

0 commit comments

Comments
 (0)