Commit 65bd8bc57de1fa90c9c4994297e7eaf0d3795ed1

Authored by Jay Berkenbilt
Committed by m-holger
1 parent b45e3420

Add DCT decompression config methods in favor of compile-time changes

As a rule, we should avoid conditional compilation is it always causes
code paths that are sometimes not even seen lexically by the compiler.
Also, we want the actual code being fuzzed to be as close as possible
to the real code. Conditional compilation is suitable to handle
underlying system differences.

Instead, favor configuration using callbacks or other methods that can
be triggered in the places where they need to be exercised.
CMakeLists.txt
... ... @@ -131,10 +131,6 @@ if(FUTURE)
131 131 add_compile_definitions(QPDF_FUTURE=1)
132 132 endif()
133 133  
134   -if(OSS_FUZZ)
135   - add_compile_definitions(QPDF_OSS_FUZZ=1)
136   -endif()
137   -
138 134 enable_testing()
139 135 set(RUN_QTEST perl ${qpdf_SOURCE_DIR}/run-qtest ${ENABLE_QTC_ARG})
140 136  
... ...
fuzz/dct_fuzzer.cc
... ... @@ -26,8 +26,18 @@ FuzzHelper::FuzzHelper(unsigned char const* data, size_t size) :
26 26 void
27 27 FuzzHelper::doChecks()
28 28 {
  29 + // Limit the memory used to decompress JPEG files during fuzzing. Excessive memory use during
  30 + // fuzzing is due to corrupt JPEG data which sometimes cannot be detected before
  31 + // jpeg_start_decompress is called. During normal use of qpdf very large JPEGs can occasionally
  32 + // occur legitimately and therefore must be allowed during normal operations.
  33 + Pl_DCT::setMemoryLimit(1'000'000'000);
  34 +
  35 + // Do not decompress corrupt data. This may cause extended runtime within jpeglib without
  36 + // exercising additional code paths in qpdf.
  37 + Pl_DCT::setThrowOnCorruptData(true);
  38 +
29 39 Pl_Discard discard;
30   - Pl_DCT p("decode", &discard, 20'000'000);
  40 + Pl_DCT p("decode", &discard);
31 41 p.write(const_cast<unsigned char*>(data), size);
32 42 p.finish();
33 43 }
... ...
fuzz/qpdf_fuzzer.cc
1 1 #include <qpdf/Buffer.hh>
2 2 #include <qpdf/BufferInputSource.hh>
  3 +#include <qpdf/Pl_DCT.hh>
3 4 #include <qpdf/Pl_Discard.hh>
4 5 #include <qpdf/QPDF.hh>
5 6 #include <qpdf/QPDFAcroFormDocumentHelper.hh>
... ... @@ -171,6 +172,16 @@ FuzzHelper::testOutlines()
171 172 void
172 173 FuzzHelper::doChecks()
173 174 {
  175 + // Limit the memory used to decompress JPEG files during fuzzing. Excessive memory use during
  176 + // fuzzing is due to corrupt JPEG data which sometimes cannot be detected before
  177 + // jpeg_start_decompress is called. During normal use of qpdf very large JPEGs can occasionally
  178 + // occur legitimately and therefore must be allowed during normal operations.
  179 + Pl_DCT::setMemoryLimit(1'000'000'000);
  180 +
  181 + // Do not decompress corrupt data. This may cause extended runtime within jpeglib without
  182 + // exercising additional code paths in qpdf, and potentially causing counterproductive timeouts.
  183 + Pl_DCT::setThrowOnCorruptData(true);
  184 +
174 185 // Get as much coverage as possible in parts of the library that
175 186 // might benefit from fuzzing.
176 187 std::cerr << "\ninfo: starting testWrite\n";
... ...
include/qpdf/Pl_DCT.hh
... ... @@ -34,20 +34,15 @@ class QPDF_DLL_CLASS Pl_DCT: public Pipeline
34 34 QPDF_DLL
35 35 Pl_DCT(char const* identifier, Pipeline* next);
36 36  
37   - // Constructor for decompressing image data. If corrupt_data_limit is non-zero and the data is
38   - // corrupt, only attempt to uncompress if the uncompressed size is less than corrupt_data_limit.
  37 + // Limit the memory used by jpeglib when decompressing data.
  38 + // NB This is a static option affecting all Pl_DCT instances.
39 39 QPDF_DLL
40   - Pl_DCT(char const* identifier, Pipeline* next, size_t corrupt_data_limit);
  40 + static void setMemoryLimit(long limit);
41 41  
42   - class QPDF_DLL_CLASS CompressConfig
43   - {
44   - public:
45   - QPDF_DLL
46   - CompressConfig() = default;
47   - QPDF_DLL
48   - virtual ~CompressConfig() = default;
49   - virtual void apply(jpeg_compress_struct*) = 0;
50   - };
  42 + // Treat corrupt data as a runtime error rather than attempting to decompress regardless.
  43 + // NB This is a static option affecting all Pl_DCT instances.
  44 + QPDF_DLL
  45 + static void setThrowOnCorruptData(bool treat_as_error);
51 46  
52 47 // Constructor for compressing image data
53 48 QPDF_DLL
... ... @@ -57,8 +52,7 @@ class QPDF_DLL_CLASS Pl_DCT: public Pipeline
57 52 JDIMENSION image_width,
58 53 JDIMENSION image_height,
59 54 int components,
60   - J_COLOR_SPACE color_space,
61   - CompressConfig* config_callback = nullptr);
  55 + J_COLOR_SPACE color_space);
62 56  
63 57 QPDF_DLL
64 58 ~Pl_DCT() override;
... ... @@ -83,6 +77,7 @@ class QPDF_DLL_CLASS Pl_DCT: public Pipeline
83 77 public:
84 78 QPDF_DLL
85 79 ~Members() = default;
  80 + Members(Members const&) = delete;
86 81  
87 82 private:
88 83 // For compression
... ... @@ -90,25 +85,18 @@ class QPDF_DLL_CLASS Pl_DCT: public Pipeline
90 85 JDIMENSION image_width,
91 86 JDIMENSION image_height,
92 87 int components,
93   - J_COLOR_SPACE color_space,
94   - CompressConfig* config_callback);
  88 + J_COLOR_SPACE color_space);
