diff options
author | Jay Berkenbilt <ejb@ql.org> | 2019-06-14 18:34:23 +0200 |
---|---|---|
committer | Jay Berkenbilt <ejb@ql.org> | 2019-06-15 23:24:24 +0200 |
commit | f561a5df325945c896bdec266d2e457a002fef0e (patch) | |
tree | cdebec58663add4ea9f9fd349835e1f6212e2c3c /fuzz | |
parent | cf469d789024cdda41684f1ea48b41829b98c242 (diff) | |
download | qpdf-f561a5df325945c896bdec266d2e457a002fef0e.tar.zst |
Implement fuzzer with good coverage
Diffstat (limited to 'fuzz')
-rw-r--r-- | fuzz/build.mk | 1 | ||||
-rw-r--r-- | fuzz/qpdf_fuzzer.cc | 212 | ||||
-rw-r--r-- | fuzz/standalone_fuzz_target_runner.cc | 5 |
3 files changed, 215 insertions, 3 deletions
diff --git a/fuzz/build.mk b/fuzz/build.mk index 5d7ab903..1ecdac9b 100644 --- a/fuzz/build.mk +++ b/fuzz/build.mk @@ -2,6 +2,7 @@ # https://github.com/google/oss-fuzz/tree/master/projects/qpdf FUZZERS = \ + qpdf_fuzzer \ qpdf_read_memory_fuzzer DEFAULT_FUZZ_RUNNER := standalone_fuzz_target_runner diff --git a/fuzz/qpdf_fuzzer.cc b/fuzz/qpdf_fuzzer.cc new file mode 100644 index 00000000..3fb33109 --- /dev/null +++ b/fuzz/qpdf_fuzzer.cc @@ -0,0 +1,212 @@ +#include <qpdf/QPDF.hh> +#include <qpdf/QPDFWriter.hh> +#include <qpdf/QUtil.hh> +#include <qpdf/BufferInputSource.hh> +#include <qpdf/Buffer.hh> +#include <qpdf/Pl_Discard.hh> +#include <qpdf/QPDFPageDocumentHelper.hh> +#include <qpdf/QPDFPageObjectHelper.hh> +#include <qpdf/QPDFPageLabelDocumentHelper.hh> +#include <qpdf/QPDFOutlineDocumentHelper.hh> +#include <qpdf/QPDFAcroFormDocumentHelper.hh> + +class DiscardContents: public QPDFObjectHandle::ParserCallbacks +{ + public: + virtual ~DiscardContents() {} + virtual void handleObject(QPDFObjectHandle) {} + virtual void handleEOF() {} +}; + +class FuzzHelper +{ + public: + FuzzHelper(unsigned char const* data, size_t size); + void run(); + + private: + PointerHolder<QPDF> getQpdf(); + PointerHolder<QPDFWriter> getWriter(PointerHolder<QPDF>); + void doWrite(PointerHolder<QPDFWriter> w); + void testWrite(); + void testPages(); + void testOutlines(); + void doChecks(); + + Buffer input_buffer; + Pl_Discard discard; +}; + +FuzzHelper::FuzzHelper(unsigned char const* data, size_t size) : + // We do not modify data, so it is safe to remove the const for Buffer + input_buffer(const_cast<unsigned char*>(data), size) +{ +} + +PointerHolder<QPDF> +FuzzHelper::getQpdf() +{ + PointerHolder<InputSource> is = + new BufferInputSource("fuzz input", &this->input_buffer); + PointerHolder<QPDF> qpdf = new QPDF(); + qpdf->processInputSource(is); + return qpdf; +} + +PointerHolder<QPDFWriter> +FuzzHelper::getWriter(PointerHolder<QPDF> qpdf) +{ + PointerHolder<QPDFWriter> w = new QPDFWriter(*qpdf); + w->setOutputPipeline(&this->discard); + w->setDeterministicID(true); + w->setDecodeLevel(qpdf_dl_all); + w->setCompressStreams(false); + return w; +} + +void +FuzzHelper::doWrite(PointerHolder<QPDFWriter> w) +{ + try + { + w->write(); + } + catch (QPDFExc const& e) + { + std::cerr << e.what() << std::endl; + } +} + +void +FuzzHelper::testWrite() +{ + // Write in various ways to exercise QPDFWriter + + PointerHolder<QPDF> q; + PointerHolder<QPDFWriter> w; + + q = getQpdf(); + w = getWriter(q); + doWrite(w); + + q = getQpdf(); + w = getWriter(q); + w->setLinearization(true); + doWrite(w); + + q = getQpdf(); + w = getWriter(q); + w->setObjectStreamMode(qpdf_o_disable); + doWrite(w); + + q = getQpdf(); + w = getWriter(q); + w->setObjectStreamMode(qpdf_o_generate); + doWrite(w); +} + +void +FuzzHelper::testPages() +{ + // Parse all content streams, and exercise some helpers that + // operate on pages. + PointerHolder<QPDF> q = getQpdf(); + QPDFPageDocumentHelper pdh(*q); + QPDFPageLabelDocumentHelper pldh(*q); + QPDFOutlineDocumentHelper odh(*q); + QPDFAcroFormDocumentHelper afdh(*q); + std::vector<QPDFPageObjectHelper> pages = pdh.getAllPages(); + DiscardContents discard_contents; + int pageno = 0; + for (std::vector<QPDFPageObjectHelper>::iterator iter = + pages.begin(); + iter != pages.end(); ++iter) + { + QPDFPageObjectHelper& page(*iter); + ++pageno; + try + { + page.parsePageContents(&discard_contents); + page.getPageImages(); + pldh.getLabelForPage(pageno); + odh.getOutlinesForPage(page.getObjectHandle().getObjGen()); + + std::vector<QPDFAnnotationObjectHelper> annotations = + afdh.getWidgetAnnotationsForPage(page); + for (std::vector<QPDFAnnotationObjectHelper>::iterator annot_iter = + annotations.begin(); + annot_iter != annotations.end(); ++annot_iter) + { + QPDFAnnotationObjectHelper& aoh = *annot_iter; + afdh.getFieldForAnnotation(aoh); + } + } + catch (QPDFExc& e) + { + std::cerr << "page " << pageno << ": " + << e.what() << std::endl; + } + } +} + +void +FuzzHelper::testOutlines() +{ + PointerHolder<QPDF> q = getQpdf(); + std::list<std::list<QPDFOutlineObjectHelper> > queue; + QPDFOutlineDocumentHelper odh(*q); + queue.push_back(odh.getTopLevelOutlines()); + while (! queue.empty()) + { + std::list<QPDFOutlineObjectHelper>& outlines = *(queue.begin()); + for (std::list<QPDFOutlineObjectHelper>::iterator iter = + outlines.begin(); + iter != outlines.end(); ++iter) + { + QPDFOutlineObjectHelper& ol = *iter; + ol.getDestPage(); + queue.push_back(ol.getKids()); + } + queue.pop_front(); + } +} + +void +FuzzHelper::doChecks() +{ + // Get as much coverage as possible in parts of the library that + // might benefit from fuzzing. + testWrite(); + testPages(); + testOutlines(); +} + +void +FuzzHelper::run() +{ + // The goal here is that you should be able to throw anything at + // libqpdf and it will respond without any memory errors and never + // do anything worse than throwing a QPDFExc or + // std::runtime_error. Throwing any other kind of exception, + // segfaulting, or having a memory error (when built with + // appropriate sanitizers) will all cause abnormal exit. + try + { + doChecks(); + } + catch (QPDFExc const& e) + { + std::cerr << "QPDFExc: " << e.what() << std::endl; + } + catch (std::runtime_error const& e) + { + std::cerr << "runtime_error: " << e.what() << std::endl; + } +} + +extern "C" int LLVMFuzzerTestOneInput(unsigned char const* data, size_t size) +{ + FuzzHelper f(data, size); + f.run(); + return 0; +} diff --git a/fuzz/standalone_fuzz_target_runner.cc b/fuzz/standalone_fuzz_target_runner.cc index 7038f188..9881d0fb 100644 --- a/fuzz/standalone_fuzz_target_runner.cc +++ b/fuzz/standalone_fuzz_target_runner.cc @@ -20,8 +20,7 @@ int main(int argc, char **argv) in.seekg(0, in.end); size_t length = in.tellg(); in.seekg (0, in.beg); - std::cout << "Reading " << length << " bytes from " << argv[i] - << std::endl; + std::cout << "checking " << argv[i] << std::endl; // Allocate exactly length bytes so that we reliably catch // buffer overflows. std::vector<char> bytes(length); @@ -30,7 +29,7 @@ int main(int argc, char **argv) LLVMFuzzerTestOneInput( reinterpret_cast<unsigned char const*>(bytes.data()), bytes.size()); - std::cout << "Execution successful" << std::endl; + std::cout << argv[i] << " successful" << std::endl; } return 0; } |