From 41c5f2f6139e4d3693c2483ee4281202a80ae451 Mon Sep 17 00:00:00 2001 From: zdohnal Date: Tue, 22 Jul 2025 15:12:19 +0200 Subject: [PATCH] Introduce foomatic-hash and reject unauthorized values in foomatic-rip (#648) The change provides a way for users to have control over what values are allowed for the foomatic-rip-related PPD keywords FoomaticRIPCommandLine, FoomaticRIPCommandLinePDF, and FoomaticRIPOptionSetting. Since the values can be later used when constructing a shell command, the filter foomatic-rip was a target of several exploits (caused by issues at different places in CUPS or in different projects of the printing stack) to do arbitrary code execution when the filter is used. By default the filter is run by user lp, so the issue is mitigated, but this PR gives admin complete control over what can be run in foomatic-rip and reject anything injected into system via different ways. First, the new tool - foomatic-hash - can be called on a PPD file or directory with drivers/PPDs, with scan output and file with hexadecimal representation of hashed values. Once the scan output is reviewed by admin, admin can decide to put the resulting hashes into /etc/foomatic/hashes.d and allow them for the filter. --- Makefile.am | 44 ++- README.md | 23 ++ configure.ac | 2 +- filter/foomatic-rip/foomatic-hash.1 | 66 ++++ filter/foomatic-rip/foomatic-hash.c | 549 ++++++++++++++++++++++++++ filter/foomatic-rip/foomatic-rip.1.in | 16 + filter/foomatic-rip/foomaticrip.c | 75 ---- filter/foomatic-rip/foomaticrip.h | 40 -- filter/foomatic-rip/options.c | 67 ++++ filter/foomatic-rip/process.c | 9 + filter/foomatic-rip/process.h | 3 + filter/foomatic-rip/util.c | 341 +++++++++++++++- filter/foomatic-rip/util.h | 67 ++++ 13 files changed, 1178 insertions(+), 124 deletions(-) create mode 100644 filter/foomatic-rip/foomatic-hash.1 create mode 100644 filter/foomatic-rip/foomatic-hash.c diff --git a/Makefile.am b/Makefile.am index f4ff7a1a3..fff4d06f7 100644 --- a/Makefile.am +++ b/Makefile.am @@ -188,6 +188,8 @@ endif if ENABLE_FOOMATIC pkgfilter_PROGRAMS += \ foomatic-rip +bin_PROGRAMS = \ + foomatic-hash endif if ENABLE_UNIVERSAL_CUPS_FILTER pkgfilter_PROGRAMS += \ @@ -278,6 +280,19 @@ commandtopclx_LDADD = \ $(LIBPPD_LIBS) \ $(CUPS_LIBS) +noinst_LTLIBRARIES = libfoomatic-util.la +libfoomatic_util_la_SOURCES = \ + filter/foomatic-rip/util.c \ + filter/foomatic-rip/util.h \ + filter/foomatic-rip/process.c \ + filter/foomatic-rip/process.h +libfoomatic_util_la_CFLAGS = \ + -DSYS_HASH_PATH='"$(datadir)/foomatic/hashes.d"' \ + -DUSR_HASH_PATH='"$(sysconfdir)/foomatic/hashes.d"' \ + $(CUPS_CFLAGS) +libfoomatic_util_la_LIBADD = \ + $(CUPS_LIBS) + foomatic_rip_SOURCES = \ filter/foomatic-rip/foomaticrip.c \ filter/foomatic-rip/foomaticrip.h \ @@ -287,24 +302,34 @@ foomatic_rip_SOURCES = \ filter/foomatic-rip/pdf.h \ filter/foomatic-rip/postscript.c \ filter/foomatic-rip/postscript.h \ - filter/foomatic-rip/process.c \ - filter/foomatic-rip/process.h \ filter/foomatic-rip/renderer.c \ filter/foomatic-rip/renderer.h \ filter/foomatic-rip/spooler.c \ - filter/foomatic-rip/spooler.h \ - filter/foomatic-rip/util.c \ - filter/foomatic-rip/util.h + filter/foomatic-rip/spooler.h foomatic_rip_CFLAGS = \ -DCONFIG_PATH='"$(sysconfdir)/foomatic"' \ $(CUPS_CFLAGS) \ $(LIBCUPSFILTERS_CFLAGS) \ - $(LIBPPD_CFLAGS) + $(LIBPPD_CFLAGS) \ + -I/$(srcdir)/filter/foomatic-rip/ foomatic_rip_LDADD = \ $(CUPS_LIBS) \ -lm \ $(LIBCUPSFILTERS_LIBS) \ - $(LIBPPD_LIBS) + $(LIBPPD_LIBS) \ + libfoomatic-util.la + +foomatic_hash_SOURCES = \ + filter/foomatic-rip/foomatic-hash.c +foomatic_hash_CFLAGS = \ + $(CUPS_CFLAGS) \ + $(LIBPPD_CFLAGS) \ + -I/$(srcdir)/filter/foomatic-rip/ +foomatic_hash_LDADD = \ + $(CUPS_LIBS) \ + -lm \ + $(LIBPPD_LIBS) \ + libfoomatic-util.la gstoraster_SOURCES = \ filter/gstoraster.c @@ -576,6 +601,7 @@ man_MANS += $(driverlessmanpages) endif foomaticmanpages = \ + filter/foomatic-rip/foomatic-hash.1 \ filter/foomatic-rip/foomatic-rip.1 if ENABLE_FOOMATIC man_MANS += $(foomaticmanpages) @@ -615,6 +641,8 @@ install-exec-hook: $(INSTALL) -d -m 755 $(DESTDIR)$(pkgbackenddir) if ENABLE_FOOMATIC $(LN_SRF) $(DESTDIR)$(pkgfilterdir)/foomatic-rip $(DESTDIR)$(bindir) + $(INSTALL) -d -m 755 $(DESTDIR)$(datadir)/foomatic/hashes.d + $(INSTALL) -d -m 755 $(DESTDIR)$(sysconfdir)/foomatic/hashes.d endif if ENABLE_DRIVERLESS $(LN_SRF) $(DESTDIR)$(pkgppdgendir)/driverless $(DESTDIR)$(bindir) @@ -626,6 +654,8 @@ endif uninstall-hook: if ENABLE_FOOMATIC $(RM) $(DESTDIR)$(bindir)/foomatic-rip + $(RMDIR) $(DESTDIR)$(datadir)/foomatic/hashes.d + $(RMDIR) $(DESTDIR)$(sysconfdir)/foomatic/hashes.d endif if ENABLE_DRIVERLESS $(RM) $(DESTDIR)$(bindir)/driverless diff --git a/README.md b/README.md index 10e66aee5..ce521bcb0 100644 --- a/README.md +++ b/README.md @@ -263,6 +263,29 @@ this filter are the same as for texttopdf (see below) as the texttops filter calls the texttopdf filter plus Ghostscript's pdf2ps. +### Tool FOOMATIC-HASH and allowing values for foomatic-rip filter + +Several CVEs for printing stack exploited a different security issue +to craft a PPD which would call the filter foomatic-rip, and provided +malicious values for PPD options FoomaticRIPCommandLine, FoomaticRIPCommandLinePDF, +and FoomaticRIPOptionSetting, because the filter constructs a command +out of the values and runs it in shell under user lp. + +To mitigate the issue, foomatic-rip now allows only values which are allowed +by admin, and the tool foomatic-hash was invented. The tool scans PPD file or +a path for drivers with affected values, and generates two files - the first +with found values for admin to review, and the second with hashes of unique +values present in the scanned file or path. If admin reviews the found values +and finds them correct, the found values will be allowed once the file with +hashes is moved into the directory /etc/foomatic/hashes.d. + +The filter foomatic-rip reads files with allowed hashes from two directories - +/etc/foomatic/hashes.d and /usr/share/foomatic/hashes.d. The former is meant +for hashes allowed by the local admin, the latter is for printer driver projects +to put there files with hashes of values which are present in their project +after the values are reviewed. + + ### Filters diff --git a/configure.ac b/configure.ac index 3fed334bc..2d4b147e2 100644 --- a/configure.ac +++ b/configure.ac @@ -120,7 +120,7 @@ PKG_CHECK_MODULES([LIBCUPSFILTERS], [libcupsfilters]) # ================ # Check for libppd # ================ -PKG_CHECK_MODULES([LIBPPD], [libppd]) +PKG_CHECK_MODULES([LIBPPD], [libppd], [AC_DEFINE(HAVE_LIBPPD, 1, [Have LIBPPD?])], [AC_MSG_RESULT([not found])]) # ====================== # Check system functions diff --git a/filter/foomatic-rip/foomatic-hash.1 b/filter/foomatic-rip/foomatic-hash.1 new file mode 100644 index 000000000..d53919838 --- /dev/null +++ b/filter/foomatic-rip/foomatic-hash.1 @@ -0,0 +1,66 @@ +.\" +.\" foomatic-hash man page. +.\" +.\" Copyright @ 2025 by Zdenek Dohnal. +.\" +.\" Licensed under Apache License v2.0. See the file "LICENSE" for more +.\" information. +.\" + + +.TH "foomatic-hash" "1" "2025-06-18" "User Commands" + +.SH "NAME" + +foomatic-hash - tool for scanning provided drivers for problematic PPD options and hash them using SHA-256 + +.SH "SYNOPSIS" + +.BI \fBfoomatic-hash\fR\ \fB--ppd\fR\ \fI\fR\ \fI\fR\ \fI\fR + +.BI \fBfoomatic-hash\fR\ \fB--ppd-paths\fR\ \fI\fR\ \fI\fR\ \fI\fR + + +.SH "DESCRIPTION" + +The tool scans the provided drivers for values of PPD keywords \fBFoomaticRIPCommandLine\fR, \fBFoomaticRIPCommandLinePDF\fR, and \fBFoomaticRIPOptionSetting\fR, puts the found values into a file for review, and prints out values hashes in hexadecimal format. The hashes are required for allowing the filter \fBfoomatic-rip\fR to process those values. + + +.SH "OPTIONS" + +The tool \fBfoomatic-hash\fR supports two options: + +.TP 10 +.BI \fB--ppd\fR\ \fI\fR +The tool scans the specific PPD file. + +.TP 10 +.BI \fB--ppd-paths\fR\ \fI\fR +The tool scans directories \fIpath1\fR, \fIpath2\fR until \fIpathN\fR for values of desired PPD keyword. Paths are absolute, symlinks are ignored. Each path is divided by comma. LibPPD support is required for the functionality. + +.SH "EXAMPLES" +Scans PPD file \fBtest.ppd\fR, prints found values into \fBfound_values\fR, hash them and save them into \fBhashed_values\fR. +.nf + + foomatic-hash --ppd test.ppd found_values hashed_values + +.fi + +Scans path \fB/etc/cups/ppd\fR for drivers, finds values if any, puts them into \fBfound_values\fR, and hashes them into \fBhashed_values\fR. +.nf + + sudo foomatic-hash --ppd-paths /etc/cups/ppd found_value hashed_values +.fi + +.SH "EXIT STATUS" + +Returns zero if scan happens successfully, non-zero return value for any error during the process. + + +.SH "SEE ALSO" + +.BR foomatic-rip (1) + + +.BR +.EL diff --git a/filter/foomatic-rip/foomatic-hash.c b/filter/foomatic-rip/foomatic-hash.c new file mode 100644 index 000000000..6e563870f --- /dev/null +++ b/filter/foomatic-rip/foomatic-hash.c @@ -0,0 +1,549 @@ +// +// foomatic-hash.c +// +// Copyright (C) 2024-2025 Zdenek Dohnal +// Copyright (C) 2008 Till Kamppeter +// Copyright (C) 2008 Lars Karlitski (formerly Uebernickel) +// +// This file implements the tool foomatic-hash, which scans presented drivers +// for FoomaticRIP* option values which are used during composing shell command, prints +// them into a file for review, hashes the found values and puts them into a separate file. +// The options in question: +// - FoomaticRIPCommandLine, +// - FoomaticRIPCommandLinePDF, +// - FoomaticRIPOptionSetting. +// +// Licensed under Apache License v2.0. See the file "LICENSE" for more +// information. +// + +#include "util.h" +#include +#include +#include +#include +#include +#include +#include + +#if defined(HAVE_LIBPPD) +#include +#endif // HAVE_LIBPPD + + +void write_array(cups_array_t *ar, char *filename); + + +// +// `write_array()` - Writes the CUPS array content into file, line by line... +// + +void +write_array(cups_array_t *ar, // I - CUPS array with contents to write + char *filename) // I - Path to file where to put data in +{ + cups_file_t *f = NULL; // CUPS file pointer + + if (cupsArrayCount(ar) == 0) + return; + + if ((f = cupsFileOpen(filename, "w")) == NULL) + { + fprintf(stderr, "Cannot open file \"%s\" for write.\n", filename); + return; + } + + for (char *s = (char*)cupsArrayGetFirst(ar); s; s = (char*)cupsArrayGetNext(ar)) + cupsFilePrintf(f, "%s\n", s); + + cupsFileClose(f); +} + + +// +// 'generate_hash_file()' - Generate file with unique hashes. +// + +int // O - 0 - success/ 1 - error +generate_hash_file(cups_array_t *values, // I - File with values to hash + char *output) // I - File where to save new file +{ + cups_array_t *syshashes = NULL, // Already existing hashes on system + *hashes = NULL; // Hashed values from input + char *data = NULL, // Pointer for storing string from array of values + comment[16], // Array for storing comment + hash_string[65]; // Array for hexadecimal representation of hashed value + + + // + // Load existing hashes from system... + // + + if (load_system_hashes(&syshashes)) + return (1); + + // + // Load hashes from previous runs if any... + // + + if (load_array(&hashes, output)) + return (1); + + // + // Now do the hashing, save the hexadecimal string if it is + // unique - if the hash is not on system or in the loaded hash + // file from previous runs... + // + + for (data = (char*)cupsArrayGetFirst(values); data; data = (char*)cupsArrayGetNext(values)) + { + if (hash_data((unsigned char*)data, strlen(data), hash_string, sizeof(hash_string))) + return (1); + + if (!cupsArrayFind(syshashes, hash_string) && !cupsArrayFind(hashes, hash_string)) + cupsArrayAdd(hashes, hash_string); + } + + if (cupsArrayCount(hashes)) + { + // + // Add comment mentioning the used hash algorithm + // + + snprintf(comment, sizeof(comment), "# %s", hash_alg); + + if (!cupsArrayFind(hashes, comment)) + cupsArrayAdd(hashes, comment); + + // + // Create a new hash file... + // + + write_array(hashes, output); + } + + cupsArrayDelete(syshashes); + cupsArrayDelete(hashes); + + return (0); +} + + +// +// `find_foomaticrip_keywords()` - reads PPD file, find FoomaticRIPCommandLine, +// FoomaticRIPCommandLinePDF and FoomaticRIPOptionSetting, save their values +// into CUPS array. +// + +void +find_foomaticrip_keywords(cups_array_t *data, // O - Array with values of FoomaticRIP* PPD keywords + cups_file_t *file) // I - File descriptor opened via CUPS API +{ + char *p; // Helper pointer + char key[128], // PPD keyword + line[256], // PPD line length is max 255 (excl. \0) + name[64], // PPD option name + text[64]; // PPD option human-readable text + + // + // Allocate struct for saving value data dynamically, + // it can span over multiplelines... + // + + dstr_t *value = create_dstr(); + + dstrassure(value, 256); + + // + // Going through the PPD file... + // + + while (cupsFileGets(file, line, 256) != NULL) + { + // + // Ignore commmented lines and whatever not starting with '*' + // to get the closest keyword + // + + if (line[0] != '*' || startswith(line, "*%")) + continue; + + // + // Get the PPD keyword + // Structure of PPD line: + // *keyword [option_name/option_text]: value1 [value2 value3...] + // + + key[0] = name[0] = text[0] = '\0'; + + if ((p = strchr(line, ':')) == NULL) + continue; + + *p = '\0'; + + sscanf(line, "*%127s%*[ \t]%63[^ \t/=)]%*1[/=]%63[^\n]", key, name, text); + + // + // Get the value... + // + + dstrclear(value); + sscanf(p + 1, " %255[^\r\n]", value->data); + value->len = strlen(value->data); + + // + // If the value is multiline (the current line ends with && or does not end with \"), + // continue saving it, and handle quotes if the value is quoted... + // + + while (1) + { + if (dstrendswith(value, "&&")) + { + // + // "&&" is the continue-on-next-line marker + // + + value->len -= 2; + value->data[value->len] = '\0'; + } + else if (value->data[0] == '\"' && !strchr(value->data +1, '\"')) + { + // + // Quoted but quotes are not yet closed - typically value blocks + // ended by keyword *End - append LF for the next line... + // + + dstrcat(value, "\n"); // keep newlines in quoted string + } + // Quotes already closed, we have the whole value... + else + break; + + // + // We read the next line if the value was not complete... + // + + if (cupsFileGets(file, line, 256) == NULL) + break; + + dstrcat(value, line); + dstrremovenewline(value); + + // + // 2047 characters to read for value sounds reasonable, + // break if we have more and crop the string... + // + + if (strlen(value->data) > 2047) + { + value->data[2047] = '\0'; + value->len = 2047; + break; + } + } + + // + // Skip if the key is not what we look for... + // + + if (strcmp(key, "FoomaticRIPCommandLine") && strcmp(key, "FoomaticRIPCommandLinePDF") && strcmp(key, "FoomaticRIPOptionSetting")) + continue; + + // + // Remove quotes... + // + + if (value->data[0] == '\"') + { + memmove(value->data, value->data +1, value->len +1); + p = strrchr(value->data, '\"'); + if (!p) + { + fprintf(stderr, "Invalid line: \"%s: ...\"\n", key); + continue; + } + *p = '\0'; + } + + // + // Remove last newline and last whitespace... + // + + dstrremovenewline(value); + + dstrtrim_right(value); + + // + // Skip empty values if there are any... + // + + if (!value->data || !value->data[0]) + continue; + + // + // Save data value + // + + if (!cupsArrayFind(data, value->data)) + cupsArrayAdd(data, value->data); + } + + free_dstr(value); +} + + +// +// `get_values_from_ppd()` - Open the PPD file and get values of +// desired FoomaticRIP PPD keywords... +// + +int // O - Return value, 0 - success, 1 - error +get_values_from_ppd(cups_array_t *data, // O - Array of found FoomaticRIP* values + char *filename) // I - Path to the file +{ + cups_file_t *file = NULL; // File descriptor + int ret = 0; // Return value + + if (!is_valid_path(filename, IS_FILE)) + return (1); + + if ((file = cupsFileOpen(filename, "r")) == NULL) + { + fprintf(stderr, "Cannot open \"%s\" for reading.\n", filename); + return (1); + } + + find_foomaticrip_keywords(data, file); + + cupsFileClose(file); + + return (ret); +} + + +#if defined(HAVE_LIBPPD) +// +// `copy_col()` - Allocation function for collection struct. +// + +ppd_collection_t * // O - Dynamically allocated PPD collection struct +copy_col(char *path) // I - Directory with drivers +{ + ppd_collection_t *col = NULL; + + if ((col = (ppd_collection_t*)calloc(1, sizeof(ppd_collection_t))) == NULL) + { + fprintf(stderr, "Cannot allocate memory for PPD collection.\n"); + return (NULL); + } + + if ((col->path = (char*)calloc(strlen(path) + 1, sizeof(char))) == NULL) + { + fprintf(stderr, "Cannot allocate memory for PPD path.\n"); + free(col); + return (NULL); + } + + snprintf(col->path, strlen(path) + 1, "%s", path); + + return (col); +} + + +// +// `free_col()` - Free function for PPD collection. +// + +void +free_col(ppd_collection_t *col) // I - PPD collection +{ + free(col->path); + free(col); +} + + +// +// `compare_col()` - Comparing function for PPD collection. +// + +int // O - Result of comparison, 0 - the same, 1 - differs +compare_col(char *a, // I - PPD collection + ppd_collection_t *b) // I - PPD collection +{ + if(!strcmp(a, b->path)) + return (0); + + return (1); +} +#endif // HAVE_LIBPPD + + +// +// `get_values_from_ppdpaths()` - Goes via sent list of directories, gets +// PPDs and gets value strings for FoomaticRIP related PPD keywords. +// + +int // O - Return value, 0 - success, 1 - error +get_values_from_ppdpaths(cups_array_t *data, // O - Array of found values + char *ppdpaths) // I - List of directories with drivers, comma separated +{ +#if defined(HAVE_LIBPPD) + char *path = NULL, // Directory path + *start = NULL, // Helper pointer to start of string + *end = NULL; // Helper pointer to end of string + cups_array_t *ppd_collections = NULL, // Directories with drivers + *ppds = NULL; // PPD URIs + cups_file_t *ppdfile = NULL; // PPD file descriptor + int ret = 0; // Return value + ppd_info_t *ppd = NULL; // In-memory record of PPD + + + if ((ppd_collections = cupsArrayNew3((cups_array_func_t)compare_col, NULL, NULL, 0, (cups_acopy_func_t)copy_col, (cups_afree_func_t)free_col)) == NULL) + { + fprintf(stderr, "Could not allocate PPD collection array.\n"); + return (1); + } + + // + // Go through input directory list, validate each record, + // and add them into array... + // + + if ((path = strchr(ppdpaths, ',')) == NULL) + { + if (is_valid_path(ppdpaths, IS_DIR)) + cupsArrayAdd(ppd_collections, ppdpaths); + } + else + { + for (start = end = ppdpaths; *end; start = end) + { + if ((end = strchr(start, ',')) != NULL) + *end++ = '\0'; + else + end = start + strlen(start); + + if (is_valid_path(start, IS_DIR) && !cupsArrayFind(ppd_collections, start)) + cupsArrayAdd(ppd_collections, start); + } + } + + // + // Get array of in-memory PPD records, later used for generating the PPDs themselves... + // + + if ((ppds = ppdCollectionListPPDs(ppd_collections, 0, 0, NULL, NULL, NULL)) == NULL) + goto end; + + // + // Go through in-memory PPD records, generate a PPD and search for FoomaticRIP* keywords... + // + + for (ppd = (ppd_info_t*)cupsArrayGetFirst(ppds); ppd; ppd = (ppd_info_t*)cupsArrayGetNext(ppds)) + { + if ((ppdfile = ppdCollectionGetPPD(ppd->record.name, ppd_collections, NULL, NULL)) == NULL) + continue; + + find_foomaticrip_keywords(data, ppdfile); + + cupsFileClose(ppdfile); + } + + +end: + for (ppd = (ppd_info_t*)cupsArrayGetFirst(ppds); ppd; ppd = (ppd_info_t*)cupsArrayGetNext(ppds)) + free(ppd); + + cupsArrayDelete(ppds); + + cupsArrayDelete(ppd_collections); + + return (ret); +#else + fprintf(stdout, "foomatic-hash is not compiled with LIBPPD support.\n"); + + return (0); +#endif // HAVE_LIBPPD +} + + +void +help() +{ + printf("Usage:\n" + "foomatic-hash --ppd \n" + "foomatic-hash --ppd-paths \n" + "\n" + "Finds values of FoomaticRIPCommandLine, FoomaticRIPPDFCommandLine\n" + "and FoomaticRIPOptionSetting from the specified PPDs, appends them\n" + "into the specified scan output for review, and hashes the found values.\n" + "\n" + "--ppd - PPD file to read\n" + "--ppd-paths - Paths to look for PPDs, available only with libppd\n" + " - Found required values from drivers\n" + " - Output file with hashes\n"); +} + + +int +main(int argc, + char** argv) +{ + cups_array_t *data = NULL; // Found FoomaticRIP* PPD keyword values + int ret = 1; + + + if (argc != 5) + { + help(); + return (0); + } + + // + // End up early if we can't write into paths provided as arguments + // + + if (!is_valid_path(argv[3], IS_FILE) || + ((data = cupsArrayNew3((cups_array_func_t)strcmp, NULL, NULL, 0, (cups_acopy_func_t)strdup, (cups_afree_func_t)free)) == NULL) || + !is_valid_path(argv[4], IS_FILE)) + return (1); + + // + // We scan single PPD file, or from directory (if libppd support is present) + // + + if (!strcmp(argv[1], "--ppd")) + { + if (get_values_from_ppd(data, argv[2])) + return (1); + } + else if (!strcmp(argv[1], "--ppd-paths")) + { + if (get_values_from_ppdpaths(data, argv[2])) + return (1); + } + else + { + fprintf(stderr, "Unsupported argument.\n"); + return (1); + } + + // + // Write found values of FoomaticRIPCommandLine, FoomaticRIPPDFCommandLine and FoomaticRIPOptionSetting + // PPD keywords... + // + + write_array(data, argv[3]); + + // + // Hash the found values.. + // + + ret = generate_hash_file(data, argv[4]); + + cupsArrayDelete(data); + + + return (ret); +} diff --git a/filter/foomatic-rip/foomatic-rip.1.in b/filter/foomatic-rip/foomatic-rip.1.in index 9685a95f5..3dff5215f 100644 --- a/filter/foomatic-rip/foomatic-rip.1.in +++ b/filter/foomatic-rip/foomatic-rip.1.in @@ -193,6 +193,15 @@ friends. Several PPD files use shell constructs that require a more modern shell like \fBbash\fR, \fBzsh\fR, or \fBksh\fR. +.SH PPD OPTION VALUE RESTRICTIONS AND EXCEPTIONS + +The values of PPD options \fBFoomaticRIPCommandLine\fR, \fBFoomaticRIPCommandLinePDF\fR and \fBFoomaticRIPOptionSetting\fR +are rejected in the default configuration because of security implications. Users can use the tool \fBfoomatic-hash(1)\fR, which provides +values of affected PPD options from found drivers and hashes of those values in hexadecimal format. User is expected to review the found values, +and if there is nothing suspicious in the output, copy the file with hashes into into the directory \fB@sysconfdir@/foomatic/hashes.d\fR +to allow the exceptions for found values. + + .SH FILES .PD 0 .TP 0 @@ -209,6 +218,13 @@ The PPD files of the currently defined printers Configuration file for foomatic-rip +.TP 0 +@sysconfdir@/foomatic/hashes.d +.TP 0 +@datadir@/foomatic/hashes.d + +Directories with hashes of allowed values + .PD 0 .\".SH SEE ALSO diff --git a/filter/foomatic-rip/foomaticrip.c b/filter/foomatic-rip/foomaticrip.c index 036d6138b..2e892cb80 100644 --- a/filter/foomatic-rip/foomaticrip.c +++ b/filter/foomatic-rip/foomaticrip.c @@ -35,72 +35,6 @@ #include -// Logging -FILE* logh = NULL; - - -void -_logv(const char *msg, - va_list ap) -{ - if (!logh) - return; - vfprintf(logh, msg, ap); - fflush(logh); -} - - -void -_log(const char* msg, - ...) -{ - va_list ap; - va_start(ap, msg); - _logv(msg, ap); - va_end(ap); -} - - -void -close_log() -{ - if (logh && logh != stderr) - fclose(logh); -} - - -int -redirect_log_to_stderr() -{ - if (dup2(fileno(logh), fileno(stderr)) < 0) - { - _log("Could not dup logh to stderr\n"); - return (0); - } - return (1); -} - - -void -rip_die(int status, - const char *msg, - ...) -{ - va_list ap; - - _log("Process is dying with \""); - va_start(ap, msg); - _logv(msg, ap); - va_end(ap); - _log("\", exit stat %d\n", status); - - _log("Cleaning up...\n"); - kill_all_processes(); - - exit(status); -} - - jobparams_t *job = NULL; @@ -186,8 +120,6 @@ char cupsfilterpath[PATH_MAX] = "/usr/local/lib/cups/filter:" "/opt/cups/filter:" "/usr/lib/cups/filter"; -char modern_shell[] = SHELL; - void config_set_option(const char *key, @@ -239,13 +171,6 @@ config_from_file(const char *filename) } -const char * -get_modern_shell() -{ - return (modern_shell); -} - - // returns position in 'str' after the option char * extract_next_option(char *str, diff --git a/filter/foomatic-rip/foomaticrip.h b/filter/foomatic-rip/foomaticrip.h index 5c9ecfaf0..60d4059c6 100644 --- a/filter/foomatic-rip/foomaticrip.h +++ b/filter/foomatic-rip/foomaticrip.h @@ -32,36 +32,6 @@ #define LOG_FILE "/tmp/foomatic-rip" #endif - -// Constants used by this filter -// -// Error codes, as some spoolers behave different depending on the reason why -// the RIP failed, we return an error code. - -#define EXIT_PRINTED 0 // file was printed normally -#define EXIT_PRNERR 1 // printer error occured -#define EXIT_PRNERR_NORETRY 2 // printer error with no hope - // of retry -#define EXIT_JOBERR 3 // job is defective -#define EXIT_SIGNAL 4 // terminated after catching - // signal -#define EXIT_ENGAGED 5 // printer is otherwise engaged - // (connection refused) -#define EXIT_STARVED 6 // starved for system resources -#define EXIT_PRNERR_NORETRY_ACCESS_DENIED 7 // bad password? bad port - // permissions? -#define EXIT_PRNERR_NOT_RESPONDING 8 // just doesn't answer at all - // (turned off?) -#define EXIT_PRNERR_NORETRY_BAD_SETTINGS 9 // interface settings are - // invalid -#define EXIT_PRNERR_NO_SUCH_ADDRESS 10 // address lookup failed, may - // be transient -#define EXIT_PRNERR_NORETRY_NO_SUCH_ADDRESS 11 // address lookup failed, not - // transient -#define EXIT_INCAPABLE 50 // printer wants (lacks) - // features or resources - - // Supported spoolers are currently: // // cups - CUPS - Common Unix Printing System @@ -73,11 +43,6 @@ // The spooler from which foomatic-rip was called. set in main() extern int spooler; -#ifndef PATH_MAX -#define PATH_MAX 4096 -#endif -#define CMDLINE_MAX 65536 - typedef struct { char printer[256]; @@ -95,11 +60,6 @@ typedef struct jobparams_t *get_current_job(); -void _log(const char* msg, ...); -int redirect_log_to_stderr(); -void rip_die(int status, const char *msg, ...); - -const char *get_modern_shell(); FILE *open_postpipe(); extern struct dstr *currentcmd; diff --git a/filter/foomatic-rip/options.c b/filter/foomatic-rip/options.c index bad833bc1..032fe9ec3 100644 --- a/filter/foomatic-rip/options.c +++ b/filter/foomatic-rip/options.c @@ -102,6 +102,42 @@ get_icc_profile_for_qualifier(const char **qualifier) } +// +// 'is_allowed_value' - Check if the option value is allowed. +// + +int // O - Boolean value - true 1 / false 0 +is_allowed_value(cups_array_t *ar, // I - Array of already known hashes from system + char *value, // I - Scanned value from PPD file + size_t value_len) // I - Value length +{ + char hash_string[65]; // Help array to store hexadecimal hashed string + + // + // Empty string is allowed... + // + + if (!value_len) + return (1); + + // + // Hash the value and get hexadecimal string for it... + // + + if (hash_data((unsigned char*)value, value_len, hash_string, sizeof(hash_string))) + return (0); + + // + // Check if the found hexadecimal hashed string is in the array -> allowed on the system... + // + + if (cupsArrayFind(ar, hash_string)) + return (1); + + return (0); +} + + // a selector is a general tri-dotted specification. // The 2nd and 3rd elements of the qualifier are optionally modified by // cupsICCQualifier2 and cupsICCQualifier3: @@ -1866,12 +1902,19 @@ read_ppd_file(const char *filename) option_t *opt, *current_opt = NULL; param_t *param; icc_mapping_entry_t *entry; + cups_array_t *known_hashes = NULL; fh = fopen(filename, "r"); if (!fh) rip_die(EXIT_PRNERR_NORETRY_BAD_SETTINGS, "Unable to open PPD file %s\n", filename); _log("Parsing PPD file ...\n"); + if (load_system_hashes(&known_hashes)) + { + fclose(fh); + rip_die(EXIT_PRNERR_NORETRY, "Not enough memory for array allocation\n."); + } + dstrassure(value, 256); qualifier_data = list_create(); @@ -1955,10 +1998,26 @@ read_ppd_file(const char *filename) } else if (strcmp(key, "FoomaticRIPCommandLine") == 0) { + if (!is_allowed_value(known_hashes, value->data, strlen(value->data))) + { + cupsArrayDelete(known_hashes); + fclose(fh); + + rip_die(EXIT_PRNERR_NOTALLOWED, "ERROR: The value of the key %s is not among the allowed values - see foomatic-rip man page for more instructions.\n", key); + } + unhtmlify(cmd, 4096, value->data); } else if (strcmp(key, "FoomaticRIPCommandLinePDF") == 0) { + if (!is_allowed_value(known_hashes, value->data, strlen(value->data))) + { + cupsArrayDelete(known_hashes); + fclose(fh); + + rip_die(EXIT_PRNERR_NOTALLOWED, "ERROR: The value of the key %s is not among the allowed values - see foomatic-rip man page for more instructions.\n", key); + } + unhtmlify(cmd_pdf, 4096, value->data); } else if (!strcmp(key, "cupsFilter")) @@ -2097,6 +2156,14 @@ read_ppd_file(const char *filename) } else if (!strcmp(key, "FoomaticRIPOptionSetting")) { + if (!is_allowed_value(known_hashes, value->data, strlen(value->data))) + { + cupsArrayDelete(known_hashes); + fclose(fh); + + rip_die(EXIT_PRNERR_NOTALLOWED, "ERROR: The value of the key %s is not among the allowed values - see foomatic-rip man page for more instructions.\n", key); + } + // "*FoomaticRIPOptionSetting