95 89 // For decompression
96   - Members(size_t corrupt_data_limit);
97   - Members(Members const&) = delete;
  90 + Members();
98 91  
99 92 action_e action;
100 93 Pl_Buffer buf;
101 94  
102   - // Used for decompression
103   - size_t corrupt_data_limit{0};
104   -
105 95 // Used for compression
106 96 JDIMENSION image_width{0};
107 97 JDIMENSION image_height{0};
108 98 int components{1};
109 99 J_COLOR_SPACE color_space{JCS_GRAYSCALE};
110   -
111   - CompressConfig* config_callback{nullptr};
112 100 };
113 101  
114 102 std::shared_ptr<Members> m;
... ...
job.sums
1 1 # Generated by generate_auto_job
2   -CMakeLists.txt 456938b9debc4997f142ccfb13f3baf2517ae5855e1fe9b2ada1a0b8f7e4facf
  2 +CMakeLists.txt 47752f33b17fa526d46fc608a25ad6b8c61feba9deb1bd659fddf93e6e08b102
3 3 generate_auto_job f64733b79dcee5a0e3e8ccc6976448e8ddf0e8b6529987a66a7d3ab2ebc10a86
4 4 include/qpdf/auto_job_c_att.hh 4c2b171ea00531db54720bf49a43f8b34481586ae7fb6cbf225099ee42bc5bb4
5 5 include/qpdf/auto_job_c_copy_att.hh 50609012bff14fd82f0649185940d617d05d530cdc522185c7f3920a561ccb42
... ...
libqpdf/Pl_DCT.cc
... ... @@ -20,6 +20,9 @@ namespace
20 20 jmp_buf jmpbuf;
21 21 std::string msg;
22 22 };
  23 +
  24 + long memory_limit{0};
  25 + bool throw_on_corrupt_data{false};
23 26 } // namespace
24 27  
25 28 static void
... ... @@ -32,38 +35,39 @@ error_handler(j_common_ptr cinfo)
32 35 longjmp(jerr->jmpbuf, 1);
33 36 }
34 37  
35   -Pl_DCT::Members::Members(size_t corrupt_data_limit) :
  38 +Pl_DCT::Members::Members() :
