Commit d8d73679e71942c4d53e6f8826aa5b51eaa57137
1 parent
c1377176
Split qpdf_fuzzer into six separate fuzzers
Showing
13 changed files
with
761 additions
and
77 deletions
fuzz/CMakeLists.txt
| ... | ... | @@ -3,6 +3,11 @@ |
| 3 | 3 | |
| 4 | 4 | set(FUZZERS |
| 5 | 5 | qpdf_fuzzer |
| 6 | + qpdf_crypt_fuzzer | |
| 7 | + qpdf_crypt_insecure_fuzzer | |
| 8 | + qpdf_lin_fuzzer | |
| 9 | + qpdf_pages_fuzzer | |
| 10 | + qpdf_outlines_fuzzer | |
| 6 | 11 | ascii85_fuzzer |
| 7 | 12 | dct_fuzzer |
| 8 | 13 | flate_fuzzer |
| ... | ... | @@ -174,7 +179,7 @@ add_test( |
| 174 | 179 | if(OSS_FUZZ) |
| 175 | 180 | list(APPEND SEED_CORPUS_ZIPS) |
| 176 | 181 | foreach(F ${FUZZERS}) |
| 177 | - if(F STREQUAL qpdf_fuzzer) | |
| 182 | + if((F STRGREATER qpdf_) AND (F STRLESS qpdg)) | |
| 178 | 183 | set(SEED_DIR ${CORPUS_DIR}) |
| 179 | 184 | else() |
| 180 | 185 | set(SEED_DIR ${CMAKE_CURRENT_SOURCE_DIR}/${F}_seed_corpus) | ... | ... |
fuzz/qpdf_crypt_fuzzer.cc
0 → 100644
| 1 | +#include <qpdf/Buffer.hh> | |
| 2 | +#include <qpdf/BufferInputSource.hh> | |
| 3 | +#include <qpdf/Pl_DCT.hh> | |
| 4 | +#include <qpdf/Pl_Discard.hh> | |
| 5 | +#include <qpdf/Pl_Flate.hh> | |
| 6 | +#include <qpdf/Pl_PNGFilter.hh> | |
| 7 | +#include <qpdf/Pl_TIFFPredictor.hh> | |
| 8 | +#include <qpdf/QPDF.hh> | |
| 9 | +#include <qpdf/QPDFPageObjectHelper.hh> | |
| 10 | +#include <qpdf/QPDFWriter.hh> | |
| 11 | +#include <qpdf/QUtil.hh> | |
| 12 | + | |
| 13 | +#include <cstdlib> | |
| 14 | + | |
| 15 | +class DiscardContents: public QPDFObjectHandle::ParserCallbacks | |
| 16 | +{ | |
| 17 | + public: | |
| 18 | + ~DiscardContents() override = default; | |
| 19 | + void | |
| 20 | + handleObject(QPDFObjectHandle) override | |
| 21 | + { | |
| 22 | + } | |
| 23 | + void | |
| 24 | + handleEOF() override | |
| 25 | + { | |
| 26 | + } | |
| 27 | +}; | |
| 28 | + | |
| 29 | +class FuzzHelper | |
| 30 | +{ | |
| 31 | + public: | |
| 32 | + FuzzHelper(unsigned char const* data, size_t size); | |
| 33 | + void run(); | |
| 34 | + | |
| 35 | + private: | |
| 36 | + std::shared_ptr<QPDF> getQpdf(); | |
| 37 | + std::shared_ptr<QPDFWriter> getWriter(std::shared_ptr<QPDF>); | |
| 38 | + void doWrite(std::shared_ptr<QPDFWriter> w); | |
| 39 | + void testWrite(); | |
| 40 | + void doChecks(); | |
| 41 | + | |
| 42 | + Buffer input_buffer; | |
| 43 | + Pl_Discard discard; | |
| 44 | +}; | |
| 45 | + | |
| 46 | +FuzzHelper::FuzzHelper(unsigned char const* data, size_t size) : | |
| 47 | + // We do not modify data, so it is safe to remove the const for Buffer | |
| 48 | + input_buffer(const_cast<unsigned char*>(data), size) | |
| 49 | +{ | |
| 50 | +} | |
| 51 | + | |
| 52 | +std::shared_ptr<QPDF> | |
| 53 | +FuzzHelper::getQpdf() | |
| 54 | +{ | |
| 55 | + auto is = | |
| 56 | + std::shared_ptr<InputSource>(new BufferInputSource("fuzz input", &this->input_buffer)); | |
| 57 | + auto qpdf = QPDF::create(); | |
| 58 | + qpdf->setMaxWarnings(200); | |
| 59 | + qpdf->processInputSource(is); | |
| 60 | + return qpdf; | |
| 61 | +} | |
| 62 | + | |
| 63 | +std::shared_ptr<QPDFWriter> | |
| 64 | +FuzzHelper::getWriter(std::shared_ptr<QPDF> qpdf) | |
| 65 | +{ | |
| 66 | + auto w = std::make_shared<QPDFWriter>(*qpdf); | |
| 67 | + w->setOutputPipeline(&this->discard); | |
| 68 | + w->setDecodeLevel(qpdf_dl_all); | |
| 69 | + return w; | |
| 70 | +} | |
| 71 | + | |
| 72 | +void | |
| 73 | +FuzzHelper::doWrite(std::shared_ptr<QPDFWriter> w) | |
| 74 | +{ | |
| 75 | + try { | |
| 76 | + w->write(); | |
| 77 | + } catch (QPDFExc const& e) { | |
| 78 | + std::cerr << e.what() << std::endl; | |
| 79 | + } catch (std::runtime_error const& e) { | |
| 80 | + std::cerr << e.what() << std::endl; | |
| 81 | + } | |
| 82 | +} | |
| 83 | + | |
| 84 | +void | |
| 85 | +FuzzHelper::testWrite() | |
| 86 | +{ | |
| 87 | + // Write in various ways to exercise QPDFWriter | |
| 88 | + | |
| 89 | + std::shared_ptr<QPDF> q; | |
| 90 | + std::shared_ptr<QPDFWriter> w; | |
| 91 | + | |
| 92 | + q = getQpdf(); | |
| 93 | + w = getWriter(q); | |
| 94 | + w->setStaticID(true); | |
| 95 | + w->setLinearization(true); | |
| 96 | + w->setR6EncryptionParameters("u", "o", true, true, true, true, true, true, qpdf_r3p_full, true); | |
| 97 | + doWrite(w); | |
| 98 | +} | |
| 99 | + | |
| 100 | +void | |
| 101 | +FuzzHelper::doChecks() | |
| 102 | +{ | |
| 103 | + // Limit the memory used to decompress JPEG files during fuzzing. Excessive memory use during | |
| 104 | + // fuzzing is due to corrupt JPEG data which sometimes cannot be detected before | |
| 105 | + // jpeg_start_decompress is called. During normal use of qpdf very large JPEGs can occasionally | |
| 106 | + // occur legitimately and therefore must be allowed during normal operations. | |
| 107 | + Pl_DCT::setMemoryLimit(100'000'000); | |
| 108 | + Pl_DCT::setScanLimit(50); | |
| 109 | + | |
| 110 | + Pl_PNGFilter::setMemoryLimit(1'000'000); | |
| 111 | + Pl_TIFFPredictor::setMemoryLimit(1'000'000); | |
| 112 | + Pl_Flate::setMemoryLimit(1'000'000); | |
| 113 | + | |
| 114 | + // Do not decompress corrupt data. This may cause extended runtime within jpeglib without | |
| 115 | + // exercising additional code paths in qpdf, and potentially causing counterproductive timeouts. | |
| 116 | + Pl_DCT::setThrowOnCorruptData(true); | |
| 117 | + | |
| 118 | + // Get as much coverage as possible in parts of the library that | |
| 119 | + // might benefit from fuzzing. | |
| 120 | + std::cerr << "\ninfo: starting testWrite\n"; | |
| 121 | + testWrite(); | |
| 122 | +} | |
| 123 | + | |
| 124 | +void | |
| 125 | +FuzzHelper::run() | |
| 126 | +{ | |
| 127 | + // The goal here is that you should be able to throw anything at | |
| 128 | + // libqpdf and it will respond without any memory errors and never | |
| 129 | + // do anything worse than throwing a QPDFExc or | |
| 130 | + // std::runtime_error. Throwing any other kind of exception, | |
| 131 | + // segfaulting, or having a memory error (when built with | |
| 132 | + // appropriate sanitizers) will all cause abnormal exit. | |
| 133 | + try { | |
| 134 | + doChecks(); | |
| 135 | + } catch (QPDFExc const& e) { | |
| 136 | + std::cerr << "QPDFExc: " << e.what() << std::endl; | |
| 137 | + } catch (std::runtime_error const& e) { | |
| 138 | + std::cerr << "runtime_error: " << e.what() << std::endl; | |
| 139 | + } | |
| 140 | +} | |
| 141 | + | |
| 142 | +extern "C" int | |
| 143 | +LLVMFuzzerTestOneInput(unsigned char const* data, size_t size) | |
| 144 | +{ | |
| 145 | +#ifndef _WIN32 | |
| 146 | + // Used by jpeg library to work around false positives in memory | |
| 147 | + // sanitizer. | |
| 148 | + setenv("JSIMD_FORCENONE", "1", 1); | |
| 149 | +#endif | |
| 150 | + FuzzHelper f(data, size); | |
| 151 | + f.run(); | |
| 152 | + return 0; | |
| 153 | +} | ... | ... |
fuzz/qpdf_crypt_fuzzer.options
0 → 100644
fuzz/qpdf_crypt_insecure_fuzzer.cc
0 → 100644
| 1 | +#include <qpdf/Buffer.hh> | |
| 2 | +#include <qpdf/BufferInputSource.hh> | |
| 3 | +#include <qpdf/Pl_DCT.hh> | |
| 4 | +#include <qpdf/Pl_Discard.hh> | |
| 5 | +#include <qpdf/Pl_Flate.hh> | |
| 6 | +#include <qpdf/Pl_PNGFilter.hh> | |
| 7 | +#include <qpdf/Pl_TIFFPredictor.hh> | |
| 8 | +#include <qpdf/QPDF.hh> | |
| 9 | +#include <qpdf/QPDFPageObjectHelper.hh> | |
| 10 | +#include <qpdf/QPDFWriter.hh> | |
| 11 | +#include <qpdf/QUtil.hh> | |
| 12 | +#include <cstdlib> | |
| 13 | + | |
| 14 | +class DiscardContents: public QPDFObjectHandle::ParserCallbacks | |
| 15 | +{ | |
| 16 | + public: | |
| 17 | + ~DiscardContents() override = default; | |
| 18 | + void | |
| 19 | + handleObject(QPDFObjectHandle) override | |
| 20 | + { | |
| 21 | + } | |
| 22 | + void | |
| 23 | + handleEOF() override | |
| 24 | + { | |
| 25 | + } | |
| 26 | +}; | |
| 27 | + | |
| 28 | +class FuzzHelper | |
| 29 | +{ | |
| 30 | + public: | |
| 31 | + FuzzHelper(unsigned char const* data, size_t size); | |
| 32 | + void run(); | |
| 33 | + | |
| 34 | + private: | |
| 35 | + std::shared_ptr<QPDF> getQpdf(); | |
| 36 | + std::shared_ptr<QPDFWriter> getWriter(std::shared_ptr<QPDF>); | |
| 37 | + void doWrite(std::shared_ptr<QPDFWriter> w); | |
| 38 | + void testWrite(); | |
| 39 | + void doChecks(); | |
| 40 | + | |
| 41 | + Buffer input_buffer; | |
| 42 | + Pl_Discard discard; | |
| 43 | +}; | |
| 44 | + | |
| 45 | +FuzzHelper::FuzzHelper(unsigned char const* data, size_t size) : | |
| 46 | + // We do not modify data, so it is safe to remove the const for Buffer | |
| 47 | + input_buffer(const_cast<unsigned char*>(data), size) | |
| 48 | +{ | |
| 49 | +} | |
| 50 | + | |
| 51 | +std::shared_ptr<QPDF> | |
| 52 | +FuzzHelper::getQpdf() | |
| 53 | +{ | |
| 54 | + auto is = | |
| 55 | + std::shared_ptr<InputSource>(new BufferInputSource("fuzz input", &this->input_buffer)); | |
| 56 | + auto qpdf = QPDF::create(); | |
| 57 | + qpdf->setMaxWarnings(200); | |
| 58 | + qpdf->processInputSource(is); | |
| 59 | + return qpdf; | |
| 60 | +} | |
| 61 | + | |
| 62 | +std::shared_ptr<QPDFWriter> | |
| 63 | +FuzzHelper::getWriter(std::shared_ptr<QPDF> qpdf) | |
| 64 | +{ | |
| 65 | + auto w = std::make_shared<QPDFWriter>(*qpdf); | |
| 66 | + w->setOutputPipeline(&this->discard); | |
| 67 | + w->setDecodeLevel(qpdf_dl_all); | |
| 68 | + return w; | |
| 69 | +} | |
| 70 | + | |
| 71 | +void | |
| 72 | +FuzzHelper::doWrite(std::shared_ptr<QPDFWriter> w) | |
| 73 | +{ | |
| 74 | + try { | |
| 75 | + w->write(); | |
| 76 | + } catch (QPDFExc const& e) { | |
| 77 | + std::cerr << e.what() << std::endl; | |
| 78 | + } catch (std::runtime_error const& e) { | |
| 79 | + std::cerr << e.what() << std::endl; | |
| 80 | + } | |
| 81 | +} | |
| 82 | + | |
| 83 | +void | |
| 84 | +FuzzHelper::testWrite() | |
| 85 | +{ | |
| 86 | + // Write in various ways to exercise QPDFWriter | |
| 87 | + | |
| 88 | + std::shared_ptr<QPDF> q; | |
| 89 | + std::shared_ptr<QPDFWriter> w; | |
| 90 | + | |
| 91 | + q = getQpdf(); | |
| 92 | + w = getWriter(q); | |
| 93 | + w->setStaticID(true); | |
| 94 | + w->setObjectStreamMode(qpdf_o_disable); | |
| 95 | + w->setR3EncryptionParametersInsecure( | |
| 96 | + "u", "o", true, true, true, true, true, true, qpdf_r3p_full); | |
| 97 | + doWrite(w); | |
| 98 | +} | |
| 99 | + | |
| 100 | +void | |
| 101 | +FuzzHelper::doChecks() | |
| 102 | +{ | |
| 103 | + // Limit the memory used to decompress JPEG files during fuzzing. Excessive memory use during | |
| 104 | + // fuzzing is due to corrupt JPEG data which sometimes cannot be detected before | |
| 105 | + // jpeg_start_decompress is called. During normal use of qpdf very large JPEGs can occasionally | |
| 106 | + // occur legitimately and therefore must be allowed during normal operations. | |
| 107 | + Pl_DCT::setMemoryLimit(100'000'000); | |
| 108 | + Pl_DCT::setScanLimit(50); | |
| 109 | + | |
| 110 | + Pl_PNGFilter::setMemoryLimit(1'000'000); | |
| 111 | + Pl_TIFFPredictor::setMemoryLimit(1'000'000); | |
| 112 | + Pl_Flate::setMemoryLimit(1'000'000); | |
| 113 | + | |
| 114 | + // Do not decompress corrupt data. This may cause extended runtime within jpeglib without | |
| 115 | + // exercising additional code paths in qpdf, and potentially causing counterproductive timeouts. | |
| 116 | + Pl_DCT::setThrowOnCorruptData(true); | |
| 117 | + | |
| 118 | + // Get as much coverage as possible in parts of the library that | |
| 119 | + // might benefit from fuzzing. | |
| 120 | + std::cerr << "\ninfo: starting testWrite\n"; | |
| 121 | + testWrite(); | |
| 122 | +} | |
| 123 | + | |
| 124 | +void | |
| 125 | +FuzzHelper::run() | |
| 126 | +{ | |
| 127 | + // The goal here is that you should be able to throw anything at | |
| 128 | + // libqpdf and it will respond without any memory errors and never | |
| 129 | + // do anything worse than throwing a QPDFExc or | |
| 130 | + // std::runtime_error. Throwing any other kind of exception, | |
| 131 | + // segfaulting, or having a memory error (when built with | |
| 132 | + // appropriate sanitizers) will all cause abnormal exit. | |
| 133 | + try { | |
| 134 | + doChecks(); | |
| 135 | + } catch (QPDFExc const& e) { | |
| 136 | + std::cerr << "QPDFExc: " << e.what() << std::endl; | |
| 137 | + } catch (std::runtime_error const& e) { | |
| 138 | + std::cerr << "runtime_error: " << e.what() << std::endl; | |
| 139 | + } | |
| 140 | +} | |
| 141 | + | |
| 142 | +extern "C" int | |
| 143 | +LLVMFuzzerTestOneInput(unsigned char const* data, size_t size) | |
| 144 | +{ | |
| 145 | +#ifndef _WIN32 | |
| 146 | + // Used by jpeg library to work around false positives in memory | |
| 147 | + // sanitizer. | |
| 148 | + setenv("JSIMD_FORCENONE", "1", 1); | |
| 149 | +#endif | |
| 150 | + FuzzHelper f(data, size); | |
| 151 | + f.run(); | |
| 152 | + return 0; | |
| 153 | +} | ... | ... |
fuzz/qpdf_crypt_insecure_fuzzer.options
0 → 100644
fuzz/qpdf_fuzzer.cc
| ... | ... | @@ -6,10 +6,6 @@ |
| 6 | 6 | #include <qpdf/Pl_PNGFilter.hh> |
| 7 | 7 | #include <qpdf/Pl_TIFFPredictor.hh> |
| 8 | 8 | #include <qpdf/QPDF.hh> |
| 9 | -#include <qpdf/QPDFAcroFormDocumentHelper.hh> | |
| 10 | -#include <qpdf/QPDFOutlineDocumentHelper.hh> | |
| 11 | -#include <qpdf/QPDFPageDocumentHelper.hh> | |
| 12 | -#include <qpdf/QPDFPageLabelDocumentHelper.hh> | |
| 13 | 9 | #include <qpdf/QPDFPageObjectHelper.hh> |
| 14 | 10 | #include <qpdf/QPDFWriter.hh> |
| 15 | 11 | #include <qpdf/QUtil.hh> |
| ... | ... | @@ -40,8 +36,6 @@ class FuzzHelper |
| 40 | 36 | std::shared_ptr<QPDFWriter> getWriter(std::shared_ptr<QPDF>); |
| 41 | 37 | void doWrite(std::shared_ptr<QPDFWriter> w); |
| 42 | 38 | void testWrite(); |
| 43 | - void testPages(); | |
| 44 | - void testOutlines(); | |
| 45 | 39 | void doChecks(); |
| 46 | 40 | |
| 47 | 41 | Buffer input_buffer; |
| ... | ... | @@ -106,71 +100,6 @@ FuzzHelper::testWrite() |
| 106 | 100 | w->setLinearization(true); |
| 107 | 101 | w->setR6EncryptionParameters("u", "o", true, true, true, true, true, true, qpdf_r3p_full, true); |
| 108 | 102 | doWrite(w); |
| 109 | - | |
| 110 | - q = getQpdf(); | |
| 111 | - w = getWriter(q); | |
| 112 | - w->setStaticID(true); | |
| 113 | - w->setObjectStreamMode(qpdf_o_disable); | |
| 114 | - w->setR3EncryptionParametersInsecure( | |
| 115 | - "u", "o", true, true, true, true, true, true, qpdf_r3p_full); | |
| 116 | - doWrite(w); | |
| 117 | - | |
| 118 | - q = getQpdf(); | |
| 119 | - w = getWriter(q); | |
| 120 | - w->setDeterministicID(true); | |
| 121 | - w->setObjectStreamMode(qpdf_o_generate); | |
| 122 | - w->setLinearization(true); | |
| 123 | - doWrite(w); | |
| 124 | -} | |
| 125 | - | |
| 126 | -void | |
| 127 | -FuzzHelper::testPages() | |
| 128 | -{ | |
| 129 | - // Parse all content streams, and exercise some helpers that | |
| 130 | - // operate on pages. | |
| 131 | - std::shared_ptr<QPDF> q = getQpdf(); | |
| 132 | - QPDFPageDocumentHelper pdh(*q); | |
| 133 | - QPDFPageLabelDocumentHelper pldh(*q); | |
| 134 | - QPDFOutlineDocumentHelper odh(*q); | |
| 135 | - QPDFAcroFormDocumentHelper afdh(*q); | |
| 136 | - afdh.generateAppearancesIfNeeded(); | |
| 137 | - pdh.flattenAnnotations(); | |
| 138 | - DiscardContents discard_contents; | |
| 139 | - int pageno = 0; | |
| 140 | - for (auto& page: pdh.getAllPages()) { | |
| 141 | - ++pageno; | |
| 142 | - try { | |
| 143 | - page.coalesceContentStreams(); | |
| 144 | - page.parseContents(&discard_contents); | |
| 145 | - page.getImages(); | |
| 146 | - pldh.getLabelForPage(pageno); | |
| 147 | - QPDFObjectHandle page_obj(page.getObjectHandle()); | |
| 148 | - page_obj.getJSON(JSON::LATEST, true).unparse(); | |
| 149 | - odh.getOutlinesForPage(page_obj.getObjGen()); | |
| 150 | - | |
| 151 | - for (auto& aoh: afdh.getWidgetAnnotationsForPage(page)) { | |
| 152 | - afdh.getFieldForAnnotation(aoh); | |
| 153 | - } | |
| 154 | - } catch (QPDFExc& e) { | |
| 155 | - std::cerr << "page " << pageno << ": " << e.what() << std::endl; | |
| 156 | - } | |
| 157 | - } | |
| 158 | -} | |
| 159 | - | |
| 160 | -void | |
| 161 | -FuzzHelper::testOutlines() | |
| 162 | -{ | |
| 163 | - std::shared_ptr<QPDF> q = getQpdf(); | |
| 164 | - std::list<std::vector<QPDFOutlineObjectHelper>> queue; | |
| 165 | - QPDFOutlineDocumentHelper odh(*q); | |
| 166 | - queue.push_back(odh.getTopLevelOutlines()); | |
| 167 | - while (!queue.empty()) { | |
| 168 | - for (auto& ol: *(queue.begin())) { | |
| 169 | - ol.getDestPage(); | |
| 170 | - queue.push_back(ol.getKids()); | |
| 171 | - } | |
| 172 | - queue.pop_front(); | |
| 173 | - } | |
| 174 | 103 | } |
| 175 | 104 | |
| 176 | 105 | void |
| ... | ... | @@ -195,10 +124,6 @@ FuzzHelper::doChecks() |
| 195 | 124 | // might benefit from fuzzing. |
| 196 | 125 | std::cerr << "\ninfo: starting testWrite\n"; |
| 197 | 126 | testWrite(); |
| 198 | - std::cerr << "\ninfo: starting testPages\n"; | |
| 199 | - testPages(); | |
| 200 | - std::cerr << "\ninfo: starting testOutlines\n"; | |
| 201 | - testOutlines(); | |
| 202 | 127 | } |
| 203 | 128 | |
| 204 | 129 | void | ... | ... |
fuzz/qpdf_lin_fuzzer.cc
0 → 100644
| 1 | +#include <qpdf/Buffer.hh> | |
| 2 | +#include <qpdf/BufferInputSource.hh> | |
| 3 | +#include <qpdf/Pl_DCT.hh> | |
| 4 | +#include <qpdf/Pl_Discard.hh> | |
| 5 | +#include <qpdf/Pl_Flate.hh> | |
| 6 | +#include <qpdf/Pl_PNGFilter.hh> | |
| 7 | +#include <qpdf/Pl_TIFFPredictor.hh> | |
| 8 | +#include <qpdf/QPDF.hh> | |
| 9 | +#include <qpdf/QPDFPageObjectHelper.hh> | |
| 10 | +#include <qpdf/QPDFWriter.hh> | |
| 11 | +#include <qpdf/QUtil.hh> | |
| 12 | +#include <cstdlib> | |
| 13 | + | |
| 14 | +class DiscardContents: public QPDFObjectHandle::ParserCallbacks | |
| 15 | +{ | |
| 16 | + public: | |
| 17 | + ~DiscardContents() override = default; | |
| 18 | + void | |
| 19 | + handleObject(QPDFObjectHandle) override | |
| 20 | + { | |
| 21 | + } | |
| 22 | + void | |
| 23 | + handleEOF() override | |
| 24 | + { | |
| 25 | + } | |
| 26 | +}; | |
| 27 | + | |
| 28 | +class FuzzHelper | |
| 29 | +{ | |
| 30 | + public: | |
| 31 | + FuzzHelper(unsigned char const* data, size_t size); | |
| 32 | + void run(); | |
| 33 | + | |
| 34 | + private: | |
| 35 | + std::shared_ptr<QPDF> getQpdf(); | |
| 36 | + std::shared_ptr<QPDFWriter> getWriter(std::shared_ptr<QPDF>); | |
| 37 | + void doWrite(std::shared_ptr<QPDFWriter> w); | |
| 38 | + void testWrite(); | |
| 39 | + void doChecks(); | |
| 40 | + | |
| 41 | + Buffer input_buffer; | |
| 42 | + Pl_Discard discard; | |
| 43 | +}; | |
| 44 | + | |
| 45 | +FuzzHelper::FuzzHelper(unsigned char const* data, size_t size) : | |
| 46 | + // We do not modify data, so it is safe to remove the const for Buffer | |
| 47 | + input_buffer(const_cast<unsigned char*>(data), size) | |
| 48 | +{ | |
| 49 | +} | |
| 50 | + | |
| 51 | +std::shared_ptr<QPDF> | |
| 52 | +FuzzHelper::getQpdf() | |
| 53 | +{ | |
| 54 | + auto is = | |
| 55 | + std::shared_ptr<InputSource>(new BufferInputSource("fuzz input", &this->input_buffer)); | |
| 56 | + auto qpdf = QPDF::create(); | |
| 57 | + qpdf->setMaxWarnings(200); | |
| 58 | + qpdf->processInputSource(is); | |
| 59 | + return qpdf; | |
| 60 | +} | |
| 61 | + | |
| 62 | +std::shared_ptr<QPDFWriter> | |
| 63 | +FuzzHelper::getWriter(std::shared_ptr<QPDF> qpdf) | |
| 64 | +{ | |
| 65 | + auto w = std::make_shared<QPDFWriter>(*qpdf); | |
| 66 | + w->setOutputPipeline(&this->discard); | |
| 67 | + w->setDecodeLevel(qpdf_dl_all); | |
| 68 | + return w; | |
| 69 | +} | |
| 70 | + | |
| 71 | +void | |
| 72 | +FuzzHelper::doWrite(std::shared_ptr<QPDFWriter> w) | |
| 73 | +{ | |
| 74 | + try { | |
| 75 | + w->write(); | |
| 76 | + } catch (QPDFExc const& e) { | |
| 77 | + std::cerr << e.what() << std::endl; | |
| 78 | + } catch (std::runtime_error const& e) { | |
| 79 | + std::cerr << e.what() << std::endl; | |
| 80 | + } | |
| 81 | +} | |
| 82 | + | |
| 83 | +void | |
| 84 | +FuzzHelper::testWrite() | |
| 85 | +{ | |
| 86 | + // Write in various ways to exercise QPDFWriter | |
| 87 | + | |
| 88 | + std::shared_ptr<QPDF> q; | |
| 89 | + std::shared_ptr<QPDFWriter> w; | |
| 90 | + | |
| 91 | + q = getQpdf(); | |
| 92 | + w = getWriter(q); | |
| 93 | + w->setDeterministicID(true); | |
| 94 | + w->setObjectStreamMode(qpdf_o_generate); | |
| 95 | + w->setLinearization(true); | |
| 96 | + doWrite(w); | |
| 97 | +} | |
| 98 | + | |
| 99 | +void | |
| 100 | +FuzzHelper::doChecks() | |
| 101 | +{ | |
| 102 | + // Limit the memory used to decompress JPEG files during fuzzing. Excessive memory use during | |
| 103 | + // fuzzing is due to corrupt JPEG data which sometimes cannot be detected before | |
| 104 | + // jpeg_start_decompress is called. During normal use of qpdf very large JPEGs can occasionally | |
| 105 | + // occur legitimately and therefore must be allowed during normal operations. | |
| 106 | + Pl_DCT::setMemoryLimit(100'000'000); | |
| 107 | + Pl_DCT::setScanLimit(50); | |
| 108 | + | |
| 109 | + Pl_PNGFilter::setMemoryLimit(1'000'000); | |
| 110 | + Pl_TIFFPredictor::setMemoryLimit(1'000'000); | |
| 111 | + Pl_Flate::setMemoryLimit(1'000'000); | |
| 112 | + | |
| 113 | + // Do not decompress corrupt data. This may cause extended runtime within jpeglib without | |
| 114 | + // exercising additional code paths in qpdf, and potentially causing counterproductive timeouts. | |
| 115 | + Pl_DCT::setThrowOnCorruptData(true); | |
| 116 | + | |
| 117 | + // Get as much coverage as possible in parts of the library that | |
| 118 | + // might benefit from fuzzing. | |
| 119 | + std::cerr << "\ninfo: starting testWrite\n"; | |
| 120 | + testWrite(); | |
| 121 | +} | |
| 122 | + | |
| 123 | +void | |
| 124 | +FuzzHelper::run() | |
| 125 | +{ | |
| 126 | + // The goal here is that you should be able to throw anything at | |
| 127 | + // libqpdf and it will respond without any memory errors and never | |
| 128 | + // do anything worse than throwing a QPDFExc or | |
| 129 | + // std::runtime_error. Throwing any other kind of exception, | |
| 130 | + // segfaulting, or having a memory error (when built with | |
| 131 | + // appropriate sanitizers) will all cause abnormal exit. | |
| 132 | + try { | |
| 133 | + doChecks(); | |
| 134 | + } catch (QPDFExc const& e) { | |
| 135 | + std::cerr << "QPDFExc: " << e.what() << std::endl; | |
| 136 | + } catch (std::runtime_error const& e) { | |
| 137 | + std::cerr << "runtime_error: " << e.what() << std::endl; | |
| 138 | + } | |
| 139 | +} | |
| 140 | + | |
| 141 | +extern "C" int | |
| 142 | +LLVMFuzzerTestOneInput(unsigned char const* data, size_t size) | |
| 143 | +{ | |
| 144 | +#ifndef _WIN32 | |
| 145 | + // Used by jpeg library to work around false positives in memory | |
| 146 | + // sanitizer. | |
| 147 | + setenv("JSIMD_FORCENONE", "1", 1); | |
| 148 | +#endif | |
| 149 | + FuzzHelper f(data, size); | |
| 150 | + f.run(); | |
| 151 | + return 0; | |
| 152 | +} | ... | ... |
fuzz/qpdf_lin_fuzzer.options
0 → 100644
fuzz/qpdf_outlines_fuzzer.cc
0 → 100644
| 1 | +#include <qpdf/Buffer.hh> | |
| 2 | +#include <qpdf/BufferInputSource.hh> | |
| 3 | +#include <qpdf/Pl_DCT.hh> | |
| 4 | +#include <qpdf/Pl_Discard.hh> | |
| 5 | +#include <qpdf/Pl_Flate.hh> | |
| 6 | +#include <qpdf/Pl_PNGFilter.hh> | |
| 7 | +#include <qpdf/Pl_TIFFPredictor.hh> | |
| 8 | +#include <qpdf/QPDF.hh> | |
| 9 | +#include <qpdf/QPDFOutlineDocumentHelper.hh> | |
| 10 | +#include <qpdf/QPDFPageObjectHelper.hh> | |
| 11 | +#include <qpdf/QUtil.hh> | |
| 12 | +#include <cstdlib> | |
| 13 | + | |
| 14 | +class DiscardContents: public QPDFObjectHandle::ParserCallbacks | |
| 15 | +{ | |
| 16 | + public: | |
| 17 | + ~DiscardContents() override = default; | |
| 18 | + void | |
| 19 | + handleObject(QPDFObjectHandle) override | |
| 20 | + { | |
| 21 | + } | |
| 22 | + void | |
| 23 | + handleEOF() override | |
| 24 | + { | |
| 25 | + } | |
| 26 | +}; | |
| 27 | + | |
| 28 | +class FuzzHelper | |
| 29 | +{ | |
| 30 | + public: | |
| 31 | + FuzzHelper(unsigned char const* data, size_t size); | |
| 32 | + void run(); | |
| 33 | + | |
| 34 | + private: | |
| 35 | + std::shared_ptr<QPDF> getQpdf(); | |
| 36 | + void testOutlines(); | |
| 37 | + void doChecks(); | |
| 38 | + | |
| 39 | + Buffer input_buffer; | |
| 40 | + Pl_Discard discard; | |
| 41 | +}; | |
| 42 | + | |
| 43 | +FuzzHelper::FuzzHelper(unsigned char const* data, size_t size) : | |
| 44 | + // We do not modify data, so it is safe to remove the const for Buffer | |
| 45 | + input_buffer(const_cast<unsigned char*>(data), size) | |
| 46 | +{ | |
| 47 | +} | |
| 48 | + | |
| 49 | +std::shared_ptr<QPDF> | |
| 50 | +FuzzHelper::getQpdf() | |
| 51 | +{ | |
| 52 | + auto is = | |
| 53 | + std::shared_ptr<InputSource>(new BufferInputSource("fuzz input", &this->input_buffer)); | |
| 54 | + auto qpdf = QPDF::create(); | |
| 55 | + qpdf->setMaxWarnings(200); | |
| 56 | + qpdf->processInputSource(is); | |
| 57 | + return qpdf; | |
| 58 | +} | |
| 59 | + | |
| 60 | +void | |
| 61 | +FuzzHelper::testOutlines() | |
| 62 | +{ | |
| 63 | + std::shared_ptr<QPDF> q = getQpdf(); | |
| 64 | + std::list<std::vector<QPDFOutlineObjectHelper>> queue; | |
| 65 | + QPDFOutlineDocumentHelper odh(*q); | |
| 66 | + queue.push_back(odh.getTopLevelOutlines()); | |
| 67 | + while (!queue.empty()) { | |
| 68 | + for (auto& ol: *(queue.begin())) { | |
| 69 | + ol.getDestPage(); | |
| 70 | + queue.push_back(ol.getKids()); | |
| 71 | + } | |
| 72 | + queue.pop_front(); | |
| 73 | + } | |
| 74 | +} | |
| 75 | + | |
| 76 | +void | |
| 77 | +FuzzHelper::doChecks() | |
| 78 | +{ | |
| 79 | + // Limit the memory used to decompress JPEG files during fuzzing. Excessive memory use during | |
| 80 | + // fuzzing is due to corrupt JPEG data which sometimes cannot be detected before | |
| 81 | + // jpeg_start_decompress is called. During normal use of qpdf very large JPEGs can occasionally | |
| 82 | + // occur legitimately and therefore must be allowed during normal operations. | |
| 83 | + Pl_DCT::setMemoryLimit(100'000'000); | |
| 84 | + Pl_DCT::setScanLimit(50); | |
| 85 | + | |
| 86 | + Pl_PNGFilter::setMemoryLimit(1'000'000); | |
| 87 | + Pl_TIFFPredictor::setMemoryLimit(1'000'000); | |
| 88 | + Pl_Flate::setMemoryLimit(1'000'000); | |
| 89 | + | |
| 90 | + // Do not decompress corrupt data. This may cause extended runtime within jpeglib without | |
| 91 | + // exercising additional code paths in qpdf, and potentially causing counterproductive timeouts. | |
| 92 | + Pl_DCT::setThrowOnCorruptData(true); | |
| 93 | + | |
| 94 | + // Get as much coverage as possible in parts of the library that | |
| 95 | + // might benefit from fuzzing. | |
| 96 | + std::cerr << "\ninfo: starting testOutlines\n"; | |
| 97 | + testOutlines(); | |
| 98 | +} | |
| 99 | + | |
| 100 | +void | |
| 101 | +FuzzHelper::run() | |
| 102 | +{ | |
| 103 | + // The goal here is that you should be able to throw anything at | |
| 104 | + // libqpdf and it will respond without any memory errors and never | |
| 105 | + // do anything worse than throwing a QPDFExc or | |
| 106 | + // std::runtime_error. Throwing any other kind of exception, | |
| 107 | + // segfaulting, or having a memory error (when built with | |
| 108 | + // appropriate sanitizers) will all cause abnormal exit. | |
| 109 | + try { | |
| 110 | + doChecks(); | |
| 111 | + } catch (QPDFExc const& e) { | |
| 112 | + std::cerr << "QPDFExc: " << e.what() << std::endl; | |
| 113 | + } catch (std::runtime_error const& e) { | |
| 114 | + std::cerr << "runtime_error: " << e.what() << std::endl; | |
| 115 | + } | |
| 116 | +} | |
| 117 | + | |
| 118 | +extern "C" int | |
| 119 | +LLVMFuzzerTestOneInput(unsigned char const* data, size_t size) | |
| 120 | +{ | |
| 121 | +#ifndef _WIN32 | |
| 122 | + // Used by jpeg library to work around false positives in memory | |
| 123 | + // sanitizer. | |
| 124 | + setenv("JSIMD_FORCENONE", "1", 1); | |
| 125 | +#endif | |
| 126 | + FuzzHelper f(data, size); | |
| 127 | + f.run(); | |
| 128 | + return 0; | |
| 129 | +} | ... | ... |
fuzz/qpdf_outlines_fuzzer.options
0 → 100644
fuzz/qpdf_pages_fuzzer.cc
0 → 100644
| 1 | +#include <qpdf/Buffer.hh> | |
| 2 | +#include <qpdf/BufferInputSource.hh> | |
| 3 | +#include <qpdf/Pl_DCT.hh> | |
| 4 | +#include <qpdf/Pl_Discard.hh> | |
| 5 | +#include <qpdf/Pl_Flate.hh> | |
| 6 | +#include <qpdf/Pl_PNGFilter.hh> | |
| 7 | +#include <qpdf/Pl_TIFFPredictor.hh> | |
| 8 | +#include <qpdf/QPDF.hh> | |
| 9 | +#include <qpdf/QPDFAcroFormDocumentHelper.hh> | |
| 10 | +#include <qpdf/QPDFOutlineDocumentHelper.hh> | |
| 11 | +#include <qpdf/QPDFPageDocumentHelper.hh> | |
| 12 | +#include <qpdf/QPDFPageLabelDocumentHelper.hh> | |
| 13 | +#include <qpdf/QPDFPageObjectHelper.hh> | |
| 14 | +#include <qpdf/QUtil.hh> | |
| 15 | +#include <cstdlib> | |
| 16 | + | |
| 17 | +class DiscardContents: public QPDFObjectHandle::ParserCallbacks | |
| 18 | +{ | |
| 19 | + public: | |
| 20 | + ~DiscardContents() override = default; | |
| 21 | + void | |
| 22 | + handleObject(QPDFObjectHandle) override | |
| 23 | + { | |
| 24 | + } | |
| 25 | + void | |
| 26 | + handleEOF() override | |
| 27 | + { | |
| 28 | + } | |
| 29 | +}; | |
| 30 | + | |
| 31 | +class FuzzHelper | |
| 32 | +{ | |
| 33 | + public: | |
| 34 | + FuzzHelper(unsigned char const* data, size_t size); | |
| 35 | + void run(); | |
| 36 | + | |
| 37 | + private: | |
| 38 | + std::shared_ptr<QPDF> getQpdf(); | |
| 39 | + void testPages(); | |
| 40 | + void doChecks(); | |
| 41 | + | |
| 42 | + Buffer input_buffer; | |
| 43 | + Pl_Discard discard; | |
| 44 | +}; | |
| 45 | + | |
| 46 | +FuzzHelper::FuzzHelper(unsigned char const* data, size_t size) : | |
| 47 | + // We do not modify data, so it is safe to remove the const for Buffer | |
| 48 | + input_buffer(const_cast<unsigned char*>(data), size) | |
| 49 | +{ | |
| 50 | +} | |
| 51 | + | |
| 52 | +std::shared_ptr<QPDF> | |
| 53 | +FuzzHelper::getQpdf() | |
| 54 | +{ | |
| 55 | + auto is = | |
| 56 | + std::shared_ptr<InputSource>(new BufferInputSource("fuzz input", &this->input_buffer)); | |
| 57 | + auto qpdf = QPDF::create(); | |
| 58 | + qpdf->setMaxWarnings(200); | |
| 59 | + qpdf->processInputSource(is); | |
| 60 | + return qpdf; | |
| 61 | +} | |
| 62 | + | |
| 63 | +void | |
| 64 | +FuzzHelper::testPages() | |
| 65 | +{ | |
| 66 | + // Parse all content streams, and exercise some helpers that | |
| 67 | + // operate on pages. | |
| 68 | + std::shared_ptr<QPDF> q = getQpdf(); | |
| 69 | + QPDFPageDocumentHelper pdh(*q); | |
| 70 | + QPDFPageLabelDocumentHelper pldh(*q); | |
| 71 | + QPDFOutlineDocumentHelper odh(*q); | |
| 72 | + QPDFAcroFormDocumentHelper afdh(*q); | |
| 73 | + afdh.generateAppearancesIfNeeded(); | |
| 74 | + pdh.flattenAnnotations(); | |
| 75 | + DiscardContents discard_contents; | |
| 76 | + int pageno = 0; | |
| 77 | + for (auto& page: pdh.getAllPages()) { | |
| 78 | + ++pageno; | |
| 79 | + try { | |
| 80 | + page.coalesceContentStreams(); | |
| 81 | + page.parseContents(&discard_contents); | |
| 82 | + page.getImages(); | |
| 83 | + pldh.getLabelForPage(pageno); | |
| 84 | + QPDFObjectHandle page_obj(page.getObjectHandle()); | |
| 85 | + page_obj.getJSON(JSON::LATEST, true).unparse(); | |
| 86 | + odh.getOutlinesForPage(page_obj.getObjGen()); | |
| 87 | + | |
| 88 | + for (auto& aoh: afdh.getWidgetAnnotationsForPage(page)) { | |
| 89 | + afdh.getFieldForAnnotation(aoh); | |
| 90 | + } | |
| 91 | + } catch (QPDFExc& e) { | |
| 92 | + std::cerr << "page " << pageno << ": " << e.what() << std::endl; | |
| 93 | + } | |
| 94 | + } | |
| 95 | +} | |
| 96 | + | |
| 97 | +void | |
| 98 | +FuzzHelper::doChecks() | |
| 99 | +{ | |
| 100 | + // Limit the memory used to decompress JPEG files during fuzzing. Excessive memory use during | |
| 101 | + // fuzzing is due to corrupt JPEG data which sometimes cannot be detected before | |
| 102 | + // jpeg_start_decompress is called. During normal use of qpdf very large JPEGs can occasionally | |
| 103 | + // occur legitimately and therefore must be allowed during normal operations. | |
| 104 | + Pl_DCT::setMemoryLimit(100'000'000); | |
| 105 | + Pl_DCT::setScanLimit(50); | |
| 106 | + | |
| 107 | + Pl_PNGFilter::setMemoryLimit(1'000'000); | |
| 108 | + Pl_TIFFPredictor::setMemoryLimit(1'000'000); | |
| 109 | + Pl_Flate::setMemoryLimit(1'000'000); | |
| 110 | + | |
| 111 | + // Do not decompress corrupt data. This may cause extended runtime within jpeglib without | |
| 112 | + // exercising additional code paths in qpdf, and potentially causing counterproductive timeouts. | |
| 113 | + Pl_DCT::setThrowOnCorruptData(true); | |
| 114 | + | |
| 115 | + // Get as much coverage as possible in parts of the library that | |
| 116 | + // might benefit from fuzzing. | |
| 117 | + std::cerr << "\ninfo: starting testPages\n"; | |
| 118 | + testPages(); | |
| 119 | +} | |
| 120 | + | |
| 121 | +void | |
| 122 | +FuzzHelper::run() | |
| 123 | +{ | |
| 124 | + // The goal here is that you should be able to throw anything at | |
| 125 | + // libqpdf and it will respond without any memory errors and never | |
| 126 | + // do anything worse than throwing a QPDFExc or | |
| 127 | + // std::runtime_error. Throwing any other kind of exception, | |
| 128 | + // segfaulting, or having a memory error (when built with | |
| 129 | + // appropriate sanitizers) will all cause abnormal exit. | |
| 130 | + try { | |
| 131 | + doChecks(); | |
| 132 | + } catch (QPDFExc const& e) { | |
| 133 | + std::cerr << "QPDFExc: " << e.what() << std::endl; | |
| 134 | + } catch (std::runtime_error const& e) { | |
| 135 | + std::cerr << "runtime_error: " << e.what() << std::endl; | |
| 136 | + } | |
| 137 | +} | |
| 138 | + | |
| 139 | +extern "C" int | |
| 140 | +LLVMFuzzerTestOneInput(unsigned char const* data, size_t size) | |
| 141 | +{ | |
| 142 | +#ifndef _WIN32 | |
| 143 | + // Used by jpeg library to work around false positives in memory | |
| 144 | + // sanitizer. | |
| 145 | + setenv("JSIMD_FORCENONE", "1", 1); | |
| 146 | +#endif | |
| 147 | + FuzzHelper f(data, size); | |
| 148 | + f.run(); | |
| 149 | + return 0; | |
| 150 | +} | ... | ... |
fuzz/qpdf_pages_fuzzer.options
0 → 100644
fuzz/qtest/fuzz.test
| ... | ... | @@ -11,6 +11,8 @@ my $td = new TestDriver('fuzz'); |
| 11 | 11 | |
| 12 | 12 | my $qpdf_corpus = $ENV{'QPDF_FUZZ_CORPUS'} || die "must set QPDF_FUZZ_CORPUS"; |
| 13 | 13 | |
| 14 | +my $n_qpdf_files = 77; # increment when adding new files | |
| 15 | + | |
| 14 | 16 | my @fuzzers = ( |
| 15 | 17 | ['ascii85' => 1], |
| 16 | 18 | ['dct' => 4], |
| ... | ... | @@ -21,7 +23,12 @@ my @fuzzers = ( |
| 21 | 23 | ['pngpredictor' => 1], |
| 22 | 24 | ['runlength' => 6], |
| 23 | 25 | ['tiffpredictor' => 2], |
| 24 | - ['qpdf' => 77], # increment when adding new files | |
| 26 | + ['qpdf' => $n_qpdf_files], | |
| 27 | + ['qpdf_crypt' => $n_qpdf_files], | |
| 28 | + ['qpdf_crypt_insecure' => $n_qpdf_files], | |
| 29 | + ['qpdf_lin' => $n_qpdf_files], | |
| 30 | + ['qpdf_pages' => $n_qpdf_files], | |
| 31 | + ['qpdf_outlines' => $n_qpdf_files], | |
| 25 | 32 | ); |
| 26 | 33 | |
| 27 | 34 | my $n_tests = 0; | ... | ... |