Submitted By: Douglas R. Reno Date: 2024-10-09 Initial Package Version: 2.0.0 Upstream Status: Applied (commit d6817477e) Origin: Upstream Description: Fixes CVE-2024-47175 in libppd. This is part of the exploit chain for an unauthenticated (without user interaction) remote code execution chain in CUPS. This patch fixes the vulnerability by preventing PPD generation based on invalid IPP responses via validating the IPP responses. diff -Naurp libppd-2.0.0.orig/ppd/ppd-cache.c libppd-2.0.0/ppd/ppd-cache.c --- libppd-2.0.0.orig/ppd/ppd-cache.c 2024-10-09 14:08:35.791817124 -0500 +++ libppd-2.0.0/ppd/ppd-cache.c 2024-10-09 14:12:53.082328729 -0500 @@ -1,6 +1,7 @@ // // PPD cache implementation for libppd. // +// Copyright © 2024 by OpenPrinting // Copyright © 2010-2019 by Apple Inc. // // Licensed under Apache License v2.0. See the file "LICENSE" for more @@ -3413,7 +3414,7 @@ ppdCacheGetBin( // // Range check input... - + if (!pc || !output_bin) return (NULL); @@ -3914,7 +3915,7 @@ ppdCacheGetPageSize( { // // Check not only the base size (like "A4") but also variants (like - // "A4.Borderless"). We check only the margins and orientation but do + // "A4.Borderless"). We check only the margins and orientation but do // not re-check the size. // @@ -4711,7 +4712,7 @@ ppdPwgPpdizeName(const char *ipp, // I - *end; // End of name buffer - if (!ipp) + if (!ipp || !_ppd_isalnum(*ipp)) { *name = '\0'; return; @@ -4721,13 +4722,19 @@ ppdPwgPpdizeName(const char *ipp, // I - for (ptr = name + 1, end = name + namesize - 1; *ipp && ptr < end;) { - if (*ipp == '-' && _ppd_isalnum(ipp[1])) + if (*ipp == '-' && isalnum(ipp[1])) { ipp ++; *ptr++ = (char)toupper(*ipp++ & 255); } + else if (*ipp == '_' || *ipp == '.' || *ipp == '-' || isalnum(*ipp)) + { + *ptr++ = *ipp++; + } else - *ptr++ = *ipp++; + { + ipp ++; + } } *ptr = '\0'; diff -Naurp libppd-2.0.0.orig/ppd/ppd-generator.c libppd-2.0.0/ppd/ppd-generator.c --- libppd-2.0.0.orig/ppd/ppd-generator.c 2024-10-09 14:08:35.791817124 -0500 +++ libppd-2.0.0/ppd/ppd-generator.c 2024-10-09 15:09:46.241654150 -0500 @@ -1,15 +1,16 @@ // // PWG Raster/Apple Raster/PCLm/PDF/IPP legacy PPD generator for libppd. // -// Copyright 2016-2019 by Till Kamppeter. -// Copyright 2017-2019 by Sahil Arora. -// Copyright 2018-2019 by Deepak Patankar. +// Copyright © 2024 by OpenPrinting +// Copyright © 2016-2019 by Till Kamppeter. +// Copyright © 2017-2019 by Sahil Arora. +// Copyright © 2018-2019 by Deepak Patankar. // // The PPD generator is based on the PPD generator for the CUPS // "lpadmin -m everywhere" functionality in the cups/ppd-cache.c // file. The copyright of this file is: // -// Copyright 2010-2016 by Apple Inc. +// Copyright © 2010-2016 by Apple Inc. // // Licensed under Apache License v2.0. See the file "LICENSE" for more // information. @@ -51,6 +52,8 @@ static int http_connect(http_t **http, const char *url, char *resource, size_t ressize); +static void ppd_put_string(cups_file_t *fp, cups_lang_t *lang, const char *ppd_option, +const char *ppd_choice, const char *pwg_msgid); // @@ -60,7 +63,7 @@ static int http_connect(http_t **http, c // than CUPS 2.2.x. We have also an additional test and development // platform for this code. Taken from cups/ppd-cache.c, // cups/string-private.h, cups/string.c. -// +// // The advantage of PPD generation instead of working with System V // interface scripts is that the print dialogs of the clients do not // need to ask the printer for its options via IPP. So we have access @@ -124,7 +127,7 @@ char ppdgenerator_msg[1024]; // IPP 1.x legacy) // -char * // O - PPD filename or NULL +char * // O - PPD filename or NULL // on error ppdCreatePPDFromIPP(char *buffer, // I - Filename buffer size_t bufsize, // I - Size of filename @@ -175,7 +178,7 @@ ppdCreatePPDFromIPP2(char *buffe cups_array_t *conflicts, // I - Array of // constraints cups_array_t *sizes, // I - Media sizes we've - // added + // added char* default_pagesize, // I - Default page size const char *default_cluster_color, // I - cluster def // color (if cluster's @@ -188,6 +191,7 @@ ppdCreatePPDFromIPP2(char *buffe // message buffer { cups_file_t *fp; // PPD file + cups_lang_t *lang; // Localization language cups_array_t *printer_sizes; // Media sizes we've added cups_size_t *size; // Current media size ipp_attribute_t *attr, // xxx-supported @@ -199,9 +203,10 @@ ppdCreatePPDFromIPP2(char *buffe ipp_t *media_col, // Media collection *media_size; // Media size collection char make[256], // Make and model - *model, // Model name + *mptr, // Pointer into make and model ppdname[PPD_MAX_NAME]; // PPD keyword + const char *model; // Model name int i, j, // Looping vars count = 0, // Number of values bottom, // Largest bottom margin @@ -284,6 +289,68 @@ ppdCreatePPDFromIPP2(char *buffe } // + // Get a sanitized make and model... + // + + if ((attr = ippFindAttribute(supported, "printer-make-and-model", IPP_TAG_TEXT)) != NULL && ippValidateAttribute(attr)) + { + // Sanitize the model name to only contain PPD-safe characters. + strlcpy(make, ippGetString(attr, 0, NULL), sizeof(make)); + + for (mptr = make; *mptr; mptr++) + { + if (*mptr < ' ' || *mptr >= 127 || *mptr == '\"') + { + // Truncate the make and model on the first bad character... + *mptr = '\0'; + break; + } + } + + while (mptr > make) + { + // Strip trailing whitespace... + mptr --; + if (*mptr == ' ') + *mptr = '\0'; + } + + if (!make[0]) + { + // Use a default make and model if nothing remains... + strlcpy(make, "Unknown", sizeof(make)); + } + } + else + { + // Use a default make and model... + strlcpy(make, "Unknown", sizeof(make)); + } + + if (!strncasecmp(make, "Hewlett Packard ", 16) || !strncasecmp(make, "Hewlett-Packard ", 16)) + { + // Normalize HP printer make and model... + model = make + 16; + strlcpy(make, "HP", sizeof(make)); + + if (!strncasecmp(model, "HP ", 3)) + model += 3; + } + else if ((mptr = strchr(make, ' ')) != NULL) + { + // Separate "MAKE MODEL"... + while (*mptr && *mptr == ' ') + *mptr++ = '\0'; + + model = mptr; + } + else + { + // No separate model name... + model = "Printer"; + } + + // // Standard stuff for PPD file... // @@ -311,25 +378,6 @@ ppdCreatePPDFromIPP2(char *buffe } } - if ((attr = ippFindAttribute(supported, "printer-make-and-model", - IPP_TAG_TEXT)) != NULL) - strlcpy(make, ippGetString(attr, 0, NULL), sizeof(make)); - else if (make_model && make_model[0] != '\0') - strlcpy(make, make_model, sizeof(make)); - else - strlcpy(make, "Unknown Printer", sizeof(make)); - - if (!strncasecmp(make, "Hewlett Packard ", 16) || - !strncasecmp(make, "Hewlett-Packard ", 16)) - { - model = make + 16; - strlcpy(make, "HP", sizeof(make)); - } - else if ((model = strchr(make, ' ')) != NULL) - *model++ = '\0'; - else - model = make; - cupsFilePrintf(fp, "*Manufacturer: \"%s\"\n", make); cupsFilePrintf(fp, "*ModelName: \"%s %s\"\n", make, model); cupsFilePrintf(fp, "*Product: \"(%s %s)\"\n", make, model); @@ -425,21 +473,19 @@ ppdCreatePPDFromIPP2(char *buffe } cupsFilePuts(fp, "\"\n"); - if ((attr = ippFindAttribute(supported, "printer-more-info", IPP_TAG_URI)) != - NULL) + if ((attr = ippFindAttribute(supported, "printer-more-info", IPP_TAG_URI)) != NULL && ippValidateAttribute(attr)) cupsFilePrintf(fp, "*APSupplies: \"%s\"\n", ippGetString(attr, 0, NULL)); - if ((attr = ippFindAttribute(supported, "printer-charge-info-uri", - IPP_TAG_URI)) != NULL) - cupsFilePrintf(fp, "*cupsChargeInfoURI: \"%s\"\n", ippGetString(attr, 0, - NULL)); + if ((attr = ippFindAttribute(supported, "printer-charge-info-uri", IPP_TAG_URI)) != NULL && ippValidateAttribute(attr)) + cupsFilePrintf(fp, "*cupsChargeInfoURI: \"%s\"\n", ippGetString(attr, 0, NULL)); // Message catalogs for UI strings + lang = cupsLangDefault(); opt_strings_catalog = cfCatalogOptionArrayNew(); cfCatalogLoad(NULL, NULL, opt_strings_catalog); if ((attr = ippFindAttribute(supported, "printer-strings-uri", - IPP_TAG_URI)) != NULL) + IPP_TAG_URI)) != NULL && ippValidateAttribute(attr)) { printer_opt_strings_catalog = cfCatalogOptionArrayNew(); cfCatalogLoad(ippGetString(attr, 0, NULL), NULL, @@ -492,7 +538,7 @@ ppdCreatePPDFromIPP2(char *buffe response = cupsDoRequest(http, request, resource); if ((attr = ippFindAttribute(response, "printer-strings-uri", - IPP_TAG_URI)) != NULL) + IPP_TAG_URI)) != NULL && ippValidateAttribute(attr)) cupsFilePrintf(fp, "*cupsStringsURI %s: \"%s\"\n", keyword, ippGetString(attr, 0, NULL)); @@ -518,13 +564,11 @@ ppdCreatePPDFromIPP2(char *buffe IPP_TAG_BOOLEAN), 0)) cupsFilePuts(fp, "*cupsJobAccountingUserId: True\n"); - if ((attr = ippFindAttribute(supported, "printer-privacy-policy-uri", - IPP_TAG_URI)) != NULL) - cupsFilePrintf(fp, "*cupsPrivacyURI: \"%s\"\n", - ippGetString(attr, 0, NULL)); + if ((attr = ippFindAttribute(supported, "printer-privacy-policy-uri", IPP_TAG_URI)) != NULL && ippValidateAttribute(attr)) + cupsFilePrintf(fp, "*cupsPrivacyURI: \"%s\"\n", ippGetString(attr, 0, NULL)); if ((attr = ippFindAttribute(supported, "printer-mandatory-job-attributes", - IPP_TAG_KEYWORD)) != NULL) + IPP_TAG_KEYWORD)) != NULL && ippValidateAttribute(attr)) { char prefix = '\"'; // Prefix for string @@ -544,8 +588,7 @@ ppdCreatePPDFromIPP2(char *buffe cupsFilePuts(fp, "\"\n"); } - if ((attr = ippFindAttribute(supported, "printer-requested-job-attributes", - IPP_TAG_KEYWORD)) != NULL) + if ((attr = ippFindAttribute(supported, "printer-requested-job-attributes", IPP_TAG_KEYWORD)) != NULL&& ippValidateAttribute(attr)) { char prefix = '\"'; // Prefix for string @@ -664,7 +707,7 @@ ppdCreatePPDFromIPP2(char *buffe } // - // Fax + // Fax // if (is_fax) @@ -705,21 +748,21 @@ ppdCreatePPDFromIPP2(char *buffe #ifdef CUPS_RASTER_HAVE_APPLERASTER else if (cupsArrayFind(pdl_list, "image/urf")) { - int resStore = 0; // Variable for storing the no. of resolutions in the resolution array + int resStore = 0; // Variable for storing the no. of resolutions in the resolution array int resArray[__INT16_MAX__]; // Creating a resolution array supporting a maximum of 32767 resolutions. int lowdpi = 0, middpi = 0, hidpi = 0; // Lower , middle and higher resolution if ((attr = ippFindAttribute(supported, "urf-supported", IPP_TAG_KEYWORD)) != NULL) { for (int i = 0, count = ippGetCount(attr); i < count; i ++) - { + { const char *rs = ippGetString(attr, i, NULL); // RS values - const char *rsCopy = ippGetString(attr, i, NULL); // RS values(copy) + const char *rsCopy = ippGetString(attr, i, NULL); // RS values(copy) if (strncasecmp(rs, "RS", 2)) // Comparing attributes to have RS in // the beginning to indicate the // resolution feature continue; - int resCount = 0; // Using a count variable which can be reset + int resCount = 0; // Using a count variable which can be reset while (*rsCopy != '\0') // Parsing through the copy pointer to // determine the no. of resolutions { @@ -817,7 +860,7 @@ ppdCreatePPDFromIPP2(char *buffe formatfound = 1; is_apple = 1; } - } + } } } } @@ -909,7 +952,7 @@ ppdCreatePPDFromIPP2(char *buffe if (manual_copies == 1) cupsFilePuts(fp, "*cupsManualCopies: True\n"); - // No resolution requirements by any of the supported PDLs? + // No resolution requirements by any of the supported PDLs? // Use "printer-resolution-supported" attribute if (common_res == NULL) { @@ -1027,7 +1070,7 @@ ppdCreatePPDFromIPP2(char *buffe // // PageSize/PageRegion/ImageableArea/PaperDimension // - + cfGenerateSizes(supported, CF_GEN_SIZES_DEFAULT, &printer_sizes, &defattr, NULL, NULL, NULL, NULL, NULL, NULL, &min_width, &min_length, @@ -1406,15 +1449,15 @@ ppdCreatePPDFromIPP2(char *buffe if (!strcmp(sources[j], keyword)) break; if (j >= 0) - cupsFilePrintf(fp, "*InputSlot %s%s%s: \"<>setpagedevice\"\n", - ppdname, - (human_readable ? "/" : ""), - (human_readable ? human_readable : ""), j); + { + cupsFilePrintf(fp, "*InputSlot %s: \"<>setpagedevice\"\n", ppdname,j); + ppd_put_string(fp, lang, "InputSlot", ppdname, human_readable); + } else - cupsFilePrintf(fp, "*InputSlot %s%s%s: \"\"\n", - ppdname, - (human_readable ? "/" : ""), - (human_readable ? human_readable : "")); + { + cupsFilePrintf(fp, "*InputSlot %s%s%s: \"\"\n", ppdname, human_readable ? "/" : "", human_readable ? human_readable : ""); + ppd_put_string(fp, lang, "InputSlot", ppdname, human_readable); + } } cupsFilePuts(fp, "*CloseUI: *InputSlot\n"); } @@ -1449,11 +1492,8 @@ ppdCreatePPDFromIPP2(char *buffe human_readable = cfCatalogLookUpChoice((char *)keyword, "media-type", opt_strings_catalog, printer_opt_strings_catalog); - cupsFilePrintf(fp, "*MediaType %s%s%s: \"<>setpagedevice\"\n", - ppdname, - (human_readable ? "/" : ""), - (human_readable ? human_readable : ""), - ppdname); + cupsFilePrintf(fp, "*MediaType %s: \"<>setpagedevice\"\n", ppdname, ppdname); + ppd_put_string(fp, lang, "MediaType", ppdname, human_readable); } cupsFilePuts(fp, "*CloseUI: *MediaType\n"); } @@ -1776,10 +1816,8 @@ ppdCreatePPDFromIPP2(char *buffe human_readable = cfCatalogLookUpChoice((char *)keyword, "output-bin", opt_strings_catalog, printer_opt_strings_catalog); - cupsFilePrintf(fp, "*OutputBin %s%s%s: \"\"\n", - ppdname, - (human_readable ? "/" : ""), - (human_readable ? human_readable : "")); + cupsFilePrintf(fp, "*OutputBin %s: \"\"\n", ppdname); + ppd_put_string(fp, lang, "OutputBin", ppdname, human_readable); outputorderinfofound = 0; faceupdown = 1; firsttolast = 1; @@ -1833,7 +1871,7 @@ ppdCreatePPDFromIPP2(char *buffe // // Finishing options... - // + // if ((attr = ippFindAttribute(supported, "finishings-supported", IPP_TAG_ENUM)) != NULL) @@ -1958,9 +1996,8 @@ ppdCreatePPDFromIPP2(char *buffe human_readable = cfCatalogLookUpChoice(buf, "finishings", opt_strings_catalog, printer_opt_strings_catalog); - cupsFilePrintf(fp, "*StapleLocation %s%s%s: \"\"\n", ppd_keyword, - (human_readable ? "/" : ""), - (human_readable ? human_readable : "")); + cupsFilePrintf(fp, "*StapleLocation %s: \"\"\n", ppd_keyword); + ppd_put_string(fp, lang, "StapleLocation", ppd_keyword, human_readable); cupsFilePrintf(fp, "*cupsIPPFinishings %d/%s: \"*StapleLocation %s\"\n", value, keyword, ppd_keyword); } @@ -2050,9 +2087,8 @@ ppdCreatePPDFromIPP2(char *buffe human_readable = cfCatalogLookUpChoice(buf, "finishings", opt_strings_catalog, printer_opt_strings_catalog); - cupsFilePrintf(fp, "*FoldType %s%s%s: \"\"\n", ppd_keyword, - (human_readable ? "/" : ""), - (human_readable ? human_readable : "")); + cupsFilePrintf(fp, "*FoldType %s: \"\"\n", ppd_keyword); + ppd_put_string(fp, lang, "FoldType", ppd_keyword, human_readable); cupsFilePrintf(fp, "*cupsIPPFinishings %d/%s: \"*FoldType %s\"\n", value, keyword, ppd_keyword); } @@ -2149,9 +2185,8 @@ ppdCreatePPDFromIPP2(char *buffe human_readable = cfCatalogLookUpChoice(buf, "finishings", opt_strings_catalog, printer_opt_strings_catalog); - cupsFilePrintf(fp, "*PunchMedia %s%s%s: \"\"\n", ppd_keyword, - (human_readable ? "/" : ""), - (human_readable ? human_readable : "")); + cupsFilePrintf(fp, "*PunchMedia %s: \"\"\n", ppd_keyword); + ppd_put_string(fp, lang, "PunchMedia", ppd_keyword, human_readable); cupsFilePrintf(fp, "*cupsIPPFinishings %d/%s: \"*PunchMedia %s\"\n", value, keyword, ppd_keyword); } @@ -2242,9 +2277,8 @@ ppdCreatePPDFromIPP2(char *buffe human_readable = cfCatalogLookUpChoice(buf, "finishings", opt_strings_catalog, printer_opt_strings_catalog); - cupsFilePrintf(fp, "*CutMedia %s%s%s: \"\"\n", ppd_keyword, - (human_readable ? "/" : ""), - (human_readable ? human_readable : "")); + cupsFilePrintf(fp, "*CutMedia %s: \"\"\n", ppd_keyword); + ppd_put_string(fp, lang, "CutMedia", ppd_keyword, human_readable); cupsFilePrintf(fp, "*cupsIPPFinishings %d/%s: \"*CutMedia %s\"\n", value, keyword, ppd_keyword); } @@ -2268,7 +2302,7 @@ ppdCreatePPDFromIPP2(char *buffe cupsFilePrintf(fp, "*OpenUI *cupsFinishingTemplate/%s: PickOne\n", (human_readable ? human_readable : "Finishing Template")); cupsFilePuts(fp, "*OrderDependency: 10 AnySetup *cupsFinishingTemplate\n"); - cupsFilePuts(fp, "*DefaultcupsFinishingTemplate: none\n"); + cupsFilePuts(fp, "*DefaultcupsFinishingTemplate: None\n"); human_readable = cfCatalogLookUpChoice("3", "finishings", opt_strings_catalog, printer_opt_strings_catalog); @@ -2299,8 +2333,9 @@ ppdCreatePPDFromIPP2(char *buffe printer_opt_strings_catalog); if (human_readable == NULL) human_readable = (char *)keyword; - cupsFilePrintf(fp, "*cupsFinishingTemplate %s/%s: \"\n", keyword, - human_readable); + ppdPwgPpdizeName(keyword, ppdname, sizeof(ppdname)); + cupsFilePrintf(fp, "*cupsFinishingTemplate %s: \"\n", ppdname); + ppd_put_string(fp, lang, "cupsFinishingTemplate", ppdname, human_readable); for (finishing_attr = ippFirstAttribute(finishing_col); finishing_attr; finishing_attr = ippNextAttribute(finishing_col)) { if (ippGetValueTag(finishing_attr) == IPP_TAG_BEGIN_COLLECTION) { @@ -2564,14 +2599,13 @@ ppdCreatePPDFromIPP2(char *buffe if (!preset || !preset_name) continue; - if ((localized_name = - cfCatalogLookUpOption((char *)preset_name, - opt_strings_catalog, - printer_opt_strings_catalog)) == NULL) - cupsFilePrintf(fp, "*APPrinterPreset %s: \"\n", preset_name); - else - cupsFilePrintf(fp, "*APPrinterPreset %s/%s: \"\n", preset_name, - localized_name); + ppdPwgPpdizeName(preset_name, ppdname, sizeof(ppdname)); + localized_name = + cfCatalogLookUpOption((char *)preset_name, + opt_strings_catalog, + printer_opt_strings_catalog); + cupsFilePrintf(fp, "*APPrinterPreset %s: \"\n", ppdname); + ppd_put_string(fp, lang, "APPrinterPreset", ppdname, localized_name); for (member = ippFirstAttribute(preset); member; member = ippNextAttribute(preset)) @@ -2620,7 +2654,10 @@ ppdCreatePPDFromIPP2(char *buffe ippGetString(ippFindAttribute(fin_col, "finishing-template", IPP_TAG_ZERO), 0, NULL)) != NULL) - cupsFilePrintf(fp, "*cupsFinishingTemplate %s\n", keyword); + { + ppdPwgPpdizeName(keyword, ppdname, sizeof(ppdname)); + cupsFilePrintf(fp, "*cupsFinishingTemplate %s\n", ppdname); + } } } else if (!strcmp(member_name, "media")) @@ -2659,7 +2696,7 @@ ppdCreatePPDFromIPP2(char *buffe NULL)) != NULL) { ppdPwgPpdizeName(keyword, ppdname, sizeof(ppdname)); - cupsFilePrintf(fp, "*InputSlot %s\n", keyword); + cupsFilePrintf(fp, "*InputSlot %s\n", ppdname); } if ((keyword = ippGetString(ippFindAttribute(media_col, "media-type", @@ -2667,7 +2704,7 @@ ppdCreatePPDFromIPP2(char *buffe NULL)) != NULL) { ppdPwgPpdizeName(keyword, ppdname, sizeof(ppdname)); - cupsFilePrintf(fp, "*MediaType %s\n", keyword); + cupsFilePrintf(fp, "*MediaType %s\n", ppdname); } } else if (!strcmp(member_name, "print-quality")) @@ -2817,3 +2854,37 @@ http_connect(http_t **http, // IO - return (*http != NULL); } + +/* + * 'ppd_put_strings()' - Write localization attributes to a PPD file. + */ + +static void +ppd_put_string(cups_file_t *fp, /* I - PPD file */ + cups_lang_t *lang, /* I - Language */ + const char *ppd_option,/* I - PPD option */ + const char *ppd_choice,/* I - PPD choice*/ + const char *text) /* I - Localized text */ +{ + if (!text) + return; + + // Add the first line of localized text... +#if CUPS_VERSION_MAJOR > 2 + cupsFilePrintf(fp, "*%s.%s %s/", cupsLangGetName(lang), ppd_option, ppd_choice); +#else + cupsFilePrintf(fp, "*%s.%s %s/", lang->language, ppd_option, ppd_choice); +#endif // CUPS_VERSION_MAJOR >2 + + while (*text && *text != '\n') + { + // Escape ":" and "<"... + if (*text == ':' || *text == '<') + cupsFilePrintf(fp, "<%02X>", *text); + else + cupsFilePutChar(fp, *text); + + text ++; + } + cupsFilePuts(fp, ": \"\"\n"); +}