diff --git a/fuzz/CMakeLists.txt b/fuzz/CMakeLists.txt index d692372..9a30b3b 100644 --- a/fuzz/CMakeLists.txt +++ b/fuzz/CMakeLists.txt @@ -147,10 +147,13 @@ set(CORPUS_OTHER 369662293.fuzz 369662293a.fuzz 376305073.fuzz + 376305073a.fuzz 377977949.fuzz + 388571629.fuzz 389339260.fuzz 389974979.fuzz 391974927.fuzz + 394129398.fuzz ) set(CORPUS_DIR ${CMAKE_CURRENT_BINARY_DIR}/qpdf_corpus) diff --git a/fuzz/qpdf_crypt_fuzzer.cc b/fuzz/qpdf_crypt_fuzzer.cc index cfa5763..54aba80 100644 --- a/fuzz/qpdf_crypt_fuzzer.cc +++ b/fuzz/qpdf_crypt_fuzzer.cc @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -108,6 +109,7 @@ FuzzHelper::doChecks() Pl_DCT::setScanLimit(50); Pl_PNGFilter::setMemoryLimit(1'000'000); + Pl_RunLength::setMemoryLimit(1'000'000); Pl_TIFFPredictor::setMemoryLimit(1'000'000); Pl_Flate::setMemoryLimit(200'000); diff --git a/fuzz/qpdf_crypt_insecure_fuzzer.cc b/fuzz/qpdf_crypt_insecure_fuzzer.cc index a8211f8..61c55c5 100644 --- a/fuzz/qpdf_crypt_insecure_fuzzer.cc +++ b/fuzz/qpdf_crypt_insecure_fuzzer.cc @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -108,6 +109,7 @@ FuzzHelper::doChecks() Pl_DCT::setScanLimit(50); Pl_PNGFilter::setMemoryLimit(1'000'000); + Pl_RunLength::setMemoryLimit(1'000'000); Pl_TIFFPredictor::setMemoryLimit(1'000'000); Pl_Flate::setMemoryLimit(200'000); diff --git a/fuzz/qpdf_extra/376305073a.fuzz b/fuzz/qpdf_extra/376305073a.fuzz new file mode 100644 index 0000000..f2c2654 --- /dev/null +++ b/fuzz/qpdf_extra/376305073a.fuzz diff --git a/fuzz/qpdf_extra/388571629.fuzz b/fuzz/qpdf_extra/388571629.fuzz new file mode 100644 index 0000000..31874ca --- /dev/null +++ b/fuzz/qpdf_extra/388571629.fuzz diff --git a/fuzz/qpdf_extra/394129398.fuzz b/fuzz/qpdf_extra/394129398.fuzz new file mode 100644 index 0000000..f5effae --- /dev/null +++ b/fuzz/qpdf_extra/394129398.fuzz diff --git a/fuzz/qpdf_fuzzer.cc b/fuzz/qpdf_fuzzer.cc index 82134ca..a066b13 100644 --- a/fuzz/qpdf_fuzzer.cc +++ b/fuzz/qpdf_fuzzer.cc @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -106,6 +107,7 @@ FuzzHelper::doChecks() Pl_DCT::setScanLimit(50); Pl_PNGFilter::setMemoryLimit(1'000'000); + Pl_RunLength::setMemoryLimit(1'000'000); Pl_TIFFPredictor::setMemoryLimit(1'000'000); Pl_Flate::setMemoryLimit(200'000); diff --git a/fuzz/qpdf_lin_fuzzer.cc b/fuzz/qpdf_lin_fuzzer.cc index d1e75b9..f116eec 100644 --- a/fuzz/qpdf_lin_fuzzer.cc +++ b/fuzz/qpdf_lin_fuzzer.cc @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -107,6 +108,7 @@ FuzzHelper::doChecks() Pl_DCT::setScanLimit(50); Pl_PNGFilter::setMemoryLimit(1'000'000); + Pl_RunLength::setMemoryLimit(1'000'000); Pl_TIFFPredictor::setMemoryLimit(1'000'000); Pl_Flate::setMemoryLimit(200'000); diff --git a/fuzz/qpdf_outlines_fuzzer.cc b/fuzz/qpdf_outlines_fuzzer.cc index d2404ee..f1887f7 100644 --- a/fuzz/qpdf_outlines_fuzzer.cc +++ b/fuzz/qpdf_outlines_fuzzer.cc @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -84,6 +85,7 @@ FuzzHelper::doChecks() Pl_DCT::setScanLimit(50); Pl_PNGFilter::setMemoryLimit(1'000'000); + Pl_RunLength::setMemoryLimit(1'000'000); Pl_TIFFPredictor::setMemoryLimit(1'000'000); Pl_Flate::setMemoryLimit(200'000); diff --git a/fuzz/qpdf_pages_fuzzer.cc b/fuzz/qpdf_pages_fuzzer.cc index cd2fff7..9fb3baf 100644 --- a/fuzz/qpdf_pages_fuzzer.cc +++ b/fuzz/qpdf_pages_fuzzer.cc @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -105,6 +106,7 @@ FuzzHelper::doChecks() Pl_DCT::setScanLimit(50); Pl_PNGFilter::setMemoryLimit(1'000'000); + Pl_RunLength::setMemoryLimit(1'000'000); Pl_TIFFPredictor::setMemoryLimit(1'000'000); Pl_Flate::setMemoryLimit(200'000); diff --git a/fuzz/qtest/fuzz.test b/fuzz/qtest/fuzz.test index 450cb6c..9a784d1 100644 --- a/fuzz/qtest/fuzz.test +++ b/fuzz/qtest/fuzz.test @@ -11,7 +11,7 @@ my $td = new TestDriver('fuzz'); my $qpdf_corpus = $ENV{'QPDF_FUZZ_CORPUS'} || die "must set QPDF_FUZZ_CORPUS"; -my $n_qpdf_files = 88; # increment when adding new files +my $n_qpdf_files = 91; # increment when adding new files my @fuzzers = ( ['ascii85' => 1], diff --git a/fuzz/runlength_fuzzer.cc b/fuzz/runlength_fuzzer.cc index 3ff3f58..b19cd30 100644 --- a/fuzz/runlength_fuzzer.cc +++ b/fuzz/runlength_fuzzer.cc @@ -25,6 +25,7 @@ FuzzHelper::FuzzHelper(unsigned char const* data, size_t size) : void FuzzHelper::doChecks() { + Pl_RunLength::setMemoryLimit(1'000'000); Pl_Discard discard; Pl_RunLength p("decode", &discard, Pl_RunLength::a_decode); p.write(const_cast(data), size); diff --git a/include/qpdf/Pl_Flate.hh b/include/qpdf/Pl_Flate.hh index 322d461..91c3c54 100644 --- a/include/qpdf/Pl_Flate.hh +++ b/include/qpdf/Pl_Flate.hh @@ -46,7 +46,7 @@ class QPDF_DLL_CLASS Pl_Flate: public Pipeline ~Pl_Flate() override; // Limit the memory used. - // NB This is a static option affecting all Pl_PNGFilter instances. + // NB This is a static option affecting all Pl_Flate instances. QPDF_DLL static void setMemoryLimit(unsigned long long limit); diff --git a/include/qpdf/Pl_RunLength.hh b/include/qpdf/Pl_RunLength.hh index 0ec91e9..0c15279 100644 --- a/include/qpdf/Pl_RunLength.hh +++ b/include/qpdf/Pl_RunLength.hh @@ -32,6 +32,11 @@ class QPDF_DLL_CLASS Pl_RunLength: public Pipeline QPDF_DLL ~Pl_RunLength() override; + // Limit the memory used. + // NB This is a static option affecting all Pl_RunLength instances. + QPDF_DLL + static void setMemoryLimit(unsigned long long limit); + QPDF_DLL void write(unsigned char const* data, size_t len) override; QPDF_DLL diff --git a/libqpdf/Pl_RunLength.cc b/libqpdf/Pl_RunLength.cc index 5969715..0608025 100644 --- a/libqpdf/Pl_RunLength.cc +++ b/libqpdf/Pl_RunLength.cc @@ -3,6 +3,11 @@ #include #include +namespace +{ + unsigned long long memory_limit{0}; +} // namespace + Pl_RunLength::Members::Members(action_e action) : action(action) { @@ -17,6 +22,12 @@ Pl_RunLength::Pl_RunLength(char const* identifier, Pipeline* next, action_e acti } } +void +Pl_RunLength::setMemoryLimit(unsigned long long limit) +{ + memory_limit = limit; +} + Pl_RunLength::~Pl_RunLength() // NOLINT (modernize-use-equals-default) { // 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) void Pl_RunLength::decode(unsigned char const* data, size_t len) { + if (memory_limit && (len + m->out.size()) > memory_limit) { + throw std::runtime_error("Pl_RunLength memory limit exceeded"); + } m->out.reserve(len); for (size_t i = 0; i < len; ++i) { unsigned char const& ch = data[i]; diff --git a/libqpdf/QPDFParser.cc b/libqpdf/QPDFParser.cc index 3f176c5..2763112 100644 --- a/libqpdf/QPDFParser.cc +++ b/libqpdf/QPDFParser.cc @@ -470,6 +470,11 @@ bool QPDFParser::tooManyBadTokens() { if (--max_bad_count > 0 && good_count > 4) { + if (frame->olist.size() > 100'000 || frame->dict.size() > 100'000) { + warn("encountered errors while parsing an array or dictionary with more than 100000 " + "elements; giving up on reading object"); + return true; + } good_count = 0; bad_count = 1; return false; diff --git a/libqpdf/QPDFWriter.cc b/libqpdf/QPDFWriter.cc index ac956a8..39f7ad6 100644 --- a/libqpdf/QPDFWriter.cc +++ b/libqpdf/QPDFWriter.cc @@ -1877,8 +1877,8 @@ QPDFWriter::generateID() if (m->deterministic_id_data.empty()) { QTC::TC("qpdf", "QPDFWriter deterministic with no data"); throw std::runtime_error("INTERNAL ERROR: QPDFWriter::generateID has no data for " - "deterministic ID. This may happen if deterministic ID and " - "file encryption are requested together."); + "deterministic ID. This may happen if deterministic ID " + "and file encryption are requested together."); } seed += m->deterministic_id_data; } else {