36 39 action(a_decompress),
37   - buf("DCT compressed image"),
38   - corrupt_data_limit(corrupt_data_limit)
  40 + buf("DCT compressed image")
39 41 {
40 42 }
41 43  
42 44 Pl_DCT::Members::Members(
43   - JDIMENSION image_width,
44   - JDIMENSION image_height,
45   - int components,
46   - J_COLOR_SPACE color_space,
47   - CompressConfig* config_callback) :
  45 + JDIMENSION image_width, JDIMENSION image_height, int components, J_COLOR_SPACE color_space) :
48 46 action(a_compress),
49 47 buf("DCT uncompressed image"),
50 48 image_width(image_width),
51 49 image_height(image_height),
52 50 components(components),
53   - color_space(color_space),
54   - config_callback(config_callback)
  51 + color_space(color_space)
55 52 {
56 53 }
57 54  
58 55 Pl_DCT::Pl_DCT(char const* identifier, Pipeline* next) :
59   - Pl_DCT(identifier, next, 0)
  56 + Pipeline(identifier, next),
  57 + m(new Members())
60 58 {
61 59 }
62 60  
63   -Pl_DCT::Pl_DCT(char const* identifier, Pipeline* next, size_t corrupt_data_limit) :
64   - Pipeline(identifier, next),
65   - m(new Members(corrupt_data_limit))
  61 +void
  62 +Pl_DCT::setMemoryLimit(long limit)
66 63 {
  64 + memory_limit = limit;
  65 +}
  66 +
  67 +void
  68 +Pl_DCT::setThrowOnCorruptData(bool treat_as_error)
  69 +{
  70 + throw_on_corrupt_data = treat_as_error;
67 71 }
68 72  
69 73 Pl_DCT::Pl_DCT(
... ... @@ -72,10 +76,9 @@ Pl_DCT::Pl_DCT(
72 76 JDIMENSION image_width,
73 77 JDIMENSION image_height,
74 78 int components,
75   - J_COLOR_SPACE color_space,
76   - CompressConfig* config_callback) :
  79 + J_COLOR_SPACE color_space) :
77 80 Pipeline(identifier, next),
78   - m(new Members(image_width, image_height, components, color_space, config_callback))
  81 + m(new Members(image_width, image_height, components, color_space))
79 82 {
80 83 }
81 84  
... ... @@ -273,9 +276,6 @@ Pl_DCT::compress(void* cinfo_p, Buffer* b)
273 276 cinfo->input_components = m->components;
274 277 cinfo->in_color_space = m->color_space;
275 278 jpeg_set_defaults(cinfo);
276   - if (m->config_callback) {
277   - m->config_callback->apply(cinfo);
278   - }
279 279  
280 280 jpeg_start_compress(cinfo, TRUE);
281 281  
... ... @@ -312,36 +312,32 @@ Pl_DCT::decompress(void* cinfo_p, Buffer* b)
312 312 # pragma GCC diagnostic pop
313 313 #endif
314 314  
315   -#ifdef QPDF_OSS_FUZZ
316   - // Limit the memory used to decompress JPEG files during fuzzing. Excessive memory use during
317   - // fuzzing is due to corrupt JPEG data which sometimes cannot be detected before
318   - // jpeg_start_decompress is called. During normal use of qpdf very large JPEGs can occasionally
319   - // occur legitimately and therefore must be allowed during normal operations.
320   - cinfo->mem->max_memory_to_use = 1'000'000'000;
321   - // For some corrupt files the memory used internally by libjpeg stays within the above limits
322   - // even though the size written to the next pipeline is significantly larger.
323   - m->corrupt_data_limit = 10'000'000;
324   -#endif
  315 + if (memory_limit > 0) {
  316 + cinfo->mem->max_memory_to_use = memory_limit;
  317 + }
  318 +
325 319 jpeg_buffer_src(cinfo, b);
326 320  
327 321 (void)jpeg_read_header(cinfo, TRUE);
  322 + if (throw_on_corrupt_data && cinfo->err->num_warnings > 0) {
  323 + throw std::runtime_error("Pl_DCT::decompress: JPEG data is corrupt");
  324 + }
