diff --git a/projects/cups-filters/fuzzer/Makefile b/projects/cups-filters/fuzzer/Makefile index eafd49b..b7670f2 100644 --- a/projects/cups-filters/fuzzer/Makefile +++ b/projects/cups-filters/fuzzer/Makefile @@ -1,34 +1,61 @@ -FUZZ_SRCS := $(wildcard fuzz_*.c) -TARGETS := $(basename $(FUZZ_SRCS)) - -# CC=clang -# CXX=clang++ - -# ifeq ($(FUZZING_ENGINE), afl) -# ifneq ($(SANITIZER), memory) -# export CC=afl-clang-fast -# export CXX=afl-clang-fast++ -# endif -# endif - -INCDIR=-I./../filter -I./../fontembed -I./../ -LIBDIR=-L./../filter -L./../fontembed -L./../ -L./../.libs - -BUILD_FLAGS=-g -O0 -LINK_FLAGS=-Wl,--allow-multiple-definition -l:libfontembed.a -l:libtiff.a -l:libjpeg.a -# LIB_FUZZING_ENGINE = -fsanitize=fuzzer,address - -All: $(TARGETS) +# Build the cups-filters PDF-parsing fuzzer(s). +# +# Works both: +# * under OSS-Fuzz -- CC/CXX/CFLAGS/CXXFLAGS/LIB_FUZZING_ENGINE/OUT come from +# the environment; `make && make ossfuzz`. +# * for local builds -- run from inside a built cups-filters tree (e.g. after +# copying this dir to cups-filters/ossfuzz/); the +# defaults below give a usable libFuzzer+ASan binary. + +CC ?= clang +CXX ?= clang++ +# Local default engine (OSS-Fuzz overrides this with its own libFuzzer engine). +LIB_FUZZING_ENGINE ?= -fsanitize=fuzzer,address +OUT ?= . + +# Path to the (built) cups-filters source tree relative to this directory. +CUPS_FILTERS ?= .. + +QPDF_CFLAGS := $(shell pkg-config --cflags libqpdf 2>/dev/null) +QPDF_LIBS := $(shell pkg-config --libs libqpdf 2>/dev/null || echo -lqpdf) +CUPS_LIBS := $(shell cups-config --libs 2>/dev/null || echo -lcups) + +INCDIR := -I$(CUPS_FILTERS)/filter/pdftopdf -I$(CUPS_FILTERS)/filter -I$(CUPS_FILTERS) $(QPDF_CFLAGS) + +# pdftopdf processor objects produced by the cups-filters build, minus the one +# that defines main() (pdftopdf-pdftopdf.o). +PDFTOPDF_OBJS := $(filter-out %/pdftopdf-pdftopdf.o,\ + $(wildcard $(CUPS_FILTERS)/filter/pdftopdf/pdftopdf-*.o)) + +FUZZERS := fuzz_pdf + +all: $(FUZZERS) + +fuzz_pdf: fuzz_pdf.cc + $(CXX) $(CXXFLAGS) -std=c++17 $(INCDIR) -c -o fuzz_pdf.o fuzz_pdf.cc + $(CXX) $(CXXFLAGS) fuzz_pdf.o $(PDFTOPDF_OBJS) \ + $(CUPS_FILTERS)/.libs/libcupsfilters.a \ + $(LIB_FUZZING_ENGINE) $(QPDF_LIBS) $(CUPS_LIBS) \ + -Wl,--allow-multiple-definition -o fuzz_pdf clean: - rm -f *.o $(TARGETS) - -pdfutils.o: - $(CC) $(CFLAGS) $(INCDIR) -c -o pdfutils.o ../filter/pdfutils.c - -$(TARGETS): pdfutils.o - $(CC) $(CFLAGS) $(INCDIR) $(BUILD_FLAGS) -c -o $@.o $@.c - $(CXX) $(CFLAGS) $(LIBDIR) $(LIB_FUZZING_ENGINE) $(LINK_FLAGS) -o $@ $@.o pdfutils.o $(LINK_FLAGS) - -ossfuzz: - cp $(TARGETS) $(OUT) \ No newline at end of file + rm -f *.o $(FUZZERS) + +# OSS-Fuzz packaging: copy the fuzzer(s) to $OUT and bundle their shared-library +# closure alongside with RPATH=$ORIGIN, because the base-runner image lacks +# libqpdf/libcups and their transitive deps (avahi, gnutls stack, ...). +# (Not needed for local runs, where the system libraries are present.) +ossfuzz: $(FUZZERS) + for f in $(FUZZERS); do \ + cp $$f $(OUT)/; \ + ldd $(OUT)/$$f | awk '/=> \//{print $$3}' | while read -r so; do \ + b=$$(basename $$so); \ + case $$b in \ + libc.so.*|libm.so.*|libpthread.so.*|libdl.so.*|librt.so.*|ld-linux*) continue;; \ + esac; \ + cp -L $$so $(OUT)/$$b; \ + done; \ + patchelf --force-rpath --set-rpath '$$ORIGIN' $(OUT)/$$f; \ + done + +.PHONY: all clean ossfuzz diff --git a/projects/cups-filters/fuzzer/fuzz_pdf.c b/projects/cups-filters/fuzzer/fuzz_pdf.c deleted file mode 100644 index 67d77f9..0000000 --- a/projects/cups-filters/fuzzer/fuzz_pdf.c +++ /dev/null @@ -1,83 +0,0 @@ -#include "pdfutils.h" -#include -#include -#include -#include -#include -#include -#include - -static void redirect_stdout_stderr(); // hide stdout - -int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { - - if (Size < 5) { - return 0; - } - - redirect_stdout_stderr(); - - pdfOut *pdf; - - pdf=pdfOut_new(); - assert(pdf); - - pdfOut_begin_pdf(pdf); - - // bad font - int font_obj=pdfOut_add_xref(pdf); - pdfOut_printf(pdf,"%d 0 obj\n" - "<>\n" - "endobj\n" - ,font_obj,"Courier"); - // test - const int PageWidth=595,PageLength=842; - int cobj=pdfOut_add_xref(pdf); - - char *buf = (char *)malloc(Size + 1); - if (!buf) return 0; - memcpy(buf, Data, Size); - buf[Size] = '\0'; - - pdfOut_printf(pdf,"%d 0 obj\n" - "<>\n" - "stream\n" - "%s\n" - "endstream\n" - "endobj\n" - ,cobj, (int) strlen(buf), buf); - - int obj=pdfOut_add_xref(pdf); - pdfOut_printf(pdf,"%d 0 obj\n" - "<> >>\n" - ">>\n" - "endobj\n" - ,obj,PageWidth,PageLength,cobj,font_obj); - pdfOut_add_page(pdf,obj); - pdfOut_finish_pdf(pdf); - - pdfOut_free(pdf); - free(buf); - - return 0; -} - - -void redirect_stdout_stderr() { - int dev_null = open("/dev/null", O_WRONLY); - if (dev_null < 0) { - perror("Failed to open /dev/null"); - return; - } - dup2(dev_null, STDOUT_FILENO); - dup2(dev_null, STDERR_FILENO); - close(dev_null); -} \ No newline at end of file diff --git a/projects/cups-filters/fuzzer/fuzz_pdf.cc b/projects/cups-filters/fuzzer/fuzz_pdf.cc new file mode 100644 index 0000000..6b9d078 --- /dev/null +++ b/projects/cups-filters/fuzzer/fuzz_pdf.cc @@ -0,0 +1,69 @@ +// PDF-parsing fuzzer for cups-filters' pdftopdf filter. +// +// Unlike the previous harness (which only drove the pdfOut *writer* with the +// input as an opaque content stream), this feeds the fuzz bytes to the real +// qpdf-backed PDFTOPDF_Processor: it parses the PDF (qpdf processFile), walks +// the page tree, reads page geometry, and checks permissions / AcroForm — i.e. +// it actually exercises PDF parsing. + +#include "pdftopdf_processor.h" + +#include +#include +#include +#include +#include +#include +#include + +// Silence stdout (qpdf / the processor may print) but keep stderr intact so +// libFuzzer progress and ASAN crash reports are preserved; restore afterwards. +static int silence_stdout() { + fflush(stdout); + int saved = dup(STDOUT_FILENO); + int devnull = open("/dev/null", O_WRONLY); + if (devnull >= 0) { + dup2(devnull, STDOUT_FILENO); + close(devnull); + } + return saved; +} + +static void restore_stdout(int saved) { + if (saved < 0) + return; + fflush(stdout); + dup2(saved, STDOUT_FILENO); + close(saved); +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { + if (Size < 5) + return 0; + + int saved_stdout = silence_stdout(); + + FILE *f = fmemopen(const_cast(Data), Size, "rb"); + if (f) { + std::unique_ptr proc(PDFTOPDF_Factory::processor()); + try { + // loadFile parses the PDF via qpdf (returns false on malformed input). + if (proc && proc->loadFile(f, WillStayAlive, 1)) { + proc->check_print_permissions(); + proc->hasAcroForm(); + std::vector> pages = + proc->get_pages(); + for (const std::shared_ptr &p : pages) { + if (p) + (void)p->getRect(); // parses /MediaBox etc. + } + } + } catch (...) { + // qpdf / pdftopdf throw on malformed PDFs; that is expected, not a bug. + } + fclose(f); + } + + restore_stdout(saved_stdout); + return 0; +} diff --git a/projects/cups-filters/oss_fuzz_build.sh b/projects/cups-filters/oss_fuzz_build.sh index 5844851..f6992ea 100755 --- a/projects/cups-filters/oss_fuzz_build.sh +++ b/projects/cups-filters/oss_fuzz_build.sh @@ -1,60 +1,26 @@ #!/bin/bash -eu -# Set fPIE -# export CFLAGS="$CFLAGS -fPIE" -# export CXXFLAGS="$CFLAGS -fPIE" -# export LDFLAGS="$CFLAGS -fPIE" +export CC=${CC:-clang} +export CXX=${CXX:-clang++} +export CFLAGS="$CFLAGS -fPIE" +export CXXFLAGS="$(echo "$CXXFLAGS" | sed 's/-stdlib=libc++//g') -fPIE" -export CC=clang -export CXX=clang++ - -export CFLAGS="-fPIE" -export CXXFLAGS="-fPIE" -export LDFLAGS="-fPIE" - -export CFLAGS="$CFLAGS -fsanitize=$SANITIZER" -export CXXFLAGS="$CXXFLAGS -fsanitize=$SANITIZER" -export LDFLAGS="-fsanitize=$SANITIZER" - -# For regular sanitizers -if [[ $SANITIZER == "coverage" ]]; then - export CFLAGS="" - export CXXFLAGS="" - export LDFLAGS="" -elif [[ $SANITIZER == "undefined" ]]; then - export CFLAGS="$CFLAGS -fno-sanitize=function" - export CXXFLAGS="$CXXFLAGS -fno-sanitize=function" - export LDFLAGS="-fno-sanitize=function" -fi - -# For fuzz introspector -if [[ $SANITIZER == "introspector" ]]; then - export CFLAGS="-O0 -flto -fno-omit-frame-pointer -gline-tables-only -Wno-error=enum-constexpr-conversion -Wno-error=incompatible-function-pointer-types -Wno-error=int-conversion -Wno-error=deprecated-declarations -Wno-error=implicit-function-declaration -Wno-error=implicit-int -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION -g" - export CXXFLAGS="-O0 -flto -fno-omit-frame-pointer -gline-tables-only -Wno-error=enum-constexpr-conversion -Wno-error=incompatible-function-pointer-types -Wno-error=int-conversion -Wno-error=deprecated-declarations -Wno-error=implicit-function-declaration -Wno-error=implicit-int -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION -g" - export LDFLAGS="-flto" -fi +FUZZER_DIR=$SRC/fuzzing/projects/cups-filters # for libtool usage export PATH=$PATH:$SRC/cups-filters -cp $SRC/fuzzing/projects/cups-filters/fuzzer/patch_qpdf_xobject $SRC/cups-filters/filter/pdftopdf/ +cp $FUZZER_DIR/fuzzer/patch_qpdf_xobject $SRC/cups-filters/filter/pdftopdf/ -# Prepare fuzz dir -pushd $SRC/fuzzing/projects/cups-filters/ -# Show fuzzer version -echo "OpenPrinting/fuzzing version: $(git rev-parse HEAD)" -cp -r $SRC/fuzzing/projects/cups-filters/fuzzer/. $SRC/cups-filters/ossfuzz/ -popd +echo "OpenPrinting/fuzzing version: $(git -C $FUZZER_DIR rev-parse HEAD)" # Build cups-filters pushd $SRC/cups-filters - -# Show build version echo "cups-filters version: $(git rev-parse HEAD)" # For multiple definition of `_cups_isalpha', `_cups_islower`, `_cups_toupper` -export LDFLAGS="$LDFLAGS -Wl,--allow-multiple-definition" # rather important without this, the build will fail +export LDFLAGS="${LDFLAGS:-} -Wl,--allow-multiple-definition" -### Temperal fix bug due to libqpdf-dev 9 +### Temporal fix bug due to libqpdf-dev 9 pushd $SRC/cups-filters/filter/pdftopdf/ patch < patch_qpdf_xobject popd @@ -62,18 +28,17 @@ popd ./autogen.sh ./configure --enable-static --disable-shared make # -j$(nproc) -popd -pushd $SRC/cups-filters/ossfuzz/ -# Build fuzzers -make -make ossfuzz +# Build the fuzzer(s) via the fuzzer Makefile (also used for local builds). +cp -r $FUZZER_DIR/fuzzer ossfuzz +make -C ossfuzz +make -C ossfuzz ossfuzz popd # Prepare corpus -pushd $SRC/fuzzing/projects/cups-filters/seeds/ +pushd $FUZZER_DIR/seeds/ for seed_folder in *; do zip -r $seed_folder.zip $seed_folder done cp *.zip $OUT -popd \ No newline at end of file +popd