Commit ce2deaf1850f11501016b62f5a9a124c0ab30ced
Committed by
GitHub
Merge pull request #1230 from m-holger/clean-dct-fuzz-changes
Alternative clean dct fuzz changes
Showing
15 changed files
with
152 additions
and
107 deletions
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 |
ChangeLog
| 1 | +2024-07-04 M Holger <m.holger@qpdf.org> | ||
| 2 | + | ||
| 3 | + * Treat corrupt JPEG streams as unfilterable. This avoids them | ||
| 4 | + getting uncompressed when writing PDF files with decode level all. | ||
| 5 | + | ||
| 6 | +2024-07-02 Jay Berkenbilt <ejb@ql.org> | ||
| 7 | + | ||
| 8 | + * Add QPDF::setMaxWarnings to set the maximum of warnings before | ||
| 9 | + warning suppression. | ||
| 10 | + | ||
| 11 | + * Add static option to Pl_DCT to limit memory usage of | ||
| 12 | + decompression. The option is generally exposed but is primarily | ||
| 13 | + intended to support fuzz tests, which have explicit memory limits | ||
| 14 | + that are smaller than what is commonly seen in the wild with PDF | ||
| 15 | + files. | ||
| 16 | + | ||
| 17 | + * Add static option to Pl_DCT to control whether decompression of | ||
| 18 | + corrupt JPEG data is attempted. | ||
| 19 | + | ||
| 20 | +2024-07-01 M Holger <m.holger@qpdf.org> | ||
| 21 | + | ||
| 22 | + * Bug fix: certain invalid object streams caused the insertion of | ||
| 23 | + invalid entries into in the xref table. | ||
| 24 | + | ||
| 25 | +2024-06-29 M Holger <m.holger@qpdf.org> | ||
| 26 | + | ||
| 27 | + * Bug fix: in QPDFOutlineObjectHelper detect loops in the list of | ||
| 28 | + direct children of an outline item. | ||
| 29 | + | ||
| 30 | +2024-06-27 M Holger <m.holger@qpdf.org> | ||
| 31 | + | ||
| 32 | + * Add sanity check in QPDF xref table reconstruction to reject | ||
| 33 | + objects with impossibly large object id in order to improve | ||
| 34 | + handling of severely damaged PDF files. | ||
| 35 | + | ||
| 36 | +2024-06-25 M Holger <m.holger@qpdf.org> | ||
| 37 | + | ||
| 38 | + * Detect severely damaged PDF files early. After parsing the xref | ||
| 39 | + table in QPDF throw a damagedPDF exception if the root of the pages | ||
| 40 | + tree is not a dictionary. | ||
| 41 | + | ||
| 1 | 2024-06-07 Jay Berkenbilt <ejb@ql.org> | 42 | 2024-06-07 Jay Berkenbilt <ejb@ql.org> |
| 2 | 43 | ||
| 3 | * 11.9.1: release | 44 | * 11.9.1: release |
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> |
| @@ -56,6 +57,7 @@ FuzzHelper::getQpdf() | @@ -56,6 +57,7 @@ FuzzHelper::getQpdf() | ||
| 56 | auto is = | 57 | auto is = |
| 57 | std::shared_ptr<InputSource>(new BufferInputSource("fuzz input", &this->input_buffer)); | 58 | std::shared_ptr<InputSource>(new BufferInputSource("fuzz input", &this->input_buffer)); |
| 58 | auto qpdf = QPDF::create(); | 59 | auto qpdf = QPDF::create(); |
| 60 | + qpdf->setMaxWarnings(20); | ||
| 59 | qpdf->processInputSource(is); | 61 | qpdf->processInputSource(is); |
| 60 | return qpdf; | 62 | return qpdf; |
| 61 | } | 63 | } |
| @@ -171,6 +173,16 @@ FuzzHelper::testOutlines() | @@ -171,6 +173,16 @@ FuzzHelper::testOutlines() | ||
| 171 | void | 173 | void |
| 172 | FuzzHelper::doChecks() | 174 | FuzzHelper::doChecks() |
| 173 | { | 175 | { |
| 176 | + // Limit the memory used to decompress JPEG files during fuzzing. Excessive memory use during | ||
| 177 | + // fuzzing is due to corrupt JPEG data which sometimes cannot be detected before | ||
| 178 | + // jpeg_start_decompress is called. During normal use of qpdf very large JPEGs can occasionally | ||
| 179 | + // occur legitimately and therefore must be allowed during normal operations. | ||
| 180 | + Pl_DCT::setMemoryLimit(1'000'000'000); | ||
| 181 | + | ||
| 182 | + // Do not decompress corrupt data. This may cause extended runtime within jpeglib without | ||
| 183 | + // exercising additional code paths in qpdf, and potentially causing counterproductive timeouts. | ||
| 184 | + Pl_DCT::setThrowOnCorruptData(true); | ||
| 185 | + | ||
| 174 | // Get as much coverage as possible in parts of the library that | 186 | // Get as much coverage as possible in parts of the library that |
| 175 | // might benefit from fuzzing. | 187 | // might benefit from fuzzing. |
| 176 | std::cerr << "\ninfo: starting testWrite\n"; | 188 | std::cerr << "\ninfo: starting testWrite\n"; |
include/qpdf/Pl_DCT.hh
| @@ -34,20 +34,17 @@ class QPDF_DLL_CLASS Pl_DCT: public Pipeline | @@ -34,20 +34,17 @@ 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. This |
| 43 | + // is the qpdf default behaviour. To attempt to decompress corrupt data set 'treat_as_error' to | ||
| 44 | + // false. | ||
| 45 | + // NB This is a static option affecting all Pl_DCT instances. | ||
| 46 | + QPDF_DLL | ||
| 47 | + static void setThrowOnCorruptData(bool treat_as_error); | ||
| 51 | 48 | ||
| 52 | // Constructor for compressing image data | 49 | // Constructor for compressing image data |
| 53 | QPDF_DLL | 50 | QPDF_DLL |
| @@ -57,8 +54,7 @@ class QPDF_DLL_CLASS Pl_DCT: public Pipeline | @@ -57,8 +54,7 @@ class QPDF_DLL_CLASS Pl_DCT: public Pipeline | ||
| 57 | JDIMENSION image_width, | 54 | JDIMENSION image_width, |
| 58 | JDIMENSION image_height, | 55 | JDIMENSION image_height, |
| 59 | int components, | 56 | int components, |
| 60 | - J_COLOR_SPACE color_space, | ||
| 61 | - CompressConfig* config_callback = nullptr); | 57 | + J_COLOR_SPACE color_space); |
| 62 | 58 | ||
| 63 | QPDF_DLL | 59 | QPDF_DLL |
| 64 | ~Pl_DCT() override; | 60 | ~Pl_DCT() override; |
| @@ -83,6 +79,7 @@ class QPDF_DLL_CLASS Pl_DCT: public Pipeline | @@ -83,6 +79,7 @@ class QPDF_DLL_CLASS Pl_DCT: public Pipeline | ||
| 83 | public: | 79 | public: |
| 84 | QPDF_DLL | 80 | QPDF_DLL |
| 85 | ~Members() = default; | 81 | ~Members() = default; |
| 82 | + Members(Members const&) = delete; | ||
| 86 | 83 | ||
| 87 | private: | 84 | private: |
| 88 | // For compression | 85 | // For compression |
| @@ -90,25 +87,18 @@ class QPDF_DLL_CLASS Pl_DCT: public Pipeline | @@ -90,25 +87,18 @@ class QPDF_DLL_CLASS Pl_DCT: public Pipeline | ||
| 90 | JDIMENSION image_width, | 87 | JDIMENSION image_width, |
| 91 | JDIMENSION image_height, | 88 | JDIMENSION image_height, |
| 92 | int components, | 89 | int components, |
| 93 | - J_COLOR_SPACE color_space, | ||
| 94 | - CompressConfig* config_callback); | 90 | + J_COLOR_SPACE color_space); |
| 95 | // For decompression | 91 | // For decompression |
| 96 | - Members(size_t corrupt_data_limit); | ||
| 97 | - Members(Members const&) = delete; | 92 | + Members(); |
| 98 | 93 | ||
| 99 | action_e action; | 94 | action_e action; |
| 100 | Pl_Buffer buf; | 95 | Pl_Buffer buf; |
| 101 | 96 | ||
| 102 | - // Used for decompression | ||
| 103 | - size_t corrupt_data_limit{0}; | ||
| 104 | - | ||
| 105 | // Used for compression | 97 | // Used for compression |
| 106 | JDIMENSION image_width{0}; | 98 | JDIMENSION image_width{0}; |
| 107 | JDIMENSION image_height{0}; | 99 | JDIMENSION image_height{0}; |
| 108 | int components{1}; | 100 | int components{1}; |
| 109 | J_COLOR_SPACE color_space{JCS_GRAYSCALE}; | 101 | J_COLOR_SPACE color_space{JCS_GRAYSCALE}; |
| 110 | - | ||
| 111 | - CompressConfig* config_callback{nullptr}; | ||
| 112 | }; | 102 | }; |
| 113 | 103 | ||
| 114 | std::shared_ptr<Members> m; | 104 | std::shared_ptr<Members> m; |
include/qpdf/QPDF.hh
| @@ -228,6 +228,10 @@ class QPDF | @@ -228,6 +228,10 @@ class QPDF | ||
| 228 | QPDF_DLL | 228 | QPDF_DLL |
| 229 | void setSuppressWarnings(bool); | 229 | void setSuppressWarnings(bool); |
| 230 | 230 | ||
| 231 | + // Set the maximum number of warnings to output. Subsequent warnings are suppressed. | ||
| 232 | + QPDF_DLL | ||
| 233 | + void setMaxWarnings(int); | ||
| 234 | + | ||
| 231 | // By default, QPDF will try to recover if it finds certain types of errors in PDF files. If | 235 | // By default, QPDF will try to recover if it finds certain types of errors in PDF files. If |
| 232 | // turned off, it will throw an exception on the first such problem it finds without attempting | 236 | // turned off, it will throw an exception on the first such problem it finds without attempting |
| 233 | // recovery. | 237 | // recovery. |
| @@ -1497,6 +1501,7 @@ class QPDF | @@ -1497,6 +1501,7 @@ class QPDF | ||
| 1497 | bool provided_password_is_hex_key{false}; | 1501 | bool provided_password_is_hex_key{false}; |
| 1498 | bool ignore_xref_streams{false}; | 1502 | bool ignore_xref_streams{false}; |
| 1499 | bool suppress_warnings{false}; | 1503 | bool suppress_warnings{false}; |
| 1504 | + int max_warnings{0}; | ||
| 1500 | bool attempt_recovery{true}; | 1505 | bool attempt_recovery{true}; |
| 1501 | bool check_mode{false}; | 1506 | bool check_mode{false}; |
| 1502 | std::shared_ptr<EncryptionParameters> encp; | 1507 | std::shared_ptr<EncryptionParameters> encp; |
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{true}; | ||
| 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
| @@ -332,6 +332,12 @@ QPDF::setSuppressWarnings(bool val) | @@ -332,6 +332,12 @@ QPDF::setSuppressWarnings(bool val) | ||
| 332 | } | 332 | } |
| 333 | 333 | ||
| 334 | void | 334 | void |
| 335 | +QPDF::setMaxWarnings(int val) | ||
| 336 | +{ | ||
| 337 | + m->suppress_warnings = val; | ||
| 338 | +} | ||
| 339 | + | ||
| 340 | +void | ||
| 335 | QPDF::setAttemptRecovery(bool val) | 341 | QPDF::setAttemptRecovery(bool val) |
| 336 | { | 342 | { |
| 337 | m->attempt_recovery = val; | 343 | m->attempt_recovery = val; |
| @@ -500,13 +506,11 @@ QPDF::warn(QPDFExc const& e) | @@ -500,13 +506,11 @@ QPDF::warn(QPDFExc const& e) | ||
| 500 | { | 506 | { |
| 501 | m->warnings.push_back(e); | 507 | m->warnings.push_back(e); |
| 502 | if (!m->suppress_warnings) { | 508 | if (!m->suppress_warnings) { |
| 503 | -#ifdef QPDF_OSS_FUZZ | ||
| 504 | - if (m->warnings.size() > 20) { | ||
| 505 | - *m->log->getWarn() << "WARNING: too many warnings - additional warnings surpressed\n"; | 509 | + if (m->max_warnings > 0 && m->warnings.size() > 20) { |
| 510 | + *m->log->getWarn() << "WARNING: too many warnings - additional warnings suppressed\n"; | ||
| 506 | m->suppress_warnings = true; | 511 | m->suppress_warnings = true; |
| 507 | return; | 512 | return; |
| 508 | } | 513 | } |
| 509 | -#endif | ||
| 510 | *m->log->getWarn() << "WARNING: " << m->warnings.back().what() << "\n"; | 514 | *m->log->getWarn() << "WARNING: " << m->warnings.back().what() << "\n"; |
| 511 | } | 515 | } |
| 512 | } | 516 | } |
| @@ -1934,6 +1938,7 @@ QPDF::resolveObjectsInStream(int obj_stream_number) | @@ -1934,6 +1938,7 @@ QPDF::resolveObjectsInStream(int obj_stream_number) | ||
| 1934 | continue; | 1938 | continue; |
| 1935 | } | 1939 | } |
| 1936 | if (num == obj_stream_number) { | 1940 | if (num == obj_stream_number) { |
| 1941 | + QTC::TC("qpdf", "QPDF ignore self-referential object stream"); | ||
| 1937 | warn(damagedPDF( | 1942 | warn(damagedPDF( |
| 1938 | input, | 1943 | input, |
| 1939 | m->last_object_description, | 1944 | m->last_object_description, |
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; |
qpdf/qpdf.testcov
| @@ -4,6 +4,7 @@ QPDF err wrong objid/generation 0 | @@ -4,6 +4,7 @@ QPDF err wrong objid/generation 0 | ||
| 4 | QPDF check objid 1 | 4 | QPDF check objid 1 |
| 5 | QPDF check generation 1 | 5 | QPDF check generation 1 |
| 6 | QPDF check obj 1 | 6 | QPDF check obj 1 |
| 7 | +QPDF ignore self-referential object stream 0 | ||
| 7 | QPDF hint table length indirect 0 | 8 | QPDF hint table length indirect 0 |
| 8 | QPDF hint table length direct 0 | 9 | QPDF hint table length direct 0 |
| 9 | QPDF P absent in lindict 0 | 10 | QPDF P absent in lindict 0 |
qpdf/qtest/object-stream.test
| @@ -16,7 +16,7 @@ cleanup(); | @@ -16,7 +16,7 @@ cleanup(); | ||
| 16 | 16 | ||
| 17 | my $td = new TestDriver('object-stream'); | 17 | my $td = new TestDriver('object-stream'); |
| 18 | 18 | ||
| 19 | -my $n_tests = 7 + (36 * 4) + (12 * 2); | 19 | +my $n_tests = 9 + (36 * 4) + (12 * 2); |
| 20 | my $n_compare_pdfs = 36; | 20 | my $n_compare_pdfs = 36; |
| 21 | 21 | ||
| 22 | for (my $n = 16; $n <= 19; ++$n) | 22 | for (my $n = 16; $n <= 19; ++$n) |
| @@ -107,5 +107,16 @@ $td->runtest("check file", | @@ -107,5 +107,16 @@ $td->runtest("check file", | ||
| 107 | {$td->FILE => "a.pdf"}, | 107 | {$td->FILE => "a.pdf"}, |
| 108 | {$td->FILE => "recover-xref-stream-recovered.pdf"}); | 108 | {$td->FILE => "recover-xref-stream-recovered.pdf"}); |
| 109 | 109 | ||
| 110 | +# Self-referential object stream | ||
| 111 | +$td->runtest("self-referential object stream", | ||
| 112 | + {$td->COMMAND => "qpdf --static-id --qdf" . | ||
| 113 | + " object-stream-self-ref.pdf a.pdf"}, | ||
| 114 | + {$td->FILE => "object-stream-self-ref.out", $td->EXIT_STATUS => 3}, | ||
| 115 | + $td->NORMALIZE_NEWLINES); | ||
| 116 | +$td->runtest("check file", | ||
| 117 | + {$td->FILE => "a.pdf"}, | ||
| 118 | + {$td->FILE => "object-stream-self-ref.out.pdf"}); | ||
| 119 | + | ||
| 120 | + | ||
| 110 | cleanup(); | 121 | cleanup(); |
| 111 | $td->report(calc_ntests($n_tests, $n_compare_pdfs)); | 122 | $td->report(calc_ntests($n_tests, $n_compare_pdfs)); |
qpdf/qtest/qpdf/object-stream-self-ref.out
0 โ 100644
qpdf/qtest/qpdf/object-stream-self-ref.out.pdf
0 โ 100644
No preview for this file type
qpdf/qtest/qpdf/object-stream-self-ref.pdf
0 โ 100644
No preview for this file type