Commit f561a5df325945c896bdec266d2e457a002fef0e

Authored by Jay Berkenbilt
1 parent cf469d78

Implement fuzzer with good coverage

README-maintainer
... ... @@ -19,14 +19,15 @@ Memory checks:
19 19  
20 20 GOOGLE OSS-FUZZ
21 21  
22   -* https://github.com/google/oss-fuzz/tree/master/projects/qpdf
  22 +* qpdf project: https://github.com/google/oss-fuzz/tree/master/projects/qpdf
  23 +
23 24 * To test locally, see https://github.com/google/oss-fuzz/tree/master/docs/,
24   - especially new_project_guide.md
  25 + especially new_project_guide.md. Summary:
25 26  
26   -Clone the oss-fuzz project. From the root directory of the repository:
  27 + Clone the oss-fuzz project. From the root directory of the repository:
27 28  
28 29 Add `-e GITHUB_FORK=fork -e GITHUB_BRANCH=branch` to build_fuzzers
29   - to work off a fork/branch rather than qpdf/master.
  30 + from a qpdf fork/branch rather than qpdf/master.
30 31  
31 32 python infra/helper.py build_image --pull qpdf
32 33 python infra/helper.py build_fuzzers qpdf
... ... @@ -34,6 +35,24 @@ Clone the oss-fuzz project. From the root directory of the repository:
34 35 python infra/helper.py build_fuzzers --sanitizer coverage qpdf
35 36 python infra/helper.py coverage qpdf
36 37  
  38 + The fuzzer is in build/out/qpdf. It can be run with a directory as
  39 + an argument to run against files in a directory. You can use
  40 +
  41 + qpdf_fuzzer -merge=1 cur new >& /dev/null&
  42 +
  43 + to add any files from new into cur if they increase coverage. You
  44 + need to do this with the coverage build (the one with
  45 + --sanitizer coverage)
  46 +
  47 +* General documentation: http://libfuzzer.info
  48 +
  49 +* Build status: https://oss-fuzz-build-logs.storage.googleapis.com/index.html
  50 +
  51 +* Project status: https://oss-fuzz.com/ (private -- log in with Google account)
  52 +
  53 +* Latest corpus:
  54 + gs://qpdf-backup.clusterfuzz-external.appspot.com/corpus/libFuzzer/qpdf_fuzzer/latest.zip
  55 +
37 56 CODING RULES
38 57  
39 58 * Avoid atoi. Use QUtil::string_to_int instead. It does
... ...
fuzz/build.mk
... ... @@ -2,6 +2,7 @@
2 2 # https://github.com/google/oss-fuzz/tree/master/projects/qpdf
3 3  
4 4 FUZZERS = \
  5 + qpdf_fuzzer \
