Commit 8f455ffa6a177a61e12ad087c2cf916b6209c5da
Committed by
GitHub
Merge pull request #1590 from m-holger/i1475
Add a limit on the maximum number of filters allowed when filtering streams.
Showing
19 changed files
with
157 additions
and
17 deletions
include/qpdf/Constants.h
| @@ -287,6 +287,9 @@ enum qpdf_param_e { | @@ -287,6 +287,9 @@ enum qpdf_param_e { | ||
| 287 | qpdf_p_parser_max_container_size, | 287 | qpdf_p_parser_max_container_size, |
| 288 | qpdf_p_parser_max_container_size_damaged, | 288 | qpdf_p_parser_max_container_size_damaged, |
| 289 | 289 | ||
| 290 | + /* stream and filter limits */ | ||
| 291 | + qpdf_p_max_stream_filters = 0x14000, | ||
| 292 | + | ||
| 290 | /* next section = 0x20000 */ | 293 | /* next section = 0x20000 */ |
| 291 | qpdf_enum_max = 0x7fffffff, | 294 | qpdf_enum_max = 0x7fffffff, |
| 292 | }; | 295 | }; |
include/qpdf/auto_job_c_global.hh
| @@ -10,3 +10,4 @@ QPDF_DLL GlobalConfig* parserMaxContainerSize(std::string const& parameter); | @@ -10,3 +10,4 @@ QPDF_DLL GlobalConfig* parserMaxContainerSize(std::string const& parameter); | ||
| 10 | QPDF_DLL GlobalConfig* parserMaxContainerSizeDamaged(std::string const& parameter); | 10 | QPDF_DLL GlobalConfig* parserMaxContainerSizeDamaged(std::string const& parameter); |
| 11 | QPDF_DLL GlobalConfig* parserMaxErrors(std::string const& parameter); | 11 | QPDF_DLL GlobalConfig* parserMaxErrors(std::string const& parameter); |
| 12 | QPDF_DLL GlobalConfig* parserMaxNesting(std::string const& parameter); | 12 | QPDF_DLL GlobalConfig* parserMaxNesting(std::string const& parameter); |
| 13 | +QPDF_DLL GlobalConfig* maxStreamFilters(std::string const& parameter); |
include/qpdf/global.hh
| @@ -56,7 +56,7 @@ namespace qpdf::global | @@ -56,7 +56,7 @@ namespace qpdf::global | ||
| 56 | 56 | ||
| 57 | /// @brief Retrieves the number of limit errors. | 57 | /// @brief Retrieves the number of limit errors. |
| 58 | /// | 58 | /// |
| 59 | - /// Returns the number a global limit was exceeded. This item is read only. | 59 | + /// Returns the number of times a global limit was exceeded. This item is read only. |
| 60 | /// | 60 | /// |
| 61 | /// @return The number of limit errors. | 61 | /// @return The number of limit errors. |
| 62 | /// | 62 | /// |
| @@ -229,6 +229,34 @@ namespace qpdf::global | @@ -229,6 +229,34 @@ namespace qpdf::global | ||
| 229 | { | 229 | { |
| 230 | set_uint32(qpdf_p_parser_max_container_size_damaged, value); | 230 | set_uint32(qpdf_p_parser_max_container_size_damaged, value); |
| 231 | } | 231 | } |
| 232 | + | ||
| 233 | + /// @brief Retrieves the maximum number of filters allowed when filtering streams. | ||
| 234 | + /// | ||
| 235 | + /// An excessive number of stream filters is usually a sign that a file is damaged or | ||
| 236 | + /// specially constructed. If the maximum is exceeded for a stream the stream is treated as | ||
| 237 | + /// unfilterable. The default maximum is 25. | ||
| 238 | + /// | ||
| 239 | + /// @return The maximum number of filters allowed when filtering streams. | ||
| 240 | + /// | ||
| 241 | + /// @since 12.3 | ||
| 242 | + uint32_t inline max_stream_filters() | ||
| 243 | + { | ||
| 244 | + return get_uint32(qpdf_p_max_stream_filters); | ||
| 245 | + } | ||
| 246 | + | ||
| 247 | + /// @brief Sets the maximum number of filters allowed when filtering streams. | ||
| 248 | + /// | ||
| 249 | + /// An excessive number of stream filters is usually a sign that a file is damaged or | ||
| 250 | + /// specially constructed. If the maximum is exceeded for a stream the stream is treated as | ||
| 251 | + /// unfilterable. The default maximum is 25. | ||
| 252 | + /// | ||
| 253 | + /// @param value The maximum number of filters allowed when filtering streams to set. | ||
| 254 | + /// | ||
| 255 | + /// @since 12.3 | ||
| 256 | + void inline max_stream_filters(uint32_t value) | ||
| 257 | + { | ||
| 258 | + set_uint32(qpdf_p_max_stream_filters, value); | ||
| 259 | + } | ||
| 232 | } // namespace limits | 260 | } // namespace limits |
| 233 | 261 | ||
| 234 | } // namespace qpdf::global | 262 | } // namespace qpdf::global |
job.sums
| @@ -4,18 +4,18 @@ generate_auto_job 8e3175a515aa8837d8a01bba0346b04b3d777d70330ba5b7d52f691316054a | @@ -4,18 +4,18 @@ generate_auto_job 8e3175a515aa8837d8a01bba0346b04b3d777d70330ba5b7d52f691316054a | ||
| 4 | include/qpdf/auto_job_c_att.hh 4c2b171ea00531db54720bf49a43f8b34481586ae7fb6cbf225099ee42bc5bb4 | 4 | include/qpdf/auto_job_c_att.hh 4c2b171ea00531db54720bf49a43f8b34481586ae7fb6cbf225099ee42bc5bb4 |
| 5 | include/qpdf/auto_job_c_copy_att.hh 50609012bff14fd82f0649185940d617d05d530cdc522185c7f3920a561ccb42 | 5 | include/qpdf/auto_job_c_copy_att.hh 50609012bff14fd82f0649185940d617d05d530cdc522185c7f3920a561ccb42 |
| 6 | include/qpdf/auto_job_c_enc.hh 28446f3c32153a52afa239ea40503e6cc8ac2c026813526a349e0cd4ae17ddd5 | 6 | include/qpdf/auto_job_c_enc.hh 28446f3c32153a52afa239ea40503e6cc8ac2c026813526a349e0cd4ae17ddd5 |
| 7 | -include/qpdf/auto_job_c_global.hh f1dc365206d033a0d6b19b6e561cc244fbd5b49a8d9604b5b646a5fd92895a5a | 7 | +include/qpdf/auto_job_c_global.hh 7df0ff87d18d7fa6d57437960377509420b6b6eb9527b534996f86d3bd7a0ddc |
| 8 | include/qpdf/auto_job_c_main.hh b865eb827356554763bb8349eadfcbc5cb260f80e025a5e229467c525007356d | 8 | include/qpdf/auto_job_c_main.hh b865eb827356554763bb8349eadfcbc5cb260f80e025a5e229467c525007356d |
| 9 | include/qpdf/auto_job_c_pages.hh 09ca15649cc94fdaf6d9bdae28a20723f2a66616bf15aa86d83df31051d82506 | 9 | include/qpdf/auto_job_c_pages.hh 09ca15649cc94fdaf6d9bdae28a20723f2a66616bf15aa86d83df31051d82506 |
| 10 | include/qpdf/auto_job_c_uo.hh 9c2f98a355858dd54d0bba444b73177a59c9e56833e02fa6406f429c07f39e62 | 10 | include/qpdf/auto_job_c_uo.hh 9c2f98a355858dd54d0bba444b73177a59c9e56833e02fa6406f429c07f39e62 |
| 11 | -job.yml 131922d22086d9f4710743e18229cc1e956268197bcae8e1aae30f3be42877be | 11 | +job.yml fa98c8444c8a22a89aeeb76670aa5919aa7a86ebfac2eb45018602fbc7e45b79 |
| 12 | libqpdf/qpdf/auto_job_decl.hh d612a02839e4f20a80e1c6a3ba09c17187fccddc3581ec7ebb1e3919ffd6801d | 12 | libqpdf/qpdf/auto_job_decl.hh d612a02839e4f20a80e1c6a3ba09c17187fccddc3581ec7ebb1e3919ffd6801d |
| 13 | -libqpdf/qpdf/auto_job_help.hh 00ac90c621b6c0529d7bad9ea596f57595517901c8d33f49d2812fbea52dfb41 | ||
| 14 | -libqpdf/qpdf/auto_job_init.hh 889dde948e0ab53616584976d9520ab7ab3773c787d241f8a107f5e2f9f2112f | 13 | +libqpdf/qpdf/auto_job_help.hh 7503b1083c952ace12976857c8ece4e1af67788af9f827fb4248bd22329f93cd |
| 14 | +libqpdf/qpdf/auto_job_init.hh 10a697528d4cae1ac566ee7612f62e611190b3c10c0021862a77fa7e4f330570 | ||
| 15 | libqpdf/qpdf/auto_job_json_decl.hh 7dbb83ddadcea39bfd1faa4ca061e1e3c3134d693b8ae634b463e7e19dc8bd0a | 15 | libqpdf/qpdf/auto_job_json_decl.hh 7dbb83ddadcea39bfd1faa4ca061e1e3c3134d693b8ae634b463e7e19dc8bd0a |
| 16 | -libqpdf/qpdf/auto_job_json_init.hh 3c5f3d07a85e89dd7ecd79342c18e1f0ad580fc57758abb434aa9c9ae277c01e | ||
| 17 | -libqpdf/qpdf/auto_job_schema.hh eb21c99d3a4dc40b333fd1b19d5de52f8813c74a1d4ca830ea4c3311c120d63e | 16 | +libqpdf/qpdf/auto_job_json_init.hh e9cacbcb78ca250a962c226a935067ef9b76f5485bae7e5302eea0a1a8e2ff65 |
| 17 | +libqpdf/qpdf/auto_job_schema.hh 2b974a436c5b4d03fb38258d6213f993cfa9f673834cebe754b4c7ad657481c9 | ||
| 18 | manual/_ext/qpdf.py 6add6321666031d55ed4aedf7c00e5662bba856dfcd66ccb526563bffefbb580 | 18 | manual/_ext/qpdf.py 6add6321666031d55ed4aedf7c00e5662bba856dfcd66ccb526563bffefbb580 |
| 19 | -manual/cli.rst 08e9e7a18d2b0d05102a072f82eabf9ede6bfb1fb797be307ea680eed93ea60f | ||
| 20 | -manual/qpdf.1 19a45f8de6b7c0584fe4395c4ae98b92147a2875e45dbdf729c70e644ccca295 | 19 | +manual/cli.rst 0b0f6a1d8ec523751d91999586bca1356abd8f17e207bc0139ce5d7dfd64fdb4 |
| 20 | +manual/qpdf.1 c1d6e58e37aed1b8d434b37edd1837b7261c9933b09d64bf3915dc3f35d6cccb | ||
| 21 | manual/qpdf.1.in 436ecc85d45c4c9e2dbd1725fb7f0177fb627179469f114561adf3cb6cbb677b | 21 | manual/qpdf.1.in 436ecc85d45c4c9e2dbd1725fb7f0177fb627179469f114561adf3cb6cbb677b |
job.yml
| @@ -95,6 +95,7 @@ options: | @@ -95,6 +95,7 @@ options: | ||
| 95 | parser-max-container-size-damaged: level | 95 | parser-max-container-size-damaged: level |
| 96 | parser-max-errors: level | 96 | parser-max-errors: level |
| 97 | parser-max-nesting: level | 97 | parser-max-nesting: level |
| 98 | + max-stream-filters: level | ||
| 98 | - table: main | 99 | - table: main |
| 99 | config: c_main | 100 | config: c_main |
| 100 | manual: | 101 | manual: |
| @@ -419,6 +420,7 @@ json: | @@ -419,6 +420,7 @@ json: | ||
| 419 | parser-max-container-size-damaged: | 420 | parser-max-container-size-damaged: |
| 420 | parser-max-errors: | 421 | parser-max-errors: |
| 421 | parser-max-nesting: | 422 | parser-max-nesting: |
| 423 | + max-stream-filters: | ||
| 422 | # other options | 424 | # other options |
| 423 | update-from-json: | 425 | update-from-json: |
| 424 | allow-weak-crypto: | 426 | allow-weak-crypto: |
libqpdf/QPDFJob_config.cc
| @@ -1216,6 +1216,13 @@ QPDFJob::GlobalConfig::parserMaxNesting(const std::string& parameter) | @@ -1216,6 +1216,13 @@ QPDFJob::GlobalConfig::parserMaxNesting(const std::string& parameter) | ||
| 1216 | return this; | 1216 | return this; |
| 1217 | } | 1217 | } |
| 1218 | 1218 | ||
| 1219 | +QPDFJob::GlobalConfig* | ||
| 1220 | +QPDFJob::GlobalConfig::maxStreamFilters(const std::string& parameter) | ||
| 1221 | +{ | ||
| 1222 | + global::Limits::max_stream_filters(to_uint32("max-stream-filters", parameter)); | ||
| 1223 | + return this; | ||
| 1224 | +} | ||
| 1225 | + | ||
| 1219 | QPDFJob::Config* | 1226 | QPDFJob::Config* |
| 1220 | QPDFJob::Config::setPageLabels(const std::vector<std::string>& specs) | 1227 | QPDFJob::Config::setPageLabels(const std::vector<std::string>& specs) |
| 1221 | { | 1228 | { |
libqpdf/QPDF_Stream.cc
| @@ -513,6 +513,13 @@ Stream::filterable( | @@ -513,6 +513,13 @@ Stream::filterable( | ||
| 513 | // No filters | 513 | // No filters |
| 514 | return true; | 514 | return true; |
| 515 | } | 515 | } |
| 516 | + if (filter_obj.size() > global::Limits::max_stream_filters()) { | ||
| 517 | + global::Limits::error(); | ||
| 518 | + warn( | ||
| 519 | + "limits error(max-stream-filters): too many filters for stream; treating stream as " | ||
| 520 | + "not filterable"); | ||
| 521 | + return false; | ||
| 522 | + } | ||
| 516 | if (filter_obj.isName()) { | 523 | if (filter_obj.isName()) { |
| 517 | // One filter | 524 | // One filter |
| 518 | auto ff = s->filter_factory(filter_obj.getName()); | 525 | auto ff = s->filter_factory(filter_obj.getName()); |
libqpdf/global.cc
| @@ -28,6 +28,9 @@ Limits::disable_defaults() | @@ -28,6 +28,9 @@ Limits::disable_defaults() | ||
| 28 | if (!l.parser_max_container_size_damaged_set_) { | 28 | if (!l.parser_max_container_size_damaged_set_) { |
| 29 | l.parser_max_container_size_damaged_ = std::numeric_limits<uint32_t>::max(); | 29 | l.parser_max_container_size_damaged_ = std::numeric_limits<uint32_t>::max(); |
| 30 | } | 30 | } |
| 31 | + if (!l.max_stream_filters_set_) { | ||
| 32 | + l.max_stream_filters_ = std::numeric_limits<uint32_t>::max(); | ||
| 33 | + } | ||
| 31 | } | 34 | } |
| 32 | 35 | ||
| 33 | qpdf_result_e | 36 | qpdf_result_e |
| @@ -56,6 +59,9 @@ qpdf_global_get_uint32(qpdf_param_e param, uint32_t* value) | @@ -56,6 +59,9 @@ qpdf_global_get_uint32(qpdf_param_e param, uint32_t* value) | ||
| 56 | case qpdf_p_parser_max_container_size_damaged: | 59 | case qpdf_p_parser_max_container_size_damaged: |
| 57 | *value = Limits::parser_max_container_size(true); | 60 | *value = Limits::parser_max_container_size(true); |
| 58 | return qpdf_r_ok; | 61 | return qpdf_r_ok; |
| 62 | + case qpdf_p_max_stream_filters: | ||
| 63 | + *value = Limits::max_stream_filters(); | ||
| 64 | + return qpdf_r_ok; | ||
| 59 | default: | 65 | default: |
| 60 | return qpdf_r_bad_parameter; | 66 | return qpdf_r_bad_parameter; |
| 61 | } | 67 | } |
| @@ -83,6 +89,9 @@ qpdf_global_set_uint32(qpdf_param_e param, uint32_t value) | @@ -83,6 +89,9 @@ qpdf_global_set_uint32(qpdf_param_e param, uint32_t value) | ||
| 83 | case qpdf_p_parser_max_container_size_damaged: | 89 | case qpdf_p_parser_max_container_size_damaged: |
| 84 | Limits::parser_max_container_size(true, value); | 90 | Limits::parser_max_container_size(true, value); |
| 85 | return qpdf_r_ok; | 91 | return qpdf_r_ok; |
| 92 | + case qpdf_p_max_stream_filters: | ||
| 93 | + Limits::max_stream_filters(value); | ||
| 94 | + return qpdf_r_ok; | ||
| 86 | default: | 95 | default: |
| 87 | return qpdf_r_bad_parameter; | 96 | return qpdf_r_bad_parameter; |
| 88 | } | 97 | } |
libqpdf/qpdf/auto_job_help.hh
| @@ -1044,6 +1044,13 @@ See also --parser-max-container-size. | @@ -1044,6 +1044,13 @@ See also --parser-max-container-size. | ||
| 1044 | } | 1044 | } |
| 1045 | static void add_help_9(QPDFArgParser& ap) | 1045 | static void add_help_9(QPDFArgParser& ap) |
| 1046 | { | 1046 | { |
| 1047 | +ap.addOptionHelp("--max-stream-filters", "global", "set the maximum number of filters allowed when filtering streams", R"(--max-stream-filters=n | ||
| 1048 | + | ||
| 1049 | +An excessive number of stream filters is usually a sign that a file | ||
| 1050 | +is damaged or specially constructed. If the maximum is exceeded for | ||
| 1051 | +a stream the stream is treated as unfilterable. | ||
| 1052 | +The default limit is 25. | ||
| 1053 | +)"); | ||
| 1047 | ap.addHelpTopic("testing", "options for testing or debugging", R"(The options below are useful when writing automated test code that | 1054 | ap.addHelpTopic("testing", "options for testing or debugging", R"(The options below are useful when writing automated test code that |
| 1048 | includes files created by qpdf or when testing qpdf itself. | 1055 | includes files created by qpdf or when testing qpdf itself. |
| 1049 | )"); | 1056 | )"); |
libqpdf/qpdf/auto_job_init.hh
| @@ -40,6 +40,7 @@ this->ap.addRequiredParameter("parser-max-container-size", [this](std::string co | @@ -40,6 +40,7 @@ this->ap.addRequiredParameter("parser-max-container-size", [this](std::string co | ||
| 40 | this->ap.addRequiredParameter("parser-max-container-size-damaged", [this](std::string const& x){c_global->parserMaxContainerSizeDamaged(x);}, "level"); | 40 | this->ap.addRequiredParameter("parser-max-container-size-damaged", [this](std::string const& x){c_global->parserMaxContainerSizeDamaged(x);}, "level"); |
| 41 | this->ap.addRequiredParameter("parser-max-errors", [this](std::string const& x){c_global->parserMaxErrors(x);}, "level"); | 41 | this->ap.addRequiredParameter("parser-max-errors", [this](std::string const& x){c_global->parserMaxErrors(x);}, "level"); |
| 42 | this->ap.addRequiredParameter("parser-max-nesting", [this](std::string const& x){c_global->parserMaxNesting(x);}, "level"); | 42 | this->ap.addRequiredParameter("parser-max-nesting", [this](std::string const& x){c_global->parserMaxNesting(x);}, "level"); |
| 43 | +this->ap.addRequiredParameter("max-stream-filters", [this](std::string const& x){c_global->maxStreamFilters(x);}, "level"); | ||
| 43 | this->ap.selectMainOptionTable(); | 44 | this->ap.selectMainOptionTable(); |
| 44 | this->ap.addPositional(p(&ArgParser::argPositional)); | 45 | this->ap.addPositional(p(&ArgParser::argPositional)); |
| 45 | this->ap.addBare("add-attachment", b(&ArgParser::argAddAttachment)); | 46 | this->ap.addBare("add-attachment", b(&ArgParser::argAddAttachment)); |
libqpdf/qpdf/auto_job_json_init.hh
| @@ -289,6 +289,9 @@ popHandler(); // key: parserMaxErrors | @@ -289,6 +289,9 @@ popHandler(); // key: parserMaxErrors | ||
| 289 | pushKey("parserMaxNesting"); | 289 | pushKey("parserMaxNesting"); |
| 290 | addParameter([this](std::string const& p) { c_global->parserMaxNesting(p); }); | 290 | addParameter([this](std::string const& p) { c_global->parserMaxNesting(p); }); |
| 291 | popHandler(); // key: parserMaxNesting | 291 | popHandler(); // key: parserMaxNesting |
| 292 | +pushKey("maxStreamFilters"); | ||
| 293 | +addParameter([this](std::string const& p) { c_global->maxStreamFilters(p); }); | ||
| 294 | +popHandler(); // key: maxStreamFilters | ||
| 292 | popHandler(); // key: global | 295 | popHandler(); // key: global |
| 293 | pushKey("updateFromJson"); | 296 | pushKey("updateFromJson"); |
| 294 | addParameter([this](std::string const& p) { c_main->updateFromJson(p); }); | 297 | addParameter([this](std::string const& p) { c_main->updateFromJson(p); }); |
libqpdf/qpdf/auto_job_schema.hh
| @@ -95,7 +95,8 @@ static constexpr char const* JOB_SCHEMA_DATA = R"({ | @@ -95,7 +95,8 @@ static constexpr char const* JOB_SCHEMA_DATA = R"({ | ||
| 95 | "parserMaxContainerSize": "set the maximum container size while parsing", | 95 | "parserMaxContainerSize": "set the maximum container size while parsing", |
| 96 | "parserMaxContainerSizeDamaged": "set the maximum container size while parsing damaged files", | 96 | "parserMaxContainerSizeDamaged": "set the maximum container size while parsing damaged files", |
| 97 | "parserMaxErrors": "set the maximum number of errors while parsing", | 97 | "parserMaxErrors": "set the maximum number of errors while parsing", |
| 98 | - "parserMaxNesting": "set the maximum nesting level while parsing objects" | 98 | + "parserMaxNesting": "set the maximum nesting level while parsing objects", |
| 99 | + "maxStreamFilters": "set the maximum number of filters allowed when filtering streams" | ||
| 99 | }, | 100 | }, |
| 100 | "updateFromJson": "update a PDF from qpdf JSON", | 101 | "updateFromJson": "update a PDF from qpdf JSON", |
| 101 | "allowWeakCrypto": "allow insecure cryptographic algorithms", | 102 | "allowWeakCrypto": "allow insecure cryptographic algorithms", |
libqpdf/qpdf/global_private.hh
| @@ -48,6 +48,19 @@ namespace qpdf::global | @@ -48,6 +48,19 @@ namespace qpdf::global | ||
| 48 | 48 | ||
| 49 | static void parser_max_container_size(bool damaged, uint32_t value); | 49 | static void parser_max_container_size(bool damaged, uint32_t value); |
| 50 | 50 | ||
| 51 | + static uint32_t const& | ||
| 52 | + max_stream_filters() | ||
| 53 | + { | ||
| 54 | + return l.max_stream_filters_; | ||
| 55 | + } | ||
| 56 | + | ||
| 57 | + static void | ||
| 58 | + max_stream_filters(uint32_t value) | ||
| 59 | + { | ||
| 60 | + l.max_stream_filters_set_ = true; | ||
| 61 | + l.max_stream_filters_ = value; | ||
| 62 | + } | ||
| 63 | + | ||
| 51 | /// Record a limit error. | 64 | /// Record a limit error. |
| 52 | static void | 65 | static void |
| 53 | error() | 66 | error() |
| @@ -79,6 +92,8 @@ namespace qpdf::global | @@ -79,6 +92,8 @@ namespace qpdf::global | ||
| 79 | uint32_t parser_max_container_size_{std::numeric_limits<uint32_t>::max()}; | 92 | uint32_t parser_max_container_size_{std::numeric_limits<uint32_t>::max()}; |
| 80 | uint32_t parser_max_container_size_damaged_{5'000}; | 93 | uint32_t parser_max_container_size_damaged_{5'000}; |
| 81 | bool parser_max_container_size_damaged_set_{false}; | 94 | bool parser_max_container_size_damaged_set_{false}; |
| 95 | + uint32_t max_stream_filters_{25}; | ||
| 96 | + bool max_stream_filters_set_{false}; | ||
| 82 | }; | 97 | }; |
| 83 | 98 | ||
| 84 | class Options | 99 | class Options |
libtests/objects.cc
| @@ -4,6 +4,7 @@ | @@ -4,6 +4,7 @@ | ||
| 4 | 4 | ||
| 5 | #include <qpdf/QPDF.hh> | 5 | #include <qpdf/QPDF.hh> |
| 6 | 6 | ||
| 7 | +#include <qpdf/Pl_Discard.hh> | ||
| 7 | #include <qpdf/QIntC.hh> | 8 | #include <qpdf/QIntC.hh> |
| 8 | #include <qpdf/QPDFJob.hh> | 9 | #include <qpdf/QPDFJob.hh> |
| 9 | #include <qpdf/QPDFObjectHandle_private.hh> | 10 | #include <qpdf/QPDFObjectHandle_private.hh> |
| @@ -167,6 +168,7 @@ test_2(QPDF& pdf, char const* arg2) | @@ -167,6 +168,7 @@ test_2(QPDF& pdf, char const* arg2) | ||
| 167 | assert(parser_max_errors() == 15); | 168 | assert(parser_max_errors() == 15); |
| 168 | assert(parser_max_container_size() == std::numeric_limits<uint32_t>::max()); | 169 | assert(parser_max_container_size() == std::numeric_limits<uint32_t>::max()); |
| 169 | assert(parser_max_container_size_damaged() == 5'000); | 170 | assert(parser_max_container_size_damaged() == 5'000); |
| 171 | + assert(max_stream_filters() == 25); | ||
| 170 | assert(default_limits()); | 172 | assert(default_limits()); |
| 171 | 173 | ||
| 172 | // Test disabling optional default limits | 174 | // Test disabling optional default limits |
| @@ -175,6 +177,7 @@ test_2(QPDF& pdf, char const* arg2) | @@ -175,6 +177,7 @@ test_2(QPDF& pdf, char const* arg2) | ||
| 175 | assert(parser_max_errors() == 0); | 177 | assert(parser_max_errors() == 0); |
| 176 | assert(parser_max_container_size() == std::numeric_limits<uint32_t>::max()); | 178 | assert(parser_max_container_size() == std::numeric_limits<uint32_t>::max()); |
| 177 | assert(parser_max_container_size_damaged() == std::numeric_limits<uint32_t>::max()); | 179 | assert(parser_max_container_size_damaged() == std::numeric_limits<uint32_t>::max()); |
| 180 | + assert(max_stream_filters() == std::numeric_limits<uint32_t>::max()); | ||
| 178 | assert(!default_limits()); | 181 | assert(!default_limits()); |
| 179 | 182 | ||
| 180 | // Check disabling default limits is irreversible | 183 | // Check disabling default limits is irreversible |
| @@ -186,11 +189,13 @@ test_2(QPDF& pdf, char const* arg2) | @@ -186,11 +189,13 @@ test_2(QPDF& pdf, char const* arg2) | ||
| 186 | parser_max_errors(12); | 189 | parser_max_errors(12); |
| 187 | parser_max_container_size(13); | 190 | parser_max_container_size(13); |
| 188 | parser_max_container_size_damaged(14); | 191 | parser_max_container_size_damaged(14); |
| 192 | + max_stream_filters(15); | ||
| 189 | 193 | ||
| 190 | assert(parser_max_nesting() == 11); | 194 | assert(parser_max_nesting() == 11); |
| 191 | assert(parser_max_errors() == 12); | 195 | assert(parser_max_errors() == 12); |
| 192 | assert(parser_max_container_size() == 13); | 196 | assert(parser_max_container_size() == 13); |
| 193 | assert(parser_max_container_size_damaged() == 14); | 197 | assert(parser_max_container_size_damaged() == 14); |
| 198 | + assert(max_stream_filters() == 15); | ||
| 194 | 199 | ||
| 195 | // Check disabling default limits does not override explicit limits | 200 | // Check disabling default limits does not override explicit limits |
| 196 | default_limits(false); | 201 | default_limits(false); |
| @@ -198,6 +203,7 @@ test_2(QPDF& pdf, char const* arg2) | @@ -198,6 +203,7 @@ test_2(QPDF& pdf, char const* arg2) | ||
| 198 | assert(parser_max_errors() == 12); | 203 | assert(parser_max_errors() == 12); |
| 199 | assert(parser_max_container_size() == 13); | 204 | assert(parser_max_container_size() == 13); |
| 200 | assert(parser_max_container_size_damaged() == 14); | 205 | assert(parser_max_container_size_damaged() == 14); |
| 206 | + assert(max_stream_filters() == 15); | ||
| 201 | 207 | ||
| 202 | // Test parameter checking | 208 | // Test parameter checking |
| 203 | QUtil::handle_result_code(qpdf_r_ok, ""); | 209 | QUtil::handle_result_code(qpdf_r_ok, ""); |
| @@ -239,6 +245,19 @@ test_2(QPDF& pdf, char const* arg2) | @@ -239,6 +245,19 @@ test_2(QPDF& pdf, char const* arg2) | ||
| 239 | } | 245 | } |
| 240 | assert(qpdf::global::limit_errors() == 2); | 246 | assert(qpdf::global::limit_errors() == 2); |
| 241 | 247 | ||
| 248 | + // Test max_stream_filters | ||
| 249 | + QPDF qpdf; | ||
| 250 | + qpdf.emptyPDF(); | ||
| 251 | + auto s = qpdf.newStream("\x01\x01\x01A"); | ||
| 252 | + s.getDict().replace("/Filter", Array({Name("/RL"), Name("/RL"), Name("/RL")})); | ||
| 253 | + Pl_Discard p; | ||
| 254 | + auto x = s.pipeStreamData(&p, 0, qpdf_dl_all, true); | ||
| 255 | + assert(x); | ||
| 256 | + max_stream_filters(2); | ||
| 257 | + assert(!s.pipeStreamData(&p, 0, qpdf_dl_all, true)); | ||
| 258 | + max_stream_filters(3); | ||
| 259 | + assert(s.pipeStreamData(&p, 0, qpdf_dl_all, true)); | ||
| 260 | + | ||
| 242 | // Test global settings using the QPDFJob interface | 261 | // Test global settings using the QPDFJob interface |
| 243 | QPDFJob j; | 262 | QPDFJob j; |
| 244 | j.config() | 263 | j.config() |
| @@ -248,14 +267,16 @@ test_2(QPDF& pdf, char const* arg2) | @@ -248,14 +267,16 @@ test_2(QPDF& pdf, char const* arg2) | ||
| 248 | ->parserMaxErrors("112") | 267 | ->parserMaxErrors("112") |
| 249 | ->parserMaxContainerSize("113") | 268 | ->parserMaxContainerSize("113") |
| 250 | ->parserMaxContainerSizeDamaged("114") | 269 | ->parserMaxContainerSizeDamaged("114") |
| 270 | + ->maxStreamFilters("115") | ||
| 251 | ->noDefaultLimits() | 271 | ->noDefaultLimits() |
| 252 | ->endGlobal() | 272 | ->endGlobal() |
| 253 | ->outputFile("a.pdf"); | 273 | ->outputFile("a.pdf"); |
| 254 | - auto qpdf = j.createQPDF(); | 274 | + auto qpdf_uptr = j.createQPDF(); |
| 255 | assert(parser_max_nesting() == 111); | 275 | assert(parser_max_nesting() == 111); |
| 256 | assert(parser_max_errors() == 112); | 276 | assert(parser_max_errors() == 112); |
| 257 | assert(parser_max_container_size() == 113); | 277 | assert(parser_max_container_size() == 113); |
| 258 | assert(parser_max_container_size_damaged() == 114); | 278 | assert(parser_max_container_size_damaged() == 114); |
| 279 | + assert(max_stream_filters() == 115); | ||
| 259 | assert(!default_limits()); | 280 | assert(!default_limits()); |
| 260 | 281 | ||
| 261 | // Test global settings using the JobJSON | 282 | // Test global settings using the JobJSON |
| @@ -268,16 +289,18 @@ test_2(QPDF& pdf, char const* arg2) | @@ -268,16 +289,18 @@ test_2(QPDF& pdf, char const* arg2) | ||
| 268 | "parserMaxErrors": "212", | 289 | "parserMaxErrors": "212", |
| 269 | "parserMaxContainerSize": "213", | 290 | "parserMaxContainerSize": "213", |
| 270 | "parserMaxContainerSizeDamaged": "214", | 291 | "parserMaxContainerSizeDamaged": "214", |
| 292 | + "maxStreamFilters": "215", | ||
| 271 | "noDefaultLimits": "" | 293 | "noDefaultLimits": "" |
| 272 | }, | 294 | }, |
| 273 | "outputFile": "a.pdf" | 295 | "outputFile": "a.pdf" |
| 274 | } | 296 | } |
| 275 | )"); | 297 | )"); |
| 276 | - qpdf = j.createQPDF(); | 298 | + qpdf_uptr = jj.createQPDF(); |
| 277 | assert(parser_max_nesting() == 211); | 299 | assert(parser_max_nesting() == 211); |
| 278 | assert(parser_max_errors() == 212); | 300 | assert(parser_max_errors() == 212); |
| 279 | assert(parser_max_container_size() == 213); | 301 | assert(parser_max_container_size() == 213); |
| 280 | assert(parser_max_container_size_damaged() == 214); | 302 | assert(parser_max_container_size_damaged() == 214); |
| 303 | + assert(max_stream_filters() == 215); | ||
| 281 | assert(!default_limits()); | 304 | assert(!default_limits()); |
| 282 | } | 305 | } |
| 283 | 306 |
libtests/qtest/objects.test
| @@ -20,12 +20,12 @@ $td->runtest("integer type checks", | @@ -20,12 +20,12 @@ $td->runtest("integer type checks", | ||
| 20 | 20 | ||
| 21 | $td->runtest("dictionary checks", | 21 | $td->runtest("dictionary checks", |
| 22 | {$td->COMMAND => "objects 1 -"}, | 22 | {$td->COMMAND => "objects 1 -"}, |
| 23 | - {$td->STRING => => "test 1 done\n", $td->EXIT_STATUS => 0}, | 23 | + {$td->STRING => "test 1 done\n", $td->EXIT_STATUS => 0}, |
| 24 | $td->NORMALIZE_NEWLINES); | 24 | $td->NORMALIZE_NEWLINES); |
| 25 | 25 | ||
| 26 | -$td->runtest("global object limits", | 26 | +$td->runtest("global limits", |
| 27 | {$td->COMMAND => "objects 2 -"}, | 27 | {$td->COMMAND => "objects 2 -"}, |
| 28 | - {$td->STRING => => "test 2 done\n", $td->EXIT_STATUS => 0}, | 28 | + {$td->FILE => "test2.out", $td->EXIT_STATUS => 0}, |
| 29 | $td->NORMALIZE_NEWLINES); | 29 | $td->NORMALIZE_NEWLINES); |
| 30 | 30 | ||
| 31 | $td->report($n_tests); | 31 | $td->report($n_tests); |
libtests/qtest/objects/test2.out
0 → 100644
manual/cli.rst
| @@ -3822,7 +3822,6 @@ Parser Limits | @@ -3822,7 +3822,6 @@ Parser Limits | ||
| 3822 | Set the maximum nesting level while parsing objects. The maximum nesting level is not | 3822 | Set the maximum nesting level while parsing objects. The maximum nesting level is not |
| 3823 | disabled by :qpdf:ref:`--no-default-limits`. Defaults to 499. | 3823 | disabled by :qpdf:ref:`--no-default-limits`. Defaults to 499. |
| 3824 | 3824 | ||
| 3825 | - | ||
| 3826 | .. qpdf:option:: --parser-max-errors=n | 3825 | .. qpdf:option:: --parser-max-errors=n |
| 3827 | 3826 | ||
| 3828 | .. help: set the maximum number of errors while parsing | 3827 | .. help: set the maximum number of errors while parsing |
| @@ -3848,7 +3847,6 @@ parsing. The limit applies when the PDF document's xref table is undamaged | @@ -3848,7 +3847,6 @@ parsing. The limit applies when the PDF document's xref table is undamaged | ||
| 3848 | and the object itself can be parsed without errors. The default limit | 3847 | and the object itself can be parsed without errors. The default limit |
| 3849 | is 4,294,967,295. See also :qpdf:ref:`--parser-max-container-size-damaged`. | 3848 | is 4,294,967,295. See also :qpdf:ref:`--parser-max-container-size-damaged`. |
| 3850 | 3849 | ||
| 3851 | - | ||
| 3852 | .. qpdf:option:: --parser-max-container-size-damaged=n | 3850 | .. qpdf:option:: --parser-max-container-size-damaged=n |
| 3853 | 3851 | ||
| 3854 | .. help: set the maximum container size while parsing damaged files | 3852 | .. help: set the maximum container size while parsing damaged files |
| @@ -3866,6 +3864,25 @@ or the object itself is damaged. The limit also applies when parsing | @@ -3866,6 +3864,25 @@ or the object itself is damaged. The limit also applies when parsing | ||
| 3866 | xref streams. The default limit is 5,000. | 3864 | xref streams. The default limit is 5,000. |
| 3867 | See also :qpdf:ref:`--parser-max-container-size`. | 3865 | See also :qpdf:ref:`--parser-max-container-size`. |
| 3868 | 3866 | ||
| 3867 | + | ||
| 3868 | +Stream and Filter Limits | ||
| 3869 | +......................... | ||
| 3870 | + | ||
| 3871 | +.. qpdf:option:: --max-stream-filters=n | ||
| 3872 | + | ||
| 3873 | + .. help: set the maximum number of filters allowed when filtering streams | ||
| 3874 | + | ||
| 3875 | + An excessive number of stream filters is usually a sign that a file | ||
| 3876 | + is damaged or specially constructed. If the maximum is exceeded for | ||
| 3877 | + a stream the stream is treated as unfilterable. | ||
| 3878 | + The default limit is 25. | ||
| 3879 | + | ||
| 3880 | +Set the maximum number of filters allowed when filtering streams. An excessive | ||
| 3881 | +number of stream filters is usually a sign that a file is damaged or specially | ||
| 3882 | +constructed. If the maximum is exceeded for a stream the stream is treated as | ||
| 3883 | +unfilterable. The default limit is 25. | ||
| 3884 | + | ||
| 3885 | + | ||
| 3869 | .. _test-options: | 3886 | .. _test-options: |
| 3870 | 3887 | ||
| 3871 | Options for Testing or Debugging | 3888 | Options for Testing or Debugging |
manual/qpdf.1
| @@ -1236,6 +1236,14 @@ parsing. The limit applies when the PDF document's xref table is damaged | @@ -1236,6 +1236,14 @@ parsing. The limit applies when the PDF document's xref table is damaged | ||
| 1236 | or the object itself is damaged. The limit also applies when parsing | 1236 | or the object itself is damaged. The limit also applies when parsing |
| 1237 | xref streams. The default limit is 5,000. | 1237 | xref streams. The default limit is 5,000. |
| 1238 | See also --parser-max-container-size. | 1238 | See also --parser-max-container-size. |
| 1239 | +.TP | ||
| 1240 | +.B --max-stream-filters \-\- set the maximum number of filters allowed when filtering streams | ||
| 1241 | +--max-stream-filters=n | ||
| 1242 | + | ||
| 1243 | +An excessive number of stream filters is usually a sign that a file | ||
| 1244 | +is damaged or specially constructed. If the maximum is exceeded for | ||
| 1245 | +a stream the stream is treated as unfilterable. | ||
| 1246 | +The default limit is 25. | ||
| 1239 | .SH TESTING (options for testing or debugging) | 1247 | .SH TESTING (options for testing or debugging) |
| 1240 | The options below are useful when writing automated test code that | 1248 | The options below are useful when writing automated test code that |
| 1241 | includes files created by qpdf or when testing qpdf itself. | 1249 | includes files created by qpdf or when testing qpdf itself. |
manual/release-notes.rst
| @@ -106,6 +106,12 @@ more detail. | @@ -106,6 +106,12 @@ more detail. | ||
| 106 | 106 | ||
| 107 | - Other changes | 107 | - Other changes |
| 108 | 108 | ||
| 109 | + - By default, streams with more than 25 filters are now treated as unfilterable. | ||
| 110 | + A large number of filters typically occur in damaged or specially constructed | ||
| 111 | + files and can cause excessive use of resources and/or stack overflows. The | ||
| 112 | + limit can be changed if necessary with the new :qpdf:ref:`--max-stream-filters` | ||
| 113 | + CLI option or the new ``qpdf::global::max_stream_filters`` function. | ||
| 114 | + | ||
| 109 | - When running in a FIPS environment using the GnuTLS crypto provider, | 115 | - When running in a FIPS environment using the GnuTLS crypto provider, |
| 110 | calls to GnuTLS now use 'LAX' mode as the use of weak algorithms is | 116 | calls to GnuTLS now use 'LAX' mode as the use of weak algorithms is |
| 111 | required to decrypt existing files and is specified by the PDF standards | 117 | required to decrypt existing files and is specified by the PDF standards |