/src/qpdf/fuzz/qpdf_crypt_fuzzer.cc
Line | Count | Source |
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_RunLength.hh> |
8 | | #include <qpdf/Pl_TIFFPredictor.hh> |
9 | | #include <qpdf/QPDF.hh> |
10 | | #include <qpdf/QPDFPageObjectHelper.hh> |
11 | | #include <qpdf/QPDFWriter.hh> |
12 | | #include <qpdf/QUtil.hh> |
13 | | |
14 | | #include <cstdlib> |
15 | | |
16 | | class DiscardContents: public QPDFObjectHandle::ParserCallbacks |
17 | | { |
18 | | public: |
19 | | ~DiscardContents() override = default; |
20 | | void |
21 | | handleObject(QPDFObjectHandle) override |
22 | | { |
23 | | } |
24 | | void |
25 | | handleEOF() override |
26 | | { |
27 | | } |
28 | | }; |
29 | | |
30 | | class FuzzHelper |
31 | | { |
32 | | public: |
33 | | FuzzHelper(unsigned char const* data, size_t size); |
34 | | void run(); |
35 | | |
36 | | private: |
37 | | std::shared_ptr<QPDF> getQpdf(); |
38 | | std::shared_ptr<QPDFWriter> getWriter(std::shared_ptr<QPDF>); |
39 | | void doWrite(std::shared_ptr<QPDFWriter> w); |
40 | | void testWrite(); |
41 | | void doChecks(); |
42 | | |
43 | | Buffer input_buffer; |
44 | | Pl_Discard discard; |
45 | | }; |
46 | | |
47 | | FuzzHelper::FuzzHelper(unsigned char const* data, size_t size) : |
48 | | // We do not modify data, so it is safe to remove the const for Buffer |
49 | 298k | input_buffer(const_cast<unsigned char*>(data), size) |
50 | 298k | { |
51 | 298k | } |
52 | | |
53 | | std::shared_ptr<QPDF> |
54 | | FuzzHelper::getQpdf() |
55 | 260k | { |
56 | 260k | auto is = |
57 | 260k | std::shared_ptr<InputSource>(new BufferInputSource("fuzz input", &this->input_buffer)); |
58 | 260k | auto qpdf = QPDF::create(); |
59 | 260k | qpdf->setMaxWarnings(200); |
60 | 260k | qpdf->processInputSource(is); |
61 | 260k | return qpdf; |
62 | 260k | } |
63 | | |
64 | | std::shared_ptr<QPDFWriter> |
65 | | FuzzHelper::getWriter(std::shared_ptr<QPDF> qpdf) |
66 | 72.0k | { |
67 | 72.0k | auto w = std::make_shared<QPDFWriter>(*qpdf); |
68 | 72.0k | w->setOutputPipeline(&this->discard); |
69 | 72.0k | w->setDecodeLevel(qpdf_dl_all); |
70 | 72.0k | return w; |
71 | 72.0k | } |
72 | | |
73 | | void |
74 | | FuzzHelper::doWrite(std::shared_ptr<QPDFWriter> w) |
75 | 70.3k | { |
76 | 70.3k | try { |
77 | 70.3k | w->write(); |
78 | 70.3k | } catch (QPDFExc const& e) { |
79 | 6.30k | std::cerr << e.what() << '\n'; |
80 | 6.30k | } catch (std::runtime_error const& e) { |
81 | 2.83k | std::cerr << e.what() << '\n'; |
82 | 2.83k | } |
83 | 70.3k | } |
84 | | |
85 | | void |
86 | | FuzzHelper::testWrite() |
87 | 178k | { |
88 | | // Write in various ways to exercise QPDFWriter |
89 | | |
90 | 178k | std::shared_ptr<QPDF> q; |
91 | 178k | std::shared_ptr<QPDFWriter> w; |
92 | | |
93 | 178k | q = getQpdf(); |
94 | 178k | w = getWriter(q); |
95 | 178k | w->setStaticID(true); |
96 | 178k | w->setLinearization(true); |
97 | 178k | w->setR6EncryptionParameters("u", "o", true, true, true, true, true, true, qpdf_r3p_full, true); |
98 | 178k | doWrite(w); |
99 | 178k | } |
100 | | |
101 | | void |
102 | | FuzzHelper::doChecks() |
103 | 283k | { |
104 | | // Limit the memory used to decompress JPEG files during fuzzing. Excessive memory use during |
105 | | // fuzzing is due to corrupt JPEG data which sometimes cannot be detected before |
106 | | // jpeg_start_decompress is called. During normal use of qpdf very large JPEGs can occasionally |
107 | | // occur legitimately and therefore must be allowed during normal operations. |
108 | 283k | Pl_DCT::setMemoryLimit(100'000'000); |
109 | 283k | Pl_DCT::setScanLimit(50); |
110 | | |
111 | 283k | Pl_PNGFilter::setMemoryLimit(1'000'000); |
112 | 283k | Pl_RunLength::setMemoryLimit(1'000'000); |
113 | 283k | Pl_TIFFPredictor::setMemoryLimit(1'000'000); |
114 | 283k | Pl_Flate::memory_limit(200'000); |
115 | | |
116 | | // Do not decompress corrupt data. This may cause extended runtime within jpeglib without |
117 | | // exercising additional code paths in qpdf, and potentially causing counterproductive timeouts. |
118 | 283k | Pl_DCT::setThrowOnCorruptData(true); |
119 | | |
120 | | // Get as much coverage as possible in parts of the library that |
121 | | // might benefit from fuzzing. |
122 | 283k | std::cerr << "\ninfo: starting testWrite\n"; |
123 | 283k | testWrite(); |
124 | 283k | } |
125 | | |
126 | | void |
127 | | FuzzHelper::run() |
128 | 260k | { |
129 | | // The goal here is that you should be able to throw anything at |
130 | | // libqpdf and it will respond without any memory errors and never |
131 | | // do anything worse than throwing a QPDFExc or |
132 | | // std::runtime_error. Throwing any other kind of exception, |
133 | | // segfaulting, or having a memory error (when built with |
134 | | // appropriate sanitizers) will all cause abnormal exit. |
135 | 260k | try { |
136 | 260k | doChecks(); |
137 | 260k | } catch (QPDFExc const& e) { |
138 | 170k | std::cerr << "QPDFExc: " << e.what() << '\n'; |
139 | 170k | } catch (std::runtime_error const& e) { |
140 | 2.02k | std::cerr << "runtime_error: " << e.what() << '\n'; |
141 | 2.02k | } |
142 | 260k | } |
143 | | |
144 | | extern "C" int |
145 | | LLVMFuzzerTestOneInput(unsigned char const* data, size_t size) |
146 | 298k | { |
147 | 298k | #ifndef _WIN32 |
148 | | // Used by jpeg library to work around false positives in memory |
149 | | // sanitizer. |
150 | 298k | setenv("JSIMD_FORCENONE", "1", 1); |
151 | 298k | #endif |
152 | 298k | FuzzHelper f(data, size); |
153 | 298k | f.run(); |
154 | 298k | return 0; |
155 | 298k | } |