Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 59 additions & 32 deletions projects/cups-filters/fuzzer/Makefile
Original file line number Diff line number Diff line change
@@ -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)
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
83 changes: 0 additions & 83 deletions projects/cups-filters/fuzzer/fuzz_pdf.c

This file was deleted.

69 changes: 69 additions & 0 deletions projects/cups-filters/fuzzer/fuzz_pdf.cc
Original file line number Diff line number Diff line change
@@ -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 <cstdio>
#include <cstdint>
#include <cstddef>
#include <memory>
#include <vector>
#include <unistd.h>
#include <fcntl.h>

// 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<uint8_t *>(Data), Size, "rb");
if (f) {
std::unique_ptr<PDFTOPDF_Processor> 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<std::shared_ptr<PDFTOPDF_PageHandle>> pages =
proc->get_pages();
for (const std::shared_ptr<PDFTOPDF_PageHandle> &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;
}
65 changes: 15 additions & 50 deletions projects/cups-filters/oss_fuzz_build.sh
Original file line number Diff line number Diff line change
@@ -1,79 +1,44 @@
#!/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

./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
popd