diff --git a/projects/cups/README.md b/projects/cups/README.md index 012c66d..420bdd7 100644 --- a/projects/cups/README.md +++ b/projects/cups/README.md @@ -1,8 +1,16 @@ -## CUPS fuzzing harness +## Fuzzing harness of cups -### Set up, build, and run. +### Build with OSS-Fuzz -#### Note: This only works in Ubuntu. +```bash +python infra/helper.py build_fuzzers --sanitizer address --engine libfuzzer --architecture x86_64 cups +``` + +The `build.sh` used in OSS-Fuzz is hosted in [oss_fuzz_build.sh](./oss_fuzz_build.sh). + +### Local build with libFuzzer + +_Note: Only tested in Ubuntu 22.04._ ```bash apt-get install zlib1g-dev libavahi-client-dev libsystemd-dev @@ -18,4 +26,39 @@ cd fuzzing/cups/ ./fuzz_cups fuzz_cups_seed/ fuzz_cups_seed_corpus/ ./fuzz_ipp fuzz_ipp_seed/ fuzz_ipp_seed_corpus/ ./fuzz_raster fuzz_raster_seed/ fuzz_raster_seed_corpus/ +./fuzz_ppd fuzz_pdd_seed/ fuzz_pdd_seed_corpus/ +``` + +### Local build with AFL++ + +```bash +# build +CC=afl-clang-lto +CXX=afl-clang-lto++ +AFL_USE_ASAN=1 +./configure --enable-static --disable-shared --host=x86_64 +make -C cups/ +make -C fuzzer/ + +# fuzz +afl-fuzz -i in/ -o out/ ./fuzz_ppd @@ ``` + +### Fuzz corpora + +#### fuzz_ppd + +From existing test input in cups, specifically cups/test.ppd and cups/test2.ppd + +```bash +echo -n -e "Letter\0na-letter\0roll_max_36.1025x3622.0472in\0 4x6\0Foo\0foo=buz option=option Foo=Buz tag=fooz\0datanum1920\0datanum1080\0" > 1 +cat cups/test.ppd >> 1 + +echo -n -e "A4\0Letter\0iso_a4_210x297mm\0 2x8\0Option\0Option=Bar Foo=Buz AL=666 Astra=Aspera\0datanum1337\0datanum4242\0 " > 2 +cat cups/test2.ppd >> 2 + +echo -n -e "A4\0A4\0iso_a4_210x297mm\0 2x8\0Astra\0Per=Aspera Ad=Astra\0datanum2048\0datanum2048\0 " > 3 +cat cups/test2.ppd >> 3 +``` + + diff --git a/projects/cups/build-fuzz.sh b/projects/cups/build-fuzz.sh index c23ded7..a31c9a1 100644 --- a/projects/cups/build-fuzz.sh +++ b/projects/cups/build-fuzz.sh @@ -25,6 +25,7 @@ popd mkdir -p fuzzing/cups/fuzz_cups_seed/ mkdir -p fuzzing/cups/fuzz_ipp_seed/ mkdir -p fuzzing/cups/fuzz_raster_seed/ +mkdir -p fuzzing/cups/fuzz_ppd_seed/ echo "" echo "Run: ./fuzzing/\${fuzzer} fuzzing/\${fuzzer}_seed fuzzing/\${fuzzer}_seed_corpus" diff --git a/projects/cups/fuzzer/Makefile b/projects/cups/fuzzer/Makefile index f82f712..609086b 100644 --- a/projects/cups/fuzzer/Makefile +++ b/projects/cups/fuzzer/Makefile @@ -2,7 +2,8 @@ TARGETS = \ fuzz_cups \ fuzz_ipp \ fuzz_raster \ - fuzz_array + fuzz_array \ + # fuzz_ppd # For local build # export CC=clang diff --git a/projects/cups/fuzzer/fuzz_ppd.c b/projects/cups/fuzzer/fuzz_ppd.c new file mode 100644 index 0000000..dfe9755 --- /dev/null +++ b/projects/cups/fuzzer/fuzz_ppd.c @@ -0,0 +1,295 @@ +/* + * PPD, Cache and PWG fuzz program for CUPS + * + * This harness is a combination of + * testppd.c, testcache.c and testpwg.c + * + * Licensed under Apache License v2.0. + * See the file "LICENSE" for more information. + */ + + #include "ppd-private.h" + #include "file-private.h" + #include + #include + #include + #include + + int fuzz_ppd(char *string, int len, char *filename, char *pwgname); + void unlink_tempfile(void); + + extern int + LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) + { + /* + * We need a huge input, because it should contain + * options and ppd file + */ + if (Size < 1000) + return 1; + + atexit(unlink_tempfile); + + char *filename = (char *)malloc(sizeof(char) * 256); + char *pwgname = (char *)malloc(sizeof(char) * 256); + sprintf(filename, "/tmp/fuzz_ppd.%d.ppd", getpid()); + sprintf(pwgname, "/tmp/fuzz_ppd.%d.pwg", getpid()); + + char *string = (char *)calloc(sizeof(char), Size + 1); + memcpy(string, Data, Size); + int len = Size; + + fuzz_ppd(string, len, filename, pwgname); + + unlink_tempfile(); + free(filename); + free(pwgname); + free(string); + return 0; + } + + int fuzz_ppd(char *data, int len, char *filename, char *pwgname) + { + int num_options = 0, // number of fuzz-generated options + finishings[1024], + width, + length; + cups_option_t *options = NULL; + _ppd_cache_t *pc, + *pc2; + ppd_choice_t *ppd_bin; + ppd_attr_t *attr; + ppd_size_t minsize, + maxsize, + *size; + cups_page_header2_t header; + ipp_t *job; + + /* + * Create and fill variables (options) + * with fuzz-generated values + */ + + char *ppdsize = strdup(data); + len -= strlen(ppdsize) + 1; + if (len <= 0) + return 1; + data += strlen(ppdsize) + 1; + + char *legacy = strdup(data); + len -= strlen(legacy) + 1; + if (len <= 0) + return 1; + data += strlen(legacy) + 1; + + char *pwg = strdup(data); + len -= strlen(pwg) + 1; + if (len <= 0) + return 1; + data += strlen(pwg) + 1; + + char *ppdmedia = strdup(data); + len -= strlen(ppdmedia) + 1; + if (len <= 0) + return 1; + data += strlen(ppdmedia) + 1; + + char *marked_option = strdup(data); + len -= strlen(marked_option) + 1; + if (len <= 0) + return 1; + data += strlen(marked_option) + 1; + + char *options_str = strdup(data); + len -= strlen(options_str) + 1; + if (len <= 0) + return 1; + data += strlen(options_str) + 1; + + char buf[12] = {0}; + if (!strncpy(buf, data, 11)) + return 1; + + length = atoi(buf); + data += strlen(buf); + len -= strlen(buf); + + if (!strncpy(buf, data, 11)) + return (1); + + width = atoi(buf); + data += strlen(buf); + len -= strlen(buf); + + /* + * Create and fill the array of cups options + * and values to check correct work of + * ppdMarkOption(), cupsGetOption(), + * cupsGetConflicts(), cupsResolveConflicts() + * and ppdInstallableConflict() functions + */ + + char **cups_options = (char **)malloc(sizeof(char *) * 2); + char **cups_values = (char **)malloc(sizeof(char *) * 2); + int elem_counter = 0, counter = 0; + + for (int i = 0; i < strlen(options_str); i++) + { + cups_options[elem_counter] = (char *)malloc(sizeof(char)); + cups_values[elem_counter] = (char *)malloc(sizeof(char)); + cups_options[elem_counter][0] = '\0'; + cups_values[elem_counter][0] = '\0'; + if (!options_str[i]) + break; + + counter = 0; + while(options_str[i] != '=' && options_str[i] && options_str[i] != ' ') + { + cups_options[elem_counter] = (char *)realloc(cups_options[elem_counter], sizeof(char) * (counter + 2)); + cups_options[elem_counter][counter] = options_str[i]; + counter++; + i++; + } + cups_options[elem_counter][counter] = '\0'; + if (options_str[i] == '=') + { + ++i; + counter = 0; + while(options_str[i] != ' ' && options_str[i]) + { + cups_values[elem_counter] = (char *)realloc(cups_values[elem_counter], sizeof(char) * (counter + 2)); + cups_values[elem_counter][counter] = options_str[i]; + counter++; + i++; + } + cups_values[elem_counter][counter] = '\0'; + } + elem_counter++; + cups_options = (char **)realloc(cups_options, sizeof(char *) * (elem_counter + 1)); + cups_values = (char **)realloc(cups_values, sizeof(char *) * (elem_counter + 1)); + } + if (len <= 0) + return 1; + + /* + * Create and fill .ppd file + * with fuzz-generated data + */ + + FILE *fp = fopen(filename, "wb"); + if (!fp) + return 1; + + fwrite(data, sizeof(*data), len, fp); + fclose(fp); + + ppd_file_t *ppd = NULL; + + if ((ppd = ppdOpenFile(filename)) == NULL) + { + ppd_status_t err; /* Last error in file */ + int line; /* Line number in file */ + ppdLastError(&line); + ppdErrorString(err); + return 1; + } + + pc = _ppdCacheCreateWithPPD(NULL, ppd); + + /* + * Do pwg tests from testpwg.c + */ + + char *pagesize; + _ppdCacheWriteFile(pc, pwgname, NULL); + pc2 = _ppdCacheCreateWithFile(pwgname, NULL); + _ppdCacheDestroy(pc2); + ppdPageSize(ppd, ppdsize); + pagesize = _ppdCacheGetPageSize(pc, NULL, ppdsize, NULL); + job = ippNew(); + ippDelete(job); + pwgMediaForPWG(pwg); + pwgMediaForLegacy(legacy); + pwgMediaForPPD(ppdmedia); + pwgMediaForSize(width, length); + + num_options = cupsParseOptions(options_str, num_options, &options); + ppdMarkDefaults(ppd); + cupsMarkOptions(ppd, num_options, options); + ppdConflicts(ppd); + + _ppdCacheGetFinishingValues(ppd, pc, (int)sizeof(finishings) / sizeof(finishings[0]), finishings); + cupsRasterInterpretPPD(&header, ppd, num_options, options, NULL); + + if (strlen(marked_option) > 0) + { + char *choice = (char *)calloc(1, sizeof(char)); + for (int i = 0; i < strlen(marked_option); i++) + { + if (!marked_option[i] || marked_option[i] != ' ') + { + choice = (char *)realloc(choice, sizeof(char) * (i + 2)); + choice[i] = marked_option[i]; + choice[i + 1] = '\0'; + } + else + break; + } + ppdFindAttr(ppd, choice, marked_option + strlen(choice)); + ppdFindNextAttr(ppd, choice, NULL); + if ((ppd_bin = ppdFindMarkedChoice(ppd, choice)) != NULL) + _ppdCacheGetBin(pc, ppd_bin->choice); + char buffer[1024] = {0}; + ppdLocalizeIPPReason(ppd, choice, marked_option + strlen(choice), buffer, sizeof(buffer)); + for (int i = 0; i < elem_counter; i++) + { + ppdMarkOption(ppd, cups_options[i], cups_values[i]); + cupsGetOption(cups_options[i], num_options, options); + num_options = cupsGetConflicts(ppd, cups_options[i], cups_values[i], &options); + cupsResolveConflicts(ppd, cups_options[i], cups_values[i], &num_options, &options); + ppdInstallableConflict(ppd, cups_options[i], cups_values[i]); + } + ppdInstallableConflict(ppd, options_str, choice); + ppdLocalizeMarkerName(ppd, choice); + free(choice); + } + + for (int i = 0; i < 5; i++) + ppdEmitString(ppd, i, 0.0); + + ppdPageSizeLimits(ppd, &minsize, &maxsize); + ppdPageSize(ppd, NULL); + + for (int i = 0; i < elem_counter; i++) + { + free(cups_options[i]); + free(cups_values[i]); + } + + free(cups_options); + free(cups_values); + + cupsFreeOptions(num_options, options); + _ppdCacheDestroy(pc); + ppdClose(ppd); + + free(options_str); + free(ppdsize); + free(marked_option); + free(legacy); + free(pwg); + free(ppdmedia); + return 0; + } + + void unlink_tempfile(void) + { + char filename[256]; + sprintf(filename, "/tmp/fuzz_ppd.%d.ppd", getpid()); + unlink(filename); + sprintf(filename, "/tmp/fuzz_ppd.%d.pwg", getpid()); + unlink(filename); + sprintf(filename, "%s.N", filename); + unlink(filename); + } + \ No newline at end of file diff --git a/projects/cups/seeds/fuzz_ppd_seed_corpus/1 b/projects/cups/seeds/fuzz_ppd_seed_corpus/1 new file mode 100644 index 0000000..3fa1112 Binary files /dev/null and b/projects/cups/seeds/fuzz_ppd_seed_corpus/1 differ diff --git a/projects/cups/seeds/fuzz_ppd_seed_corpus/2 b/projects/cups/seeds/fuzz_ppd_seed_corpus/2 new file mode 100644 index 0000000..b317709 Binary files /dev/null and b/projects/cups/seeds/fuzz_ppd_seed_corpus/2 differ diff --git a/projects/cups/seeds/fuzz_ppd_seed_corpus/3 b/projects/cups/seeds/fuzz_ppd_seed_corpus/3 new file mode 100644 index 0000000..dcb3a0e Binary files /dev/null and b/projects/cups/seeds/fuzz_ppd_seed_corpus/3 differ