5 6 qpdf_read_memory_fuzzer
6 7  
7 8 DEFAULT_FUZZ_RUNNER := standalone_fuzz_target_runner
... ...
fuzz/qpdf_fuzzer.cc 0 → 100644
  1 +#include <qpdf/QPDF.hh>
  2 +#include <qpdf/QPDFWriter.hh>
  3 +#include <qpdf/QUtil.hh>
  4 +#include <qpdf/BufferInputSource.hh>
  5 +#include <qpdf/Buffer.hh>
  6 +#include <qpdf/Pl_Discard.hh>
  7 +#include <qpdf/QPDFPageDocumentHelper.hh>
  8 +#include <qpdf/QPDFPageObjectHelper.hh>
  9 +#include <qpdf/QPDFPageLabelDocumentHelper.hh>
  10 +#include <qpdf/QPDFOutlineDocumentHelper.hh>
  11 +#include <qpdf/QPDFAcroFormDocumentHelper.hh>
  12 +
  13 +class DiscardContents: public QPDFObjectHandle::ParserCallbacks
  14 +{
  15 + public:
  16 + virtual ~DiscardContents() {}
  17 + virtual void handleObject(QPDFObjectHandle) {}
  18 + virtual void handleEOF() {}
  19 +};
  20 +
  21 +class FuzzHelper
  22 +{
  23 + public:
  24 + FuzzHelper(unsigned char const* data, size_t size);
  25 + void run();
  26 +
  27 + private:
  28 + PointerHolder<QPDF> getQpdf();
  29 + PointerHolder<QPDFWriter> getWriter(PointerHolder<QPDF>);
  30 + void doWrite(PointerHolder<QPDFWriter> w);
  31 + void testWrite();
  32 + void testPages();
  33 + void testOutlines();
  34 + void doChecks();
  35 +
  36 + Buffer input_buffer;
  37 + Pl_Discard discard;
  38 +};
  39 +
  40 +FuzzHelper::FuzzHelper(unsigned char const* data, size_t size) :
  41 + // We do not modify data, so it is safe to remove the const for Buffer
  42 + input_buffer(const_cast<unsigned char*>(data), size)
  43 +{
  44 +}
  45 +
  46 +PointerHolder<QPDF>
  47 +FuzzHelper::getQpdf()
  48 +{
  49 + PointerHolder<InputSource> is =
  50 + new BufferInputSource("fuzz input", &this->input_buffer);
  51 + PointerHolder<QPDF> qpdf = new QPDF();
  52 + qpdf->processInputSource(is);
  53 + return qpdf;
  54 +}
  55 +
  56 +PointerHolder<QPDFWriter>
  57 +FuzzHelper::getWriter(PointerHolder<QPDF> qpdf)
  58 +{
  59 + PointerHolder<QPDFWriter> w = new QPDFWriter(*qpdf);
  60 + w->setOutputPipeline(&this->discard);
  61 + w->setDeterministicID(true);
  62 + w->setDecodeLevel(qpdf_dl_all);
  63 + w->setCompressStreams(false);
  64 + return w;
  65 +}
  66 +
  67 +void
  68 +FuzzHelper::doWrite(PointerHolder<QPDFWriter> w)
  69 +{
  70 + try
  71 + {
  72 + w->write();
  73 + }
  74 + catch (QPDFExc const& e)
  75 + {
  76 + std::cerr << e.what() << std::endl;
  77 + }
  78 +}
  79 +
  80 +void
  81 +FuzzHelper::testWrite()
  82 +{
  83 + // Write in various ways to exercise QPDFWriter
  84 +
  85 + PointerHolder<QPDF> q;
  86 + PointerHolder<QPDFWriter> w;
  87 +
  88 + q = getQpdf();
  89 + w = getWriter(q);
  90 + doWrite(w);
  91 +
  92 + q = getQpdf();
  93 + w = getWriter(q);
  94 + w->setLinearization(true);
  95 + doWrite(w);
  96 +
  97 + q = getQpdf();
  98 + w = getWriter(q);
  99 + w->setObjectStreamMode(qpdf_o_disable);
  100 + doWrite(w);
  101 +
  102 + q = getQpdf();
  103 + w = getWriter(q);
  104 + w->setObjectStreamMode(qpdf_o_generate);
  105 + doWrite(w);
  106 +}
  107 +
  108 +void
  109 +FuzzHelper::testPages()
  110 +{
  111 + // Parse all content streams, and exercise some helpers that
  112 + // operate on pages.
  113 + PointerHolder<QPDF> q = getQpdf();
  114 + QPDFPageDocumentHelper pdh(*q);
  115 + QPDFPageLabelDocumentHelper pldh(*q);
  116 + QPDFOutlineDocumentHelper odh(*q);
  117 + QPDFAcroFormDocumentHelper afdh(*q);
  118 + std::vector<QPDFPageObjectHelper> pages = pdh.getAllPages();
  119 + DiscardContents discard_contents;
  120 + int pageno = 0;
  121 + for (std::vector<QPDFPageObjectHelper>::iterator iter =
  122 + pages.begin();
  123 + iter != pages.end(); ++iter)
  124 + {
  125 + QPDFPageObjectHelper& page(*iter);
  126 + ++pageno;
  127 + try
  128 + {
  129 + page.parsePageContents(&discard_contents);
  130 + page.getPageImages();
  131 + pldh.getLabelForPage(pageno);
  132 + odh.getOutlinesForPage(page.getObjectHandle().getObjGen());
  133 +
  134 + std::vector<QPDFAnnotationObjectHelper> annotations =
  135 + afdh.getWidgetAnnotationsForPage(page);
  136 + for (std::vector<QPDFAnnotationObjectHelper>::iterator annot_iter =
  137 + annotations.begin();
  138 + annot_iter != annotations.end(); ++annot_iter)
  139 + {
  140 + QPDFAnnotationObjectHelper& aoh = *annot_iter;
  141 + afdh.getFieldForAnnotation(aoh);
  142 + }
  143 + }
  144 + catch (QPDFExc& e)
  145 + {
  146 + std::cerr << "page " << pageno << ": "
  147 + << e.what() << std::endl;
  148 + }
  149 + }
  150 +}
  151 +
  152 +void
  153 +FuzzHelper::testOutlines()
  154 +{
  155 + PointerHolder<QPDF> q = getQpdf();
  156 + std::list<std::list<QPDFOutlineObjectHelper> > queue;
  157 + QPDFOutlineDocumentHelper odh(*q);
  158 + queue.push_back(odh.getTopLevelOutlines());
  159 + while (! queue.empty())
  160 + {
  161 + std::list<QPDFOutlineObjectHelper>& outlines = *(queue.begin());
  162 + for (std::list<QPDFOutlineObjectHelper>::iterator iter =
  163 + outlines.begin();
  164 + iter != outlines.end(); ++iter)
  165 + {
  166 + QPDFOutlineObjectHelper& ol = *iter;
  167 + ol.getDestPage();
  168 + queue.push_back(ol.getKids());
  169 + }
  170 + queue.pop_front();
  171 + }
  172 +}
  173 +
  174 +void
  175 +FuzzHelper::doChecks()
  176 +{
  177 + // Get as much coverage as possible in parts of the library that
  178 + // might benefit from fuzzing.
  179 + testWrite();
  180 + testPages();
  181 + testOutlines();
  182 +}
  183 +
  184 +void
  185 +FuzzHelper::run()
  186 +{
  187 + // The goal here is that you should be able to throw anything at
  188 + // libqpdf and it will respond without any memory errors and never
  189 + // do anything worse than throwing a QPDFExc or
  190 + // std::runtime_error. Throwing any other kind of exception,
  191 + // segfaulting, or having a memory error (when built with
  192 + // appropriate sanitizers) will all cause abnormal exit.
  193 + try
  194 + {
  195 + doChecks();
  196 + }
  197 + catch (QPDFExc const& e)
  198 + {
  199 + std::cerr << "QPDFExc: " << e.what() << std::endl;
  200 + }
  201 + catch (std::runtime_error const& e)
  202 + {
  203 + std::cerr << "runtime_error: " << e.what() << std::endl;
  204 + }
  205 +}
  206 +
  207 +extern "C" int LLVMFuzzerTestOneInput(unsigned char const* data, size_t size)
  208 +{
  209 + FuzzHelper f(data, size);
  210 + f.run();
  211 + return 0;
  212 +}
... ...
fuzz/standalone_fuzz_target_runner.cc
... ... @@ -20,8 +20,7 @@ int main(int argc, char **argv)
20 20 in.seekg(0, in.end);
21 21 size_t length = in.tellg();
22 22 in.seekg (0, in.beg);
23   - std::cout << "Reading " << length << " bytes from " << argv[i]
24   - << std::endl;
  23 + std::cout << "checking " << argv[i] << std::endl;
25 24 // Allocate exactly length bytes so that we reliably catch
26 25 // buffer overflows.
27 26 std::vector<char> bytes(length);
... ... @@ -30,7 +29,7 @@ int main(int argc, char **argv)
30 29 LLVMFuzzerTestOneInput(
31 30 reinterpret_cast<unsigned char const*>(bytes.data()),
32 31 bytes.size());
33   - std::cout << "Execution successful" << std::endl;
  32 + std::cout << argv[i] << " successful" << std::endl;
34 33 }
35 34 return 0;
36 35 }
... ...