328 325 (void)jpeg_calc_output_dimensions(cinfo);
329 326 unsigned int width = cinfo->output_width * QIntC::to_uint(cinfo->output_components);
330   - if (cinfo->err->num_warnings == 0 || m->corrupt_data_limit == 0 ||
331   - (width * QIntC::to_uint(cinfo->output_height)) < m->corrupt_data_limit) {
332   - // err->num_warnings is the number of corrupt data warnings emitted.
333   - // err->msg_code could also be the code of an informational message.
334   - JSAMPARRAY buffer = (*cinfo->mem->alloc_sarray)(
335   - reinterpret_cast<j_common_ptr>(cinfo), JPOOL_IMAGE, width, 1);
336   -
337   - (void)jpeg_start_decompress(cinfo);
338   - while (cinfo->output_scanline < cinfo->output_height) {
339   - (void)jpeg_read_scanlines(cinfo, buffer, 1);
340   - getNext()->write(buffer[0], width * sizeof(buffer[0][0]));
341   - }
342   - (void)jpeg_finish_decompress(cinfo);
343   - } else {
344   - *QPDFLogger::defaultLogger()->getError() << "corrupt JPEG data ignored" << "\n";
  327 + // err->num_warnings is the number of corrupt data warnings emitted.
  328 + // err->msg_code could also be the code of an informational message.
  329 + JSAMPARRAY buffer =
  330 + (*cinfo->mem->alloc_sarray)(reinterpret_cast<j_common_ptr>(cinfo), JPOOL_IMAGE, width, 1);
  331 +
  332 + (void)jpeg_start_decompress(cinfo);
  333 + while (cinfo->output_scanline < cinfo->output_height &&
  334 + (!throw_on_corrupt_data || cinfo->err->num_warnings == 0)) {
  335 + (void)jpeg_read_scanlines(cinfo, buffer, 1);
  336 + getNext()->write(buffer[0], width * sizeof(buffer[0][0]));
  337 + }
  338 + (void)jpeg_finish_decompress(cinfo);
  339 + if (throw_on_corrupt_data && cinfo->err->num_warnings > 0) {
  340 + throw std::runtime_error("Pl_DCT::decompress: JPEG data is corrupt");
345 341 }
346 342 getNext()->finish();
347 343 }
... ...
libqpdf/QPDF.cc
... ... @@ -500,6 +500,7 @@ QPDF::warn(QPDFExc const&amp; e)
500 500 {
501 501 m->warnings.push_back(e);
502 502 if (!m->suppress_warnings) {
  503 +// QXXXQ
503 504 #ifdef QPDF_OSS_FUZZ
504 505 if (m->warnings.size() > 20) {
505 506 *m->log->getWarn() << "WARNING: too many warnings - additional warnings surpressed\n";
... ...
libtests/dct_compress.cc
... ... @@ -14,21 +14,6 @@ usage()
14 14 exit(2);
15 15 }
16 16  
17   -class Callback: public Pl_DCT::CompressConfig
18   -{
19   - public:
20   - Callback() = default;
21   - ~Callback() override = default;
22   - void apply(jpeg_compress_struct*) override;
23   - bool called{false};
24   -};
25   -
26   -void
27   -Callback::apply(jpeg_compress_struct*)
28   -{
29   - this->called = true;
30   -}
31   -
32 17 int
33 18 main(int argc, char* argv[])
34 19 {
... ... @@ -66,21 +51,12 @@ main(int argc, char* argv[])
66 51 FILE* outfile = QUtil::safe_fopen(outfilename, "wb");
67 52 Pl_StdioFile out("stdout", outfile);
68 53 unsigned char buf[100];
69   - bool done = false;
70   - Callback callback;
71   - Pl_DCT dct("dct", &out, width, height, components, cs, &callback);
72   - while (!done) {
73   - size_t len = fread(buf, 1, sizeof(buf), infile);
74   - if (len <= 0) {
75   - done = true;
76   - } else {
77   - dct.write(buf, len);
78   - }
  54 + Pl_DCT dct("dct", &out, width, height, components, cs);
  55 + while (size_t len = fread(buf, 1, sizeof(buf), infile)) {
  56 + dct.write(buf, len);
79 57 }
80 58 dct.finish();
81   - if (!callback.called) {
82   - std::cout << "Callback was not called" << std::endl;
83   - }
  59 +
84 60 fclose(infile);
85 61 fclose(outfile);
86 62 return 0;
... ...