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,10 +131,6 @@ if(FUTURE)
131 add_compile_definitions(QPDF_FUTURE=1) 131 add_compile_definitions(QPDF_FUTURE=1)
132 endif() 132 endif()
133 133
134 -if(OSS_FUZZ)  
135 - add_compile_definitions(QPDF_OSS_FUZZ=1)  
136 -endif()  
137 -  
138 enable_testing() 134 enable_testing()
139 set(RUN_QTEST perl ${qpdf_SOURCE_DIR}/run-qtest ${ENABLE_QTC_ARG}) 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,8 +26,18 @@ FuzzHelper::FuzzHelper(unsigned char const* data, size_t size) :
26 void 26 void
27 FuzzHelper::doChecks() 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 Pl_Discard discard; 39 Pl_Discard discard;
30 - Pl_DCT p("decode", &discard, 20'000'000); 40 + Pl_DCT p("decode", &discard);
31 p.write(const_cast<unsigned char*>(data), size); 41 p.write(const_cast<unsigned char*>(data), size);
32 p.finish(); 42 p.finish();
33 } 43 }
fuzz/qpdf_fuzzer.cc
1 #include <qpdf/Buffer.hh> 1 #include <qpdf/Buffer.hh>
2 #include <qpdf/BufferInputSource.hh> 2 #include <qpdf/BufferInputSource.hh>
  3 +#include <qpdf/Pl_DCT.hh>
3 #include <qpdf/Pl_Discard.hh> 4 #include <qpdf/Pl_Discard.hh>
4 #include <qpdf/QPDF.hh> 5 #include <qpdf/QPDF.hh>
5 #include <qpdf/QPDFAcroFormDocumentHelper.hh> 6 #include <qpdf/QPDFAcroFormDocumentHelper.hh>
@@ -171,6 +172,16 @@ FuzzHelper::testOutlines() @@ -171,6 +172,16 @@ FuzzHelper::testOutlines()
171 void 172 void
172 FuzzHelper::doChecks() 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 // Get as much coverage as possible in parts of the library that 185 // Get as much coverage as possible in parts of the library that
175 // might benefit from fuzzing. 186 // might benefit from fuzzing.
176 std::cerr << "\ninfo: starting testWrite\n"; 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,20 +34,15 @@ class QPDF_DLL_CLASS Pl_DCT: public Pipeline
34 QPDF_DLL 34 QPDF_DLL
35 Pl_DCT(char const* identifier, Pipeline* next); 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 QPDF_DLL 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 // Constructor for compressing image data 47 // Constructor for compressing image data
53 QPDF_DLL 48 QPDF_DLL
@@ -57,8 +52,7 @@ class QPDF_DLL_CLASS Pl_DCT: public Pipeline @@ -57,8 +52,7 @@ class QPDF_DLL_CLASS Pl_DCT: public Pipeline
57 JDIMENSION image_width, 52 JDIMENSION image_width,
58 JDIMENSION image_height, 53 JDIMENSION image_height,
59 int components, 54 int components,
60 - J_COLOR_SPACE color_space,  
61 - CompressConfig* config_callback = nullptr); 55 + J_COLOR_SPACE color_space);
62 56
63 QPDF_DLL 57 QPDF_DLL
64 ~Pl_DCT() override; 58 ~Pl_DCT() override;
@@ -83,6 +77,7 @@ class QPDF_DLL_CLASS Pl_DCT: public Pipeline @@ -83,6 +77,7 @@ class QPDF_DLL_CLASS Pl_DCT: public Pipeline
83 public: 77 public:
84 QPDF_DLL 78 QPDF_DLL
85 ~Members() = default; 79 ~Members() = default;
  80 + Members(Members const&) = delete;
86 81
87 private: 82 private:
88 // For compression 83 // For compression
@@ -90,25 +85,18 @@ class QPDF_DLL_CLASS Pl_DCT: public Pipeline @@ -90,25 +85,18 @@ class QPDF_DLL_CLASS Pl_DCT: public Pipeline
90 JDIMENSION image_width, 85 JDIMENSION image_width,
91 JDIMENSION image_height, 86 JDIMENSION image_height,
92 int components, 87 int components,
93 - J_COLOR_SPACE color_space,  
94 - CompressConfig* config_callback); 88 + J_COLOR_SPACE color_space);
95 // For decompression 89 // For decompression
96 - Members(size_t corrupt_data_limit);  
97 - Members(Members const&) = delete; 90 + Members();
98 91
99 action_e action; 92 action_e action;
100 Pl_Buffer buf; 93 Pl_Buffer buf;
101 94
102 - // Used for decompression  
103 - size_t corrupt_data_limit{0};  
104 -  
105 // Used for compression 95 // Used for compression
106 JDIMENSION image_width{0}; 96 JDIMENSION image_width{0};
107 JDIMENSION image_height{0}; 97 JDIMENSION image_height{0};
108 int components{1}; 98 int components{1};
109 J_COLOR_SPACE color_space{JCS_GRAYSCALE}; 99 J_COLOR_SPACE color_space{JCS_GRAYSCALE};
110 -  
111 - CompressConfig* config_callback{nullptr};  
112 }; 100 };
113 101
114 std::shared_ptr<Members> m; 102 std::shared_ptr<Members> m;
job.sums
1 # Generated by generate_auto_job 1 # Generated by generate_auto_job
2 -CMakeLists.txt 456938b9debc4997f142ccfb13f3baf2517ae5855e1fe9b2ada1a0b8f7e4facf 2 +CMakeLists.txt 47752f33b17fa526d46fc608a25ad6b8c61feba9deb1bd659fddf93e6e08b102
3 generate_auto_job f64733b79dcee5a0e3e8ccc6976448e8ddf0e8b6529987a66a7d3ab2ebc10a86 3 generate_auto_job f64733b79dcee5a0e3e8ccc6976448e8ddf0e8b6529987a66a7d3ab2ebc10a86
4 include/qpdf/auto_job_c_att.hh 4c2b171ea00531db54720bf49a43f8b34481586ae7fb6cbf225099ee42bc5bb4 4 include/qpdf/auto_job_c_att.hh 4c2b171ea00531db54720bf49a43f8b34481586ae7fb6cbf225099ee42bc5bb4
5 include/qpdf/auto_job_c_copy_att.hh 50609012bff14fd82f0649185940d617d05d530cdc522185c7f3920a561ccb42 5 include/qpdf/auto_job_c_copy_att.hh 50609012bff14fd82f0649185940d617d05d530cdc522185c7f3920a561ccb42
libqpdf/Pl_DCT.cc
@@ -20,6 +20,9 @@ namespace @@ -20,6 +20,9 @@ namespace
20 jmp_buf jmpbuf; 20 jmp_buf jmpbuf;
21 std::string msg; 21 std::string msg;
22 }; 22 };
  23 +
  24 + long memory_limit{0};
  25 + bool throw_on_corrupt_data{false};
