Commit 466169d0cb2d735fb745026ceafc7333028da045

Authored by m-holger
Committed by GitHub
2 parents f5cac93a 1db68172

Merge pull request #1350 from m-holger/fuzz

Apply sanity checks on SF_FlateLzwDecode parameters
.git-blame-ignore-revs
... ... @@ -18,3 +18,5 @@ d740c6ccced02147f84a39d5e5f0984d12bac6cb
18 18 9ae7bdea966102f9621b22192747a891078e7470
19 19 # Normalize white space in ChangeLog
20 20 d7b909f97d3effc9540c35b0251bdf1c9abf187c
  21 +# Remove 'this->'
  22 +f5cac93ac63559ddc0aec1b1c61c9e2503c7b8e0
... ...
fuzz/CMakeLists.txt
... ... @@ -154,6 +154,7 @@ set(CORPUS_OTHER
154 154 389974979.fuzz
155 155 391974927.fuzz
156 156 394129398.fuzz
  157 + 394463491.fuzz
157 158 )
158 159  
159 160 set(CORPUS_DIR ${CMAKE_CURRENT_BINARY_DIR}/qpdf_corpus)
... ...
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  
... ...