Commit 671b6e2ecf883b71e098785b77dd877564c8d1b0
1 parent
ad3ecadf
Limit memory usage of Pl_Runlength during fuzzing
Fixes oss-fuzz case 394129398. Issue arose from chaining multiple runlength filters and inflating a compressed stream of ~100 bytes to several gigabytes. There is no obvious fix without imposing an arbitrary implementation limit and therefore potentially excluding valid PDF files.
Showing
15 changed files
with
38 additions
and
4 deletions
fuzz/CMakeLists.txt
| ... | ... | @@ -147,10 +147,12 @@ set(CORPUS_OTHER |
| 147 | 147 | 369662293.fuzz |
| 148 | 148 | 369662293a.fuzz |
| 149 | 149 | 376305073.fuzz |
| 150 | + 376305073a.fuzz | |
| 150 | 151 | 377977949.fuzz |
| 151 | 152 | 389339260.fuzz |
| 152 | 153 | 389974979.fuzz |
| 153 | 154 | 391974927.fuzz |
| 155 | + 394129398.fuzz | |
| 154 | 156 | ) |
| 155 | 157 | |
| 156 | 158 | set(CORPUS_DIR ${CMAKE_CURRENT_BINARY_DIR}/qpdf_corpus) | ... | ... |
fuzz/qpdf_crypt_fuzzer.cc
| ... | ... | @@ -4,6 +4,7 @@ |
| 4 | 4 | #include <qpdf/Pl_Discard.hh> |
| 5 | 5 | #include <qpdf/Pl_Flate.hh> |
| 6 | 6 | #include <qpdf/Pl_PNGFilter.hh> |
| 7 | +#include <qpdf/Pl_RunLength.hh> | |
| 7 | 8 | #include <qpdf/Pl_TIFFPredictor.hh> |
| 8 | 9 | #include <qpdf/QPDF.hh> |
| 9 | 10 | #include <qpdf/QPDFPageObjectHelper.hh> |
| ... | ... | @@ -108,6 +109,7 @@ FuzzHelper::doChecks() |
| 108 | 109 | Pl_DCT::setScanLimit(50); |
| 109 | 110 | |
| 110 | 111 | Pl_PNGFilter::setMemoryLimit(1'000'000); |
| 112 | + Pl_RunLength::setMemoryLimit(1'000'000); | |
| 111 | 113 | Pl_TIFFPredictor::setMemoryLimit(1'000'000); |
| 112 | 114 | Pl_Flate::setMemoryLimit(200'000); |
| 113 | 115 | ... | ... |
fuzz/qpdf_crypt_insecure_fuzzer.cc
| ... | ... | @@ -4,6 +4,7 @@ |
| 4 | 4 | #include <qpdf/Pl_Discard.hh> |
| 5 | 5 | #include <qpdf/Pl_Flate.hh> |
| 6 | 6 | #include <qpdf/Pl_PNGFilter.hh> |
| 7 | +#include <qpdf/Pl_RunLength.hh> | |
| 7 | 8 | #include <qpdf/Pl_TIFFPredictor.hh> |
| 8 | 9 | #include <qpdf/QPDF.hh> |
| 9 | 10 | #include <qpdf/QPDFPageObjectHelper.hh> |
| ... | ... | @@ -108,6 +109,7 @@ FuzzHelper::doChecks() |
| 108 | 109 | Pl_DCT::setScanLimit(50); |
| 109 | 110 | |
| 110 | 111 | Pl_PNGFilter::setMemoryLimit(1'000'000); |
| 112 | + Pl_RunLength::setMemoryLimit(1'000'000); | |
| 111 | 113 | Pl_TIFFPredictor::setMemoryLimit(1'000'000); |
| 112 | 114 | Pl_Flate::setMemoryLimit(200'000); |
| 113 | 115 | ... | ... |
fuzz/qpdf_extra/376305073a.fuzz
0 → 100644
No preview for this file type
fuzz/qpdf_extra/394129398.fuzz
0 → 100644
No preview for this file type
fuzz/qpdf_fuzzer.cc
| ... | ... | @@ -4,6 +4,7 @@ |
| 4 | 4 | #include <qpdf/Pl_Discard.hh> |
| 5 | 5 | #include <qpdf/Pl_Flate.hh> |
| 6 | 6 | #include <qpdf/Pl_PNGFilter.hh> |
| 7 | +#include <qpdf/Pl_RunLength.hh> | |
| 7 | 8 | #include <qpdf/Pl_TIFFPredictor.hh> |
| 8 | 9 | #include <qpdf/QPDF.hh> |
| 9 | 10 | #include <qpdf/QPDFPageObjectHelper.hh> |
| ... | ... | @@ -106,6 +107,7 @@ FuzzHelper::doChecks() |
| 106 | 107 | Pl_DCT::setScanLimit(50); |
| 107 | 108 | |
| 108 | 109 | Pl_PNGFilter::setMemoryLimit(1'000'000); |
| 110 | + Pl_RunLength::setMemoryLimit(1'000'000); | |
| 109 | 111 | Pl_TIFFPredictor::setMemoryLimit(1'000'000); |
| 110 | 112 | Pl_Flate::setMemoryLimit(200'000); |
| 111 | 113 | ... | ... |
fuzz/qpdf_lin_fuzzer.cc
| ... | ... | @@ -4,6 +4,7 @@ |
| 4 | 4 | #include <qpdf/Pl_Discard.hh> |
| 5 | 5 | #include <qpdf/Pl_Flate.hh> |
| 6 | 6 | #include <qpdf/Pl_PNGFilter.hh> |
| 7 | +#include <qpdf/Pl_RunLength.hh> | |
| 7 | 8 | #include <qpdf/Pl_TIFFPredictor.hh> |
| 8 | 9 | #include <qpdf/QPDF.hh> |
| 9 | 10 | #include <qpdf/QPDFPageObjectHelper.hh> |
| ... | ... | @@ -107,6 +108,7 @@ FuzzHelper::doChecks() |
| 107 | 108 | Pl_DCT::setScanLimit(50); |
| 108 | 109 | |
| 109 | 110 | Pl_PNGFilter::setMemoryLimit(1'000'000); |
| 111 | + Pl_RunLength::setMemoryLimit(1'000'000); | |
| 110 | 112 | Pl_TIFFPredictor::setMemoryLimit(1'000'000); |
| 111 | 113 | Pl_Flate::setMemoryLimit(200'000); |
| 112 | 114 | ... | ... |
fuzz/qpdf_outlines_fuzzer.cc
| ... | ... | @@ -4,6 +4,7 @@ |
| 4 | 4 | #include <qpdf/Pl_Discard.hh> |
| 5 | 5 | #include <qpdf/Pl_Flate.hh> |
| 6 | 6 | #include <qpdf/Pl_PNGFilter.hh> |
| 7 | +#include <qpdf/Pl_RunLength.hh> | |
| 7 | 8 | #include <qpdf/Pl_TIFFPredictor.hh> |
| 8 | 9 | #include <qpdf/QPDF.hh> |
| 9 | 10 | #include <qpdf/QPDFOutlineDocumentHelper.hh> |
| ... | ... | @@ -84,6 +85,7 @@ FuzzHelper::doChecks() |
| 84 | 85 | Pl_DCT::setScanLimit(50); |
| 85 | 86 | |
| 86 | 87 | Pl_PNGFilter::setMemoryLimit(1'000'000); |
| 88 | + Pl_RunLength::setMemoryLimit(1'000'000); | |
| 87 | 89 | Pl_TIFFPredictor::setMemoryLimit(1'000'000); |
| 88 | 90 | Pl_Flate::setMemoryLimit(200'000); |
| 89 | 91 | ... | ... |
fuzz/qpdf_pages_fuzzer.cc
| ... | ... | @@ -4,6 +4,7 @@ |
| 4 | 4 | #include <qpdf/Pl_Discard.hh> |
| 5 | 5 | #include <qpdf/Pl_Flate.hh> |
| 6 | 6 | #include <qpdf/Pl_PNGFilter.hh> |
| 7 | +#include <qpdf/Pl_RunLength.hh> | |
| 7 | 8 | #include <qpdf/Pl_TIFFPredictor.hh> |
| 8 | 9 | #include <qpdf/QPDF.hh> |
| 9 | 10 | #include <qpdf/QPDFAcroFormDocumentHelper.hh> |
| ... | ... | @@ -105,6 +106,7 @@ FuzzHelper::doChecks() |
| 105 | 106 | Pl_DCT::setScanLimit(50); |
| 106 | 107 | |
| 107 | 108 | Pl_PNGFilter::setMemoryLimit(1'000'000); |
| 109 | + Pl_RunLength::setMemoryLimit(1'000'000); | |
| 108 | 110 | Pl_TIFFPredictor::setMemoryLimit(1'000'000); |
| 109 | 111 | Pl_Flate::setMemoryLimit(200'000); |
| 110 | 112 | ... | ... |
fuzz/qtest/fuzz.test
| ... | ... | @@ -11,7 +11,7 @@ my $td = new TestDriver('fuzz'); |
| 11 | 11 | |
| 12 | 12 | my $qpdf_corpus = $ENV{'QPDF_FUZZ_CORPUS'} || die "must set QPDF_FUZZ_CORPUS"; |
| 13 | 13 | |
| 14 | -my $n_qpdf_files = 88; # increment when adding new files | |
| 14 | +my $n_qpdf_files = 90; # increment when adding new files | |
| 15 | 15 | |
| 16 | 16 | my @fuzzers = ( |
| 17 | 17 | ['ascii85' => 1], | ... | ... |
fuzz/runlength_fuzzer.cc
| ... | ... | @@ -25,6 +25,7 @@ FuzzHelper::FuzzHelper(unsigned char const* data, size_t size) : |
| 25 | 25 | void |
| 26 | 26 | FuzzHelper::doChecks() |
| 27 | 27 | { |
| 28 | + Pl_RunLength::setMemoryLimit(1'000'000); | |
| 28 | 29 | Pl_Discard discard; |
| 29 | 30 | Pl_RunLength p("decode", &discard, Pl_RunLength::a_decode); |
| 30 | 31 | p.write(const_cast<unsigned char*>(data), size); | ... | ... |
include/qpdf/Pl_Flate.hh
| ... | ... | @@ -46,7 +46,7 @@ class QPDF_DLL_CLASS Pl_Flate: public Pipeline |
| 46 | 46 | ~Pl_Flate() override; |
| 47 | 47 | |
| 48 | 48 | // Limit the memory used. |
| 49 | - // NB This is a static option affecting all Pl_PNGFilter instances. | |
| 49 | + // NB This is a static option affecting all Pl_Flate instances. | |
| 50 | 50 | QPDF_DLL |
| 51 | 51 | static void setMemoryLimit(unsigned long long limit); |
| 52 | 52 | ... | ... |
include/qpdf/Pl_RunLength.hh
| ... | ... | @@ -32,6 +32,11 @@ class QPDF_DLL_CLASS Pl_RunLength: public Pipeline |
| 32 | 32 | QPDF_DLL |
| 33 | 33 | ~Pl_RunLength() override; |
| 34 | 34 | |
| 35 | + // Limit the memory used. | |
| 36 | + // NB This is a static option affecting all Pl_RunLength instances. | |
| 37 | + QPDF_DLL | |
| 38 | + static void setMemoryLimit(unsigned long long limit); | |
| 39 | + | |
| 35 | 40 | QPDF_DLL |
| 36 | 41 | void write(unsigned char const* data, size_t len) override; |
| 37 | 42 | QPDF_DLL | ... | ... |
libqpdf/Pl_RunLength.cc
| ... | ... | @@ -3,6 +3,11 @@ |
| 3 | 3 | #include <qpdf/QTC.hh> |
| 4 | 4 | #include <qpdf/QUtil.hh> |
| 5 | 5 | |
| 6 | +namespace | |
| 7 | +{ | |
| 8 | + unsigned long long memory_limit{0}; | |
| 9 | +} // namespace | |
| 10 | + | |
| 6 | 11 | Pl_RunLength::Members::Members(action_e action) : |
| 7 | 12 | action(action) |
| 8 | 13 | { |
| ... | ... | @@ -17,6 +22,12 @@ Pl_RunLength::Pl_RunLength(char const* identifier, Pipeline* next, action_e acti |
| 17 | 22 | } |
| 18 | 23 | } |
| 19 | 24 | |
| 25 | +void | |
| 26 | +Pl_RunLength::setMemoryLimit(unsigned long long limit) | |
| 27 | +{ | |
| 28 | + memory_limit = limit; | |
| 29 | +} | |
| 30 | + | |
| 20 | 31 | Pl_RunLength::~Pl_RunLength() // NOLINT (modernize-use-equals-default) |
| 21 | 32 | { |
| 22 | 33 | // Must be explicit and not inline -- see QPDF_DLL_CLASS in README-maintainer |
| ... | ... | @@ -67,6 +78,9 @@ Pl_RunLength::encode(unsigned char const* data, size_t len) |
| 67 | 78 | void |
| 68 | 79 | Pl_RunLength::decode(unsigned char const* data, size_t len) |
| 69 | 80 | { |
| 81 | + if (memory_limit && (len + m->out.size()) > memory_limit) { | |
| 82 | + throw std::runtime_error("Pl_RunLength memory limit exceeded"); | |
| 83 | + } | |
| 70 | 84 | m->out.reserve(len); |
| 71 | 85 | for (size_t i = 0; i < len; ++i) { |
| 72 | 86 | unsigned char const& ch = data[i]; | ... | ... |
libqpdf/QPDFWriter.cc
| ... | ... | @@ -1877,8 +1877,8 @@ QPDFWriter::generateID() |
| 1877 | 1877 | if (m->deterministic_id_data.empty()) { |
| 1878 | 1878 | QTC::TC("qpdf", "QPDFWriter deterministic with no data"); |
| 1879 | 1879 | throw std::runtime_error("INTERNAL ERROR: QPDFWriter::generateID has no data for " |
| 1880 | - "deterministic ID. This may happen if deterministic ID and " | |
| 1881 | - "file encryption are requested together."); | |
| 1880 | + "deterministic ID. This may happen if deterministic ID " | |
| 1881 | + "and file encryption are requested together."); | |
| 1882 | 1882 | } |
| 1883 | 1883 | seed += m->deterministic_id_data; |
| 1884 | 1884 | } else { | ... | ... |