23 } // namespace 26 } // namespace
24 27
25 static void 28 static void
@@ -32,38 +35,39 @@ error_handler(j_common_ptr cinfo) @@ -32,38 +35,39 @@ error_handler(j_common_ptr cinfo)
32 longjmp(jerr->jmpbuf, 1); 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 action(a_decompress), 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 Pl_DCT::Members::Members( 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 action(a_compress), 46 action(a_compress),
49 buf("DCT uncompressed image"), 47 buf("DCT uncompressed image"),
50 image_width(image_width), 48 image_width(image_width),
51 image_height(image_height), 49 image_height(image_height),
52 components(components), 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 Pl_DCT::Pl_DCT(char const* identifier, Pipeline* next) : 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 Pl_DCT::Pl_DCT( 73 Pl_DCT::Pl_DCT(
@@ -72,10 +76,9 @@ Pl_DCT::Pl_DCT( @@ -72,10 +76,9 @@ Pl_DCT::Pl_DCT(
72 JDIMENSION image_width, 76 JDIMENSION image_width,
73 JDIMENSION image_height, 77 JDIMENSION image_height,
74 int components, 78 int components,
75 - J_COLOR_SPACE color_space,  
76 - CompressConfig* config_callback) : 79 + J_COLOR_SPACE color_space) :
77 Pipeline(identifier, next), 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,9 +276,6 @@ Pl_DCT::compress(void* cinfo_p, Buffer* b)
273 cinfo->input_components = m->components; 276 cinfo->input_components = m->components;
274 cinfo->in_color_space = m->color_space; 277 cinfo->in_color_space = m->color_space;
275 jpeg_set_defaults(cinfo); 278 jpeg_set_defaults(cinfo);
276 - if (m->config_callback) {  
277 - m->config_callback->apply(cinfo);  
278 - }  
279 279
280 jpeg_start_compress(cinfo, TRUE); 280 jpeg_start_compress(cinfo, TRUE);
281 281
@@ -312,36 +312,32 @@ Pl_DCT::decompress(void* cinfo_p, Buffer* b) @@ -312,36 +312,32 @@ Pl_DCT::decompress(void* cinfo_p, Buffer* b)
312 # pragma GCC diagnostic pop 312 # pragma GCC diagnostic pop
313 #endif 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 jpeg_buffer_src(cinfo, b); 319 jpeg_buffer_src(cinfo, b);
326 320
327 (void)jpeg_read_header(cinfo, TRUE); 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 (void)jpeg_calc_output_dimensions(cinfo); 325 (void)jpeg_calc_output_dimensions(cinfo);
329 unsigned int width = cinfo->output_width * QIntC::to_uint(cinfo->output_components); 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 getNext()->finish(); 342 getNext()->finish();
347 } 343 }
libqpdf/QPDF.cc
@@ -500,6 +500,7 @@ QPDF::warn(QPDFExc const&amp; e) @@ -500,6 +500,7 @@ QPDF::warn(QPDFExc const&amp; e)
500 { 500 {
501 m->warnings.push_back(e); 501 m->warnings.push_back(e);
502 if (!m->suppress_warnings) { 502 if (!m->suppress_warnings) {
  503 +// QXXXQ
503 #ifdef QPDF_OSS_FUZZ 504 #ifdef QPDF_OSS_FUZZ
504 if (m->warnings.size() > 20) { 505 if (m->warnings.size() > 20) {
505 *m->log->getWarn() << "WARNING: too many warnings - additional warnings surpressed\n"; 506 *m->log->getWarn() << "WARNING: too many warnings - additional warnings surpressed\n";
libtests/dct_compress.cc
@@ -14,21 +14,6 @@ usage() @@ -14,21 +14,6 @@ usage()
14 exit(2); 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 int 17 int
33 main(int argc, char* argv[]) 18 main(int argc, char* argv[])
34 { 19 {
@@ -66,21 +51,12 @@ main(int argc, char* argv[]) @@ -66,21 +51,12 @@ main(int argc, char* argv[])
66 FILE* outfile = QUtil::safe_fopen(outfilename, "wb"); 51 FILE* outfile = QUtil::safe_fopen(outfilename, "wb");
67 Pl_StdioFile out("stdout", outfile); 52 Pl_StdioFile out("stdout", outfile);
68 unsigned char buf[100]; 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 dct.finish(); 58 dct.finish();
81 - if (!callback.called) {  
82 - std::cout << "Callback was not called" << std::endl;  
83 - } 59 +
84 fclose(infile); 60 fclose(infile);
85 fclose(outfile); 61 fclose(outfile);
86 return 0; 62 return 0;