Commit 466169d0cb2d735fb745026ceafc7333028da045
Committed by
GitHub
Merge pull request #1350 from m-holger/fuzz
Apply sanity checks on SF_FlateLzwDecode parameters
Showing
15 changed files
with
78 additions
and
67 deletions
.git-blame-ignore-revs
fuzz/CMakeLists.txt
fuzz/qpdf_crypt_fuzzer.cc
| ... | ... | @@ -111,7 +111,7 @@ FuzzHelper::doChecks() |
| 111 | 111 | Pl_PNGFilter::setMemoryLimit(1'000'000); |
| 112 | 112 | Pl_RunLength::setMemoryLimit(1'000'000); |
| 113 | 113 | Pl_TIFFPredictor::setMemoryLimit(1'000'000); |
| 114 | - Pl_Flate::setMemoryLimit(200'000); | |
| 114 | + Pl_Flate::memory_limit(200'000); | |
| 115 | 115 | |
| 116 | 116 | // Do not decompress corrupt data. This may cause extended runtime within jpeglib without |
| 117 | 117 | // exercising additional code paths in qpdf, and potentially causing counterproductive timeouts. | ... | ... |
fuzz/qpdf_crypt_insecure_fuzzer.cc
| ... | ... | @@ -111,7 +111,7 @@ FuzzHelper::doChecks() |
| 111 | 111 | Pl_PNGFilter::setMemoryLimit(1'000'000); |
| 112 | 112 | Pl_RunLength::setMemoryLimit(1'000'000); |
| 113 | 113 | Pl_TIFFPredictor::setMemoryLimit(1'000'000); |
| 114 | - Pl_Flate::setMemoryLimit(200'000); | |
| 114 | + Pl_Flate::memory_limit(200'000); | |
| 115 | 115 | |
| 116 | 116 | // Do not decompress corrupt data. This may cause extended runtime within jpeglib without |
| 117 | 117 | // exercising additional code paths in qpdf, and potentially causing counterproductive timeouts. | ... | ... |
fuzz/qpdf_extra/394463491.fuzz
0 → 100644
No preview for this file type
fuzz/qpdf_fuzzer.cc
| ... | ... | @@ -109,7 +109,7 @@ FuzzHelper::doChecks() |
| 109 | 109 | Pl_PNGFilter::setMemoryLimit(1'000'000); |
| 110 | 110 | Pl_RunLength::setMemoryLimit(1'000'000); |
| 111 | 111 | Pl_TIFFPredictor::setMemoryLimit(1'000'000); |
| 112 | - Pl_Flate::setMemoryLimit(200'000); | |
| 112 | + Pl_Flate::memory_limit(200'000); | |
| 113 | 113 | |
| 114 | 114 | // Do not decompress corrupt data. This may cause extended runtime within jpeglib without |
| 115 | 115 | // exercising additional code paths in qpdf, and potentially causing counterproductive timeouts. | ... | ... |
fuzz/qpdf_lin_fuzzer.cc
| ... | ... | @@ -110,7 +110,7 @@ FuzzHelper::doChecks() |
| 110 | 110 | Pl_PNGFilter::setMemoryLimit(1'000'000); |
| 111 | 111 | Pl_RunLength::setMemoryLimit(1'000'000); |
| 112 | 112 | Pl_TIFFPredictor::setMemoryLimit(1'000'000); |
| 113 | - Pl_Flate::setMemoryLimit(200'000); | |
| 113 | + Pl_Flate::memory_limit(200'000); | |
| 114 | 114 | |
| 115 | 115 | // Do not decompress corrupt data. This may cause extended runtime within jpeglib without |
| 116 | 116 | // exercising additional code paths in qpdf, and potentially causing counterproductive timeouts. | ... | ... |
fuzz/qpdf_outlines_fuzzer.cc
| ... | ... | @@ -87,7 +87,7 @@ FuzzHelper::doChecks() |
| 87 | 87 | Pl_PNGFilter::setMemoryLimit(1'000'000); |
| 88 | 88 | Pl_RunLength::setMemoryLimit(1'000'000); |
| 89 | 89 | Pl_TIFFPredictor::setMemoryLimit(1'000'000); |
| 90 | - Pl_Flate::setMemoryLimit(200'000); | |
| 90 | + Pl_Flate::memory_limit(200'000); | |
| 91 | 91 | |
| 92 | 92 | // Do not decompress corrupt data. This may cause extended runtime within jpeglib without |
| 93 | 93 | // exercising additional code paths in qpdf, and potentially causing counterproductive timeouts. | ... | ... |
fuzz/qpdf_pages_fuzzer.cc
| ... | ... | @@ -108,7 +108,7 @@ FuzzHelper::doChecks() |
| 108 | 108 | Pl_PNGFilter::setMemoryLimit(1'000'000); |
| 109 | 109 | Pl_RunLength::setMemoryLimit(1'000'000); |
| 110 | 110 | Pl_TIFFPredictor::setMemoryLimit(1'000'000); |
| 111 | - Pl_Flate::setMemoryLimit(200'000); | |
| 111 | + Pl_Flate::memory_limit(200'000); | |
| 112 | 112 | |
| 113 | 113 | // Do not decompress corrupt data. This may cause extended runtime within jpeglib without |
| 114 | 114 | // exercising additional code paths in qpdf, and potentially causing counterproductive timeouts. | ... | ... |
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 = 91; # increment when adding new files | |
| 14 | +my $n_qpdf_files = 92; # increment when adding new files | |
| 15 | 15 | |
| 16 | 16 | my @fuzzers = ( |
| 17 | 17 | ['ascii85' => 1], | ... | ... |
include/qpdf/Pl_Flate.hh
| ... | ... | @@ -48,7 +48,9 @@ class QPDF_DLL_CLASS Pl_Flate: public Pipeline |
| 48 | 48 | // Limit the memory used. |
| 49 | 49 | // NB This is a static option affecting all Pl_Flate instances. |
| 50 | 50 | QPDF_DLL |
| 51 | - static void setMemoryLimit(unsigned long long limit); | |
| 51 | + static unsigned long long memory_limit(); | |
| 52 | + QPDF_DLL | |
| 53 | + static void memory_limit(unsigned long long limit); | |
| 52 | 54 | |
| 53 | 55 | QPDF_DLL |
| 54 | 56 | void write(unsigned char const* data, size_t len) override; | ... | ... |
libqpdf/Pl_Flate.cc
| ... | ... | @@ -14,7 +14,7 @@ |
| 14 | 14 | |
| 15 | 15 | namespace |
| 16 | 16 | { |
| 17 | - unsigned long long memory_limit{0}; | |
| 17 | + unsigned long long memory_limit_{0}; | |
| 18 | 18 | } // namespace |
| 19 | 19 | |
| 20 | 20 | int Pl_Flate::compression_level = Z_DEFAULT_COMPRESSION; |
| ... | ... | @@ -80,10 +80,16 @@ Pl_Flate::~Pl_Flate() // NOLINT (modernize-use-equals-default) |
| 80 | 80 | // Must be explicit and not inline -- see QPDF_DLL_CLASS in README-maintainer |
| 81 | 81 | } |
| 82 | 82 | |
| 83 | +unsigned long long | |
| 84 | +Pl_Flate::memory_limit() | |
| 85 | +{ | |
| 86 | + return memory_limit_; | |
| 87 | +} | |
| 88 | + | |
| 83 | 89 | void |
| 84 | -Pl_Flate::setMemoryLimit(unsigned long long limit) | |
| 90 | +Pl_Flate::memory_limit(unsigned long long limit) | |
| 85 | 91 | { |
| 86 | - memory_limit = limit; | |
| 92 | + memory_limit_ = limit; | |
| 87 | 93 | } |
| 88 | 94 | |
| 89 | 95 | void |
| ... | ... | @@ -197,9 +203,9 @@ Pl_Flate::handleData(unsigned char const* data, size_t len, int flush) |
| 197 | 203 | } |
| 198 | 204 | uLong ready = QIntC::to_ulong(m->out_bufsize - zstream.avail_out); |
| 199 | 205 | if (ready > 0) { |
| 200 | - if (memory_limit && m->action != a_deflate) { | |
| 206 | + if (memory_limit_ && m->action != a_deflate) { | |
| 201 | 207 | m->written += ready; |
| 202 | - if (m->written > memory_limit) { | |
| 208 | + if (m->written > memory_limit_) { | |
| 203 | 209 | throw std::runtime_error("PL_Flate memory limit exceeded"); |
| 204 | 210 | } |
| 205 | 211 | } |
| ... | ... | @@ -220,7 +226,7 @@ Pl_Flate::handleData(unsigned char const* data, size_t len, int flush) |
| 220 | 226 | void |
| 221 | 227 | Pl_Flate::finish() |
| 222 | 228 | { |
| 223 | - if (m->written > memory_limit) { | |
| 229 | + if (m->written > memory_limit_) { | |
| 224 | 230 | throw std::runtime_error("PL_Flate memory limit exceeded"); |
| 225 | 231 | } |
| 226 | 232 | try { | ... | ... |
libqpdf/QPDFJob_argv.cc
| ... | ... | @@ -117,8 +117,10 @@ ArgParser::argZopfli() |
| 117 | 117 | logger->info("Set the environment variable QPDF_ZOPFLI to activate.\n"); |
| 118 | 118 | logger->info("* QPDF_ZOPFLI=disabled or QPDF_ZOPFLI not set: don't use zopfli.\n"); |
| 119 | 119 | logger->info("* QPDF_ZOPFLI=force: use zopfli, and fail if not available.\n"); |
| 120 | - logger->info("* QPDF_ZOPFLI=silent: use zopfli if available and silently fall back if not.\n"); | |
| 121 | - logger->info("* QPDF_ZOPFLI= any other value: use zopfli if available, and warn if not.\n"); | |
| 120 | + logger->info( | |
| 121 | + "* QPDF_ZOPFLI=silent: use zopfli if available and silently fall back if not.\n"); | |
| 122 | + logger->info( | |
| 123 | + "* QPDF_ZOPFLI= any other value: use zopfli if available, and warn if not.\n"); | |
| 122 | 124 | } |
| 123 | 125 | } else { |
| 124 | 126 | logger->error("zopfli support is not enabled\n"); | ... | ... |
libqpdf/SF_FlateLzwDecode.cc
| ... | ... | @@ -7,17 +7,6 @@ |
| 7 | 7 | #include <qpdf/QIntC.hh> |
| 8 | 8 | #include <qpdf/QTC.hh> |
| 9 | 9 | |
| 10 | -SF_FlateLzwDecode::SF_FlateLzwDecode(bool lzw) : | |
| 11 | - lzw(lzw), | |
| 12 | - // Initialize values to their defaults as per the PDF spec | |
| 13 | - predictor(1), | |
| 14 | - columns(1), | |
| 15 | - colors(1), | |
| 16 | - bits_per_component(8), | |
| 17 | - early_code_change(true) | |
| 18 | -{ | |
| 19 | -} | |
| 20 | - | |
| 21 | 10 | bool |
| 22 | 11 | SF_FlateLzwDecode::setDecodeParms(QPDFObjectHandle decode_parms) |
| 23 | 12 | { |
| ... | ... | @@ -25,78 +14,83 @@ SF_FlateLzwDecode::setDecodeParms(QPDFObjectHandle decode_parms) |
| 25 | 14 | return true; |
| 26 | 15 | } |
| 27 | 16 | |
| 28 | - bool filterable = true; | |
| 17 | + auto memory_limit = Pl_Flate::memory_limit(); | |
| 18 | + | |
| 29 | 19 | std::set<std::string> keys = decode_parms.getKeys(); |
| 30 | 20 | for (auto const& key: keys) { |
| 31 | 21 | QPDFObjectHandle value = decode_parms.getKey(key); |
| 32 | 22 | if (key == "/Predictor") { |
| 33 | 23 | if (value.isInteger()) { |
| 34 | - this->predictor = value.getIntValueAsInt(); | |
| 35 | - if (!((this->predictor == 1) || (this->predictor == 2) || | |
| 36 | - ((this->predictor >= 10) && (this->predictor <= 15)))) { | |
| 37 | - filterable = false; | |
| 24 | + predictor = value.getIntValueAsInt(); | |
| 25 | + if (!(predictor == 1 || predictor == 2 || (predictor >= 10 && predictor <= 15))) { | |
| 26 | + return false; | |
| 38 | 27 | } |
| 39 | 28 | } else { |
| 40 | - filterable = false; | |
| 29 | + return false; | |
| 41 | 30 | } |
| 42 | - } else if ((key == "/Columns") || (key == "/Colors") || (key == "/BitsPerComponent")) { | |
| 31 | + } else if (key == "/Columns" || key == "/Colors" || key == "/BitsPerComponent") { | |
| 43 | 32 | if (value.isInteger()) { |
| 44 | 33 | int val = value.getIntValueAsInt(); |
| 34 | + if (memory_limit && static_cast<unsigned int>(val) > memory_limit) { | |
| 35 | + QPDFLogger::defaultLogger()->warn( | |
| 36 | + "SF_FlateLzwDecode parameter exceeds PL_Flate memory limit\n"); | |
| 37 | + return false; | |
| 38 | + } | |
| 45 | 39 | if (key == "/Columns") { |
| 46 | - this->columns = val; | |
| 40 | + columns = val; | |
| 47 | 41 | } else if (key == "/Colors") { |
| 48 | - this->colors = val; | |
| 42 | + colors = val; | |
| 49 | 43 | } else if (key == "/BitsPerComponent") { |
| 50 | - this->bits_per_component = val; | |
| 44 | + bits_per_component = val; | |
| 51 | 45 | } |
| 52 | 46 | } else { |
| 53 | - filterable = false; | |
| 47 | + return false; | |
| 54 | 48 | } |
| 55 | 49 | } else if (lzw && (key == "/EarlyChange")) { |
| 56 | 50 | if (value.isInteger()) { |
| 57 | 51 | int earlychange = value.getIntValueAsInt(); |
| 58 | - this->early_code_change = (earlychange == 1); | |
| 59 | - if (!((earlychange == 0) || (earlychange == 1))) { | |
| 60 | - filterable = false; | |
| 52 | + early_code_change = (earlychange == 1); | |
| 53 | + if (!(earlychange == 0 || earlychange == 1)) { | |
| 54 | + return false; | |
| 61 | 55 | } |
| 62 | 56 | } else { |
| 63 | - filterable = false; | |
| 57 | + return false; | |
| 64 | 58 | } |
| 65 | 59 | } |
| 66 | 60 | } |
| 67 | 61 | |
| 68 | - if ((this->predictor > 1) && (this->columns == 0)) { | |
| 69 | - filterable = false; | |
| 62 | + if (predictor > 1 && columns == 0) { | |
| 63 | + return false; | |
| 70 | 64 | } |
| 71 | 65 | |
| 72 | - return filterable; | |
| 66 | + return true; | |
| 73 | 67 | } |
| 74 | 68 | |
| 75 | 69 | Pipeline* |
| 76 | 70 | SF_FlateLzwDecode::getDecodePipeline(Pipeline* next) |
| 77 | 71 | { |
| 78 | 72 | std::shared_ptr<Pipeline> pipeline; |
| 79 | - if ((this->predictor >= 10) && (this->predictor <= 15)) { | |
| 73 | + if (predictor >= 10 && predictor <= 15) { | |
| 80 | 74 | QTC::TC("qpdf", "SF_FlateLzwDecode PNG filter"); |
| 81 | 75 | pipeline = std::make_shared<Pl_PNGFilter>( |
| 82 | 76 | "png decode", |
| 83 | 77 | next, |
| 84 | 78 | Pl_PNGFilter::a_decode, |
| 85 | - QIntC::to_uint(this->columns), | |
| 86 | - QIntC::to_uint(this->colors), | |
| 87 | - QIntC::to_uint(this->bits_per_component)); | |
| 88 | - this->pipelines.push_back(pipeline); | |
| 79 | + QIntC::to_uint(columns), | |
| 80 | + QIntC::to_uint(colors), | |
| 81 | + QIntC::to_uint(bits_per_component)); | |
| 82 | + pipelines.push_back(pipeline); | |
| 89 | 83 | next = pipeline.get(); |
| 90 | - } else if (this->predictor == 2) { | |
| 84 | + } else if (predictor == 2) { | |
| 91 | 85 | QTC::TC("qpdf", "SF_FlateLzwDecode TIFF predictor"); |
| 92 | 86 | pipeline = std::make_shared<Pl_TIFFPredictor>( |
| 93 | 87 | "tiff decode", |
| 94 | 88 | next, |
| 95 | 89 | Pl_TIFFPredictor::a_decode, |
| 96 | - QIntC::to_uint(this->columns), | |
| 97 | - QIntC::to_uint(this->colors), | |
| 98 | - QIntC::to_uint(this->bits_per_component)); | |
| 99 | - this->pipelines.push_back(pipeline); | |
| 90 | + QIntC::to_uint(columns), | |
| 91 | + QIntC::to_uint(colors), | |
| 92 | + QIntC::to_uint(bits_per_component)); | |
| 93 | + pipelines.push_back(pipeline); | |
| 100 | 94 | next = pipeline.get(); |
| 101 | 95 | } |
| 102 | 96 | |
| ... | ... | @@ -105,7 +99,7 @@ SF_FlateLzwDecode::getDecodePipeline(Pipeline* next) |
| 105 | 99 | } else { |
| 106 | 100 | pipeline = std::make_shared<Pl_Flate>("stream inflate", next, Pl_Flate::a_inflate); |
| 107 | 101 | } |
| 108 | - this->pipelines.push_back(pipeline); | |
| 102 | + pipelines.push_back(pipeline); | |
| 109 | 103 | return pipeline.get(); |
| 110 | 104 | } |
| 111 | 105 | ... | ... |
libqpdf/qpdf/SF_FlateLzwDecode.hh
| ... | ... | @@ -5,25 +5,29 @@ |
| 5 | 5 | #ifndef SF_FLATELZWDECODE_HH |
| 6 | 6 | # define SF_FLATELZWDECODE_HH |
| 7 | 7 | |
| 8 | -class SF_FlateLzwDecode: public QPDFStreamFilter | |
| 8 | +class SF_FlateLzwDecode final: public QPDFStreamFilter | |
| 9 | 9 | { |
| 10 | 10 | public: |
| 11 | - SF_FlateLzwDecode(bool lzw); | |
| 12 | - ~SF_FlateLzwDecode() override = default; | |
| 11 | + SF_FlateLzwDecode(bool lzw) : | |
| 12 | + lzw(lzw) | |
| 13 | + { | |
| 14 | + } | |
| 15 | + ~SF_FlateLzwDecode() final = default; | |
| 13 | 16 | |
| 14 | - bool setDecodeParms(QPDFObjectHandle decode_parms) override; | |
| 15 | - Pipeline* getDecodePipeline(Pipeline* next) override; | |
| 17 | + bool setDecodeParms(QPDFObjectHandle decode_parms) final; | |
| 18 | + Pipeline* getDecodePipeline(Pipeline* next) final; | |
| 16 | 19 | |
| 17 | 20 | static std::shared_ptr<QPDFStreamFilter> flate_factory(); |
| 18 | 21 | static std::shared_ptr<QPDFStreamFilter> lzw_factory(); |
| 19 | 22 | |
| 20 | 23 | private: |
| 21 | - bool lzw; | |
| 22 | - int predictor; | |
| 23 | - int columns; | |
| 24 | - int colors; | |
| 25 | - int bits_per_component; | |
| 26 | - bool early_code_change; | |
| 24 | + bool lzw{}; | |
| 25 | + // Defaults as per the PDF spec | |
| 26 | + int predictor{1}; | |
| 27 | + int columns{1}; | |
| 28 | + int colors{1}; | |
| 29 | + int bits_per_component{8}; | |
| 30 | + bool early_code_change{true}; | |
| 27 | 31 | std::vector<std::shared_ptr<Pipeline>> pipelines; |
| 28 | 32 | }; |
| 29 | 33 | ... | ... |