Commit 0a6ab1060f90a10ba93dab2516a33436ab5fd55d
Committed by
GitHub
Merge pull request #1275 from m-holger/fuzz
In qpdf_fuzzer and dct_fuzzer add a scan limit for Pl_DCT
Showing
4 changed files
with
30 additions
and
0 deletions
fuzz/dct_fuzzer.cc
| @@ -31,6 +31,7 @@ FuzzHelper::doChecks() | @@ -31,6 +31,7 @@ FuzzHelper::doChecks() | ||
| 31 | // jpeg_start_decompress is called. During normal use of qpdf very large JPEGs can occasionally | 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. | 32 | // occur legitimately and therefore must be allowed during normal operations. |
| 33 | Pl_DCT::setMemoryLimit(200'000'000); | 33 | Pl_DCT::setMemoryLimit(200'000'000); |
| 34 | + Pl_DCT::setScanLimit(50); | ||
| 34 | 35 | ||
| 35 | // Do not decompress corrupt data. This may cause extended runtime within jpeglib without | 36 | // Do not decompress corrupt data. This may cause extended runtime within jpeglib without |
| 36 | // exercising additional code paths in qpdf. | 37 | // exercising additional code paths in qpdf. |
fuzz/qpdf_fuzzer.cc
| @@ -181,6 +181,7 @@ FuzzHelper::doChecks() | @@ -181,6 +181,7 @@ FuzzHelper::doChecks() | ||
| 181 | // jpeg_start_decompress is called. During normal use of qpdf very large JPEGs can occasionally | 181 | // jpeg_start_decompress is called. During normal use of qpdf very large JPEGs can occasionally |
| 182 | // occur legitimately and therefore must be allowed during normal operations. | 182 | // occur legitimately and therefore must be allowed during normal operations. |
| 183 | Pl_DCT::setMemoryLimit(100'000'000); | 183 | Pl_DCT::setMemoryLimit(100'000'000); |
| 184 | + Pl_DCT::setScanLimit(50); | ||
| 184 | 185 | ||
| 185 | Pl_PNGFilter::setMemoryLimit(1'000'000); | 186 | Pl_PNGFilter::setMemoryLimit(1'000'000); |
| 186 | Pl_TIFFPredictor::setMemoryLimit(1'000'000); | 187 | Pl_TIFFPredictor::setMemoryLimit(1'000'000); |
include/qpdf/Pl_DCT.hh
| @@ -39,6 +39,11 @@ class QPDF_DLL_CLASS Pl_DCT: public Pipeline | @@ -39,6 +39,11 @@ class QPDF_DLL_CLASS Pl_DCT: public Pipeline | ||
| 39 | QPDF_DLL | 39 | QPDF_DLL |
| 40 | static void setMemoryLimit(long limit); | 40 | static void setMemoryLimit(long limit); |
| 41 | 41 | ||
| 42 | + // Limit the number of scans used by jpeglib when decompressing progressive jpegs. | ||
| 43 | + // NB This is a static option affecting all Pl_DCT instances. | ||
| 44 | + QPDF_DLL | ||
| 45 | + static void setScanLimit(int limit); | ||
| 46 | + | ||
| 42 | // Treat corrupt data as a runtime error rather than attempting to decompress regardless. This | 47 | // 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 | 48 | // is the qpdf default behaviour. To attempt to decompress corrupt data set 'treat_as_error' to |
| 44 | // false. | 49 | // false. |
libqpdf/Pl_DCT.cc
| @@ -22,6 +22,7 @@ namespace | @@ -22,6 +22,7 @@ namespace | ||
| 22 | }; | 22 | }; |
| 23 | 23 | ||
| 24 | long memory_limit{0}; | 24 | long memory_limit{0}; |
| 25 | + int scan_limit{0}; | ||
| 25 | bool throw_on_corrupt_data{true}; | 26 | bool throw_on_corrupt_data{true}; |
| 26 | } // namespace | 27 | } // namespace |
| 27 | 28 | ||
| @@ -45,6 +46,17 @@ emit_message(j_common_ptr cinfo, int msg_level) | @@ -45,6 +46,17 @@ emit_message(j_common_ptr cinfo, int msg_level) | ||
| 45 | } | 46 | } |
| 46 | } | 47 | } |
| 47 | 48 | ||
| 49 | +static void | ||
| 50 | +progress_monitor(j_common_ptr cinfo) | ||
| 51 | +{ | ||
| 52 | + if (cinfo->is_decompressor && | ||
| 53 | + reinterpret_cast<jpeg_decompress_struct*>(cinfo)->input_scan_number > scan_limit) { | ||
| 54 | + auto* jerr = reinterpret_cast<qpdf_jpeg_error_mgr*>(cinfo->err); | ||
| 55 | + jerr->msg = "Pl_DCT::decompress: JPEG data has too many scans"; | ||
| 56 | + longjmp(jerr->jmpbuf, 1); | ||
| 57 | + } | ||
| 58 | +} | ||
| 59 | + | ||
| 48 | Pl_DCT::Members::Members() : | 60 | Pl_DCT::Members::Members() : |
| 49 | action(a_decompress), | 61 | action(a_decompress), |
| 50 | buf("DCT compressed image") | 62 | buf("DCT compressed image") |
| @@ -75,6 +87,12 @@ Pl_DCT::setMemoryLimit(long limit) | @@ -75,6 +87,12 @@ Pl_DCT::setMemoryLimit(long limit) | ||
| 75 | } | 87 | } |
| 76 | 88 | ||
| 77 | void | 89 | void |
| 90 | +Pl_DCT::setScanLimit(int limit) | ||
| 91 | +{ | ||
| 92 | + scan_limit = limit; | ||
| 93 | +} | ||
| 94 | + | ||
| 95 | +void | ||
| 78 | Pl_DCT::setThrowOnCorruptData(bool treat_as_error) | 96 | Pl_DCT::setThrowOnCorruptData(bool treat_as_error) |
| 79 | { | 97 | { |
| 80 | throw_on_corrupt_data = treat_as_error; | 98 | throw_on_corrupt_data = treat_as_error; |
| @@ -341,6 +359,11 @@ Pl_DCT::decompress(void* cinfo_p, Buffer* b) | @@ -341,6 +359,11 @@ Pl_DCT::decompress(void* cinfo_p, Buffer* b) | ||
| 341 | // first warning is encountered causing a timeout in oss-fuzz. | 359 | // first warning is encountered causing a timeout in oss-fuzz. |
| 342 | throw std::runtime_error("Pl_DCT::decompress: JPEG data large - may be too slow"); | 360 | throw std::runtime_error("Pl_DCT::decompress: JPEG data large - may be too slow"); |
| 343 | } | 361 | } |
| 362 | + jpeg_progress_mgr progress_mgr; | ||
| 363 | + if (scan_limit > 0) { | ||
| 364 | + progress_mgr.progress_monitor = &progress_monitor; | ||
| 365 | + cinfo->progress = &progress_mgr; | ||
| 366 | + } | ||
| 344 | JSAMPARRAY buffer = | 367 | JSAMPARRAY buffer = |
| 345 | (*cinfo->mem->alloc_sarray)(reinterpret_cast<j_common_ptr>(cinfo), JPOOL_IMAGE, width, 1); | 368 | (*cinfo->mem->alloc_sarray)(reinterpret_cast<j_common_ptr>(cinfo), JPOOL_IMAGE, width, 1); |
| 346 | 369 |