2
1
mirror of https://github.com/qpdf/qpdf.git synced 2025-02-14 09:40:22 +00:00

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.
This commit is contained in:
m-holger 2025-02-04 13:15:02 +00:00
parent ad3ecadf05
commit 671b6e2ecf
15 changed files with 38 additions and 4 deletions

View File

@ -147,10 +147,12 @@ set(CORPUS_OTHER
369662293.fuzz
369662293a.fuzz
376305073.fuzz
376305073a.fuzz
377977949.fuzz
389339260.fuzz
389974979.fuzz
391974927.fuzz
394129398.fuzz
)
set(CORPUS_DIR ${CMAKE_CURRENT_BINARY_DIR}/qpdf_corpus)

View File

@ -4,6 +4,7 @@
#include <qpdf/Pl_Discard.hh>
#include <qpdf/Pl_Flate.hh>
#include <qpdf/Pl_PNGFilter.hh>
#include <qpdf/Pl_RunLength.hh>
#include <qpdf/Pl_TIFFPredictor.hh>
#include <qpdf/QPDF.hh>
#include <qpdf/QPDFPageObjectHelper.hh>
@ -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);

View File

@ -4,6 +4,7 @@
#include <qpdf/Pl_Discard.hh>
#include <qpdf/Pl_Flate.hh>
#include <qpdf/Pl_PNGFilter.hh>
#include <qpdf/Pl_RunLength.hh>
#include <qpdf/Pl_TIFFPredictor.hh>
#include <qpdf/QPDF.hh>
#include <qpdf/QPDFPageObjectHelper.hh>
@ -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);

Binary file not shown.

Binary file not shown.

View File

@ -4,6 +4,7 @@
#include <qpdf/Pl_Discard.hh>
#include <qpdf/Pl_Flate.hh>
#include <qpdf/Pl_PNGFilter.hh>
#include <qpdf/Pl_RunLength.hh>
#include <qpdf/Pl_TIFFPredictor.hh>
#include <qpdf/QPDF.hh>
#include <qpdf/QPDFPageObjectHelper.hh>
@ -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);

View File

@ -4,6 +4,7 @@
#include <qpdf/Pl_Discard.hh>
#include <qpdf/Pl_Flate.hh>
#include <qpdf/Pl_PNGFilter.hh>
#include <qpdf/Pl_RunLength.hh>
#include <qpdf/Pl_TIFFPredictor.hh>
#include <qpdf/QPDF.hh>
#include <qpdf/QPDFPageObjectHelper.hh>
@ -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);

View File

@ -4,6 +4,7 @@
#include <qpdf/Pl_Discard.hh>
#include <qpdf/Pl_Flate.hh>
#include <qpdf/Pl_PNGFilter.hh>
#include <qpdf/Pl_RunLength.hh>
#include <qpdf/Pl_TIFFPredictor.hh>
#include <qpdf/QPDF.hh>
#include <qpdf/QPDFOutlineDocumentHelper.hh>
@ -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);

View File

@ -4,6 +4,7 @@
#include <qpdf/Pl_Discard.hh>
#include <qpdf/Pl_Flate.hh>
#include <qpdf/Pl_PNGFilter.hh>
#include <qpdf/Pl_RunLength.hh>
#include <qpdf/Pl_TIFFPredictor.hh>
#include <qpdf/QPDF.hh>
#include <qpdf/QPDFAcroFormDocumentHelper.hh>
@ -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);

View File

@ -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 = 90; # increment when adding new files
my @fuzzers = (
['ascii85' => 1],

View File

@ -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<unsigned char*>(data), size);

View File

@ -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);

View File

@ -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

View File

@ -3,6 +3,11 @@
#include <qpdf/QTC.hh>
#include <qpdf/QUtil.hh>
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];

View File

@ -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 {