Commit 898c2943f8b876a51bf497dfc56c5186dfd6be04
1 parent
a3fca12d
Introduce `--max-stream-filters` to limit filters on streams
Add a new configurable limit `--max-stream-filters` to address excessive stream filters in damaged or specially constructed PDFs. Update related documentation, tests, and release notes to reflect this feature.
Showing
19 changed files
with
157 additions
and
17 deletions
include/qpdf/Constants.h
| ... | ... | @@ -287,6 +287,9 @@ enum qpdf_param_e { |
| 287 | 287 | qpdf_p_parser_max_container_size, |
| 288 | 288 | qpdf_p_parser_max_container_size_damaged, |
| 289 | 289 | |
| 290 | + /* stream and filter limits */ | |
| 291 | + qpdf_p_max_stream_filters = 0x14000, | |
| 292 | + | |
| 290 | 293 | /* next section = 0x20000 */ |
| 291 | 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 | 10 | QPDF_DLL GlobalConfig* parserMaxContainerSizeDamaged(std::string const& parameter); |
| 11 | 11 | QPDF_DLL GlobalConfig* parserMaxErrors(std::string const& parameter); |
| 12 | 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 | 56 | |
| 57 | 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 | 61 | /// @return The number of limit errors. |
| 62 | 62 | /// |
| ... | ... | @@ -229,6 +229,34 @@ namespace qpdf::global |
| 229 | 229 | { |
| 230 | 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 | 260 | } // namespace limits |
| 233 | 261 | |
| 234 | 262 | } // namespace qpdf::global | ... | ... |
job.sums
| ... | ... | @@ -4,18 +4,18 @@ generate_auto_job 8e3175a515aa8837d8a01bba0346b04b3d777d70330ba5b7d52f691316054a |
| 4 | 4 | include/qpdf/auto_job_c_att.hh 4c2b171ea00531db54720bf49a43f8b34481586ae7fb6cbf225099ee42bc5bb4 |
| 5 | 5 | include/qpdf/auto_job_c_copy_att.hh 50609012bff14fd82f0649185940d617d05d530cdc522185c7f3920a561ccb42 |
| 6 | 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 | 8 | include/qpdf/auto_job_c_main.hh b865eb827356554763bb8349eadfcbc5cb260f80e025a5e229467c525007356d |
| 9 | 9 | include/qpdf/auto_job_c_pages.hh 09ca15649cc94fdaf6d9bdae28a20723f2a66616bf15aa86d83df31051d82506 |
| 10 | 10 | include/qpdf/auto_job_c_uo.hh 9c2f98a355858dd54d0bba444b73177a59c9e56833e02fa6406f429c07f39e62 |
| 11 | -job.yml 131922d22086d9f4710743e18229cc1e956268197bcae8e1aae30f3be42877be | |
| 11 | +job.yml fa98c8444c8a22a89aeeb76670aa5919aa7a86ebfac2eb45018602fbc7e45b79 | |
| 12 | 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 | 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 | 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 | 21 | manual/qpdf.1.in 436ecc85d45c4c9e2dbd1725fb7f0177fb627179469f114561adf3cb6cbb677b | ... | ... |
job.yml
| ... | ... | @@ -95,6 +95,7 @@ options: |
| 95 | 95 | parser-max-container-size-damaged: level |
| 96 | 96 | parser-max-errors: level |
| 97 | 97 | parser-max-nesting: level |
| 98 | + max-stream-filters: level | |
| 98 | 99 | - table: main |
| 99 | 100 | config: c_main |
| 100 | 101 | manual: |
| ... | ... | @@ -419,6 +420,7 @@ json: |
| 419 | 420 | parser-max-container-size-damaged: |
| 420 | 421 | parser-max-errors: |
| 421 | 422 | parser-max-nesting: |
| 423 | + max-stream-filters: | |
| 422 | 424 | # other options |
| 423 | 425 | update-from-json: |
| 424 | 426 | allow-weak-crypto: | ... | ... |
libqpdf/QPDFJob_config.cc
| ... | ... | @@ -1216,6 +1216,13 @@ QPDFJob::GlobalConfig::parserMaxNesting(const std::string& parameter) |
| 1216 | 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 | 1226 | QPDFJob::Config* |
| 1220 | 1227 | QPDFJob::Config::setPageLabels(const std::vector<std::string>& specs) |
| 1221 | 1228 | { | ... | ... |
libqpdf/QPDF_Stream.cc
| ... | ... | @@ -513,6 +513,13 @@ Stream::filterable( |
| 513 | 513 | // No filters |
| 514 | 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 | 523 | if (filter_obj.isName()) { |
| 517 | 524 | // One filter |
| 518 | 525 | auto ff = s->filter_factory(filter_obj.getName()); | ... | ... |
libqpdf/global.cc
| ... | ... | @@ -28,6 +28,9 @@ Limits::disable_defaults() |
| 28 | 28 | if (!l.parser_max_container_size_damaged_set_) { |
| 29 | 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 | 36 | qpdf_result_e |
| ... | ... | @@ -56,6 +59,9 @@ qpdf_global_get_uint32(qpdf_param_e param, uint32_t* value) |
| 56 | 59 | case qpdf_p_parser_max_container_size_damaged: |
| 57 | 60 | *value = Limits::parser_max_container_size(true); |
| 58 | 61 | return qpdf_r_ok; |
| 62 | + case qpdf_p_max_stream_filters: | |
| 63 | + *value = Limits::max_stream_filters(); | |
| 64 | + return qpdf_r_ok; | |
| 59 | 65 | default: |
| 60 | 66 | return qpdf_r_bad_parameter; |
| 61 | 67 | } |
| ... | ... | @@ -83,6 +89,9 @@ qpdf_global_set_uint32(qpdf_param_e param, uint32_t value) |
| 83 | 89 | case qpdf_p_parser_max_container_size_damaged: |
| 84 | 90 | Limits::parser_max_container_size(true, value); |
| 85 | 91 | return qpdf_r_ok; |
| 92 | + case qpdf_p_max_stream_filters: | |
| 93 | + Limits::max_stream_filters(value); | |
| 94 | + return qpdf_r_ok; | |
| 86 | 95 | default: |
| 87 | 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 | 1044 | } |
| 1045 | 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 | 1054 | ap.addHelpTopic("testing", "options for testing or debugging", R"(The options below are useful when writing automated test code that |
| 1048 | 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 | 40 | this->ap.addRequiredParameter("parser-max-container-size-damaged", [this](std::string const& x){c_global->parserMaxContainerSizeDamaged(x);}, "level"); |
| 41 | 41 | this->ap.addRequiredParameter("parser-max-errors", [this](std::string const& x){c_global->parserMaxErrors(x);}, "level"); |
| 42 | 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 | 44 | this->ap.selectMainOptionTable(); |
| 44 | 45 | this->ap.addPositional(p(&ArgParser::argPositional)); |
| 45 | 46 | this->ap.addBare("add-attachment", b(&ArgParser::argAddAttachment)); | ... | ... |
libqpdf/qpdf/auto_job_json_init.hh
| ... | ... | @@ -289,6 +289,9 @@ popHandler(); // key: parserMaxErrors |
| 289 | 289 | pushKey("parserMaxNesting"); |
| 290 | 290 | addParameter([this](std::string const& p) { c_global->parserMaxNesting(p); }); |
| 291 | 291 | popHandler(); // key: parserMaxNesting |
| 292 | +pushKey("maxStreamFilters"); | |
| 293 | +addParameter([this](std::string const& p) { c_global->maxStreamFilters(p); }); | |
| 294 | +popHandler(); // key: maxStreamFilters | |
| 292 | 295 | popHandler(); // key: global |
| 293 | 296 | pushKey("updateFromJson"); |
| 294 | 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 | 95 | "parserMaxContainerSize": "set the maximum container size while parsing", |
| 96 | 96 | "parserMaxContainerSizeDamaged": "set the maximum container size while parsing damaged files", |
| 97 | 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 | 101 | "updateFromJson": "update a PDF from qpdf JSON", |
| 101 | 102 | "allowWeakCrypto": "allow insecure cryptographic algorithms", | ... | ... |
libqpdf/qpdf/global_private.hh
| ... | ... | @@ -48,6 +48,19 @@ namespace qpdf::global |
| 48 | 48 | |
| 49 | 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 | 64 | /// Record a limit error. |
| 52 | 65 | static void |
| 53 | 66 | error() |
| ... | ... | @@ -79,6 +92,8 @@ namespace qpdf::global |
| 79 | 92 | uint32_t parser_max_container_size_{std::numeric_limits<uint32_t>::max()}; |
| 80 | 93 | uint32_t parser_max_container_size_damaged_{5'000}; |
| 81 | 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 | 99 | class Options | ... | ... |
libtests/objects.cc
| ... | ... | @@ -4,6 +4,7 @@ |
| 4 | 4 | |
| 5 | 5 | #include <qpdf/QPDF.hh> |
| 6 | 6 | |
| 7 | +#include <qpdf/Pl_Discard.hh> | |
| 7 | 8 | #include <qpdf/QIntC.hh> |
| 8 | 9 | #include <qpdf/QPDFJob.hh> |
| 9 | 10 | #include <qpdf/QPDFObjectHandle_private.hh> |
| ... | ... | @@ -167,6 +168,7 @@ test_2(QPDF& pdf, char const* arg2) |
| 167 | 168 | assert(parser_max_errors() == 15); |
| 168 | 169 | assert(parser_max_container_size() == std::numeric_limits<uint32_t>::max()); |
| 169 | 170 | assert(parser_max_container_size_damaged() == 5'000); |
| 171 | + assert(max_stream_filters() == 25); | |
| 170 | 172 | assert(default_limits()); |
| 171 | 173 | |
| 172 | 174 | // Test disabling optional default limits |
| ... | ... | @@ -175,6 +177,7 @@ test_2(QPDF& pdf, char const* arg2) |
| 175 | 177 | assert(parser_max_errors() == 0); |
| 176 | 178 | assert(parser_max_container_size() == std::numeric_limits<uint32_t>::max()); |
| 177 | 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 | 181 | assert(!default_limits()); |
| 179 | 182 | |
| 180 | 183 | // Check disabling default limits is irreversible |
| ... | ... | @@ -186,11 +189,13 @@ test_2(QPDF& pdf, char const* arg2) |
| 186 | 189 | parser_max_errors(12); |
| 187 | 190 | parser_max_container_size(13); |
| 188 | 191 | parser_max_container_size_damaged(14); |
| 192 | + max_stream_filters(15); | |
| 189 | 193 | |
| 190 | 194 | assert(parser_max_nesting() == 11); |
| 191 | 195 | assert(parser_max_errors() == 12); |
| 192 | 196 | assert(parser_max_container_size() == 13); |
| 193 | 197 | assert(parser_max_container_size_damaged() == 14); |
| 198 | + assert(max_stream_filters() == 15); | |
| 194 | 199 | |
| 195 | 200 | // Check disabling default limits does not override explicit limits |
| 196 | 201 | default_limits(false); |
| ... | ... | @@ -198,6 +203,7 @@ test_2(QPDF& pdf, char const* arg2) |
| 198 | 203 | assert(parser_max_errors() == 12); |
| 199 | 204 | assert(parser_max_container_size() == 13); |
| 200 | 205 | assert(parser_max_container_size_damaged() == 14); |
| 206 | + assert(max_stream_filters() == 15); | |
| 201 | 207 | |
| 202 | 208 | // Test parameter checking |
| 203 | 209 | QUtil::handle_result_code(qpdf_r_ok, ""); |
| ... | ... | @@ -239,6 +245,19 @@ test_2(QPDF& pdf, char const* arg2) |
| 239 | 245 | } |
| 240 | 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 | 261 | // Test global settings using the QPDFJob interface |
| 243 | 262 | QPDFJob j; |
| 244 | 263 | j.config() |
| ... | ... | @@ -248,14 +267,16 @@ test_2(QPDF& pdf, char const* arg2) |
| 248 | 267 | ->parserMaxErrors("112") |
| 249 | 268 | ->parserMaxContainerSize("113") |
| 250 | 269 | ->parserMaxContainerSizeDamaged("114") |
| 270 | + ->maxStreamFilters("115") | |
| 251 | 271 | ->noDefaultLimits() |
| 252 | 272 | ->endGlobal() |
| 253 | 273 | ->outputFile("a.pdf"); |
| 254 | - auto qpdf = j.createQPDF(); | |
| 274 | + auto qpdf_uptr = j.createQPDF(); | |
| 255 | 275 | assert(parser_max_nesting() == 111); |
| 256 | 276 | assert(parser_max_errors() == 112); |
| 257 | 277 | assert(parser_max_container_size() == 113); |
| 258 | 278 | assert(parser_max_container_size_damaged() == 114); |
| 279 | + assert(max_stream_filters() == 115); | |
| 259 | 280 | assert(!default_limits()); |
| 260 | 281 | |
| 261 | 282 | // Test global settings using the JobJSON |
| ... | ... | @@ -268,16 +289,18 @@ test_2(QPDF& pdf, char const* arg2) |
| 268 | 289 | "parserMaxErrors": "212", |
| 269 | 290 | "parserMaxContainerSize": "213", |
| 270 | 291 | "parserMaxContainerSizeDamaged": "214", |
| 292 | + "maxStreamFilters": "215", | |
| 271 | 293 | "noDefaultLimits": "" |
| 272 | 294 | }, |
| 273 | 295 | "outputFile": "a.pdf" |
| 274 | 296 | } |
| 275 | 297 | )"); |
| 276 | - qpdf = j.createQPDF(); | |
| 298 | + qpdf_uptr = jj.createQPDF(); | |
| 277 | 299 | assert(parser_max_nesting() == 211); |
| 278 | 300 | assert(parser_max_errors() == 212); |
| 279 | 301 | assert(parser_max_container_size() == 213); |
| 280 | 302 | assert(parser_max_container_size_damaged() == 214); |
| 303 | + assert(max_stream_filters() == 215); | |
| 281 | 304 | assert(!default_limits()); |
| 282 | 305 | } |
| 283 | 306 | ... | ... |
libtests/qtest/objects.test
| ... | ... | @@ -20,12 +20,12 @@ $td->runtest("integer type checks", |
| 20 | 20 | |
| 21 | 21 | $td->runtest("dictionary checks", |
| 22 | 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 | 24 | $td->NORMALIZE_NEWLINES); |
| 25 | 25 | |
| 26 | -$td->runtest("global object limits", | |
| 26 | +$td->runtest("global limits", | |
| 27 | 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 | 29 | $td->NORMALIZE_NEWLINES); |
| 30 | 30 | |
| 31 | 31 | $td->report($n_tests); | ... | ... |
libtests/qtest/objects/test2.out
0 → 100644
manual/cli.rst
| ... | ... | @@ -3822,7 +3822,6 @@ Parser Limits |
| 3822 | 3822 | Set the maximum nesting level while parsing objects. The maximum nesting level is not |
| 3823 | 3823 | disabled by :qpdf:ref:`--no-default-limits`. Defaults to 499. |
| 3824 | 3824 | |
| 3825 | - | |
| 3826 | 3825 | .. qpdf:option:: --parser-max-errors=n |
| 3827 | 3826 | |
| 3828 | 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 | 3847 | and the object itself can be parsed without errors. The default limit |
| 3849 | 3848 | is 4,294,967,295. See also :qpdf:ref:`--parser-max-container-size-damaged`. |
| 3850 | 3849 | |
| 3851 | - | |
| 3852 | 3850 | .. qpdf:option:: --parser-max-container-size-damaged=n |
| 3853 | 3851 | |
| 3854 | 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 | 3864 | xref streams. The default limit is 5,000. |
| 3867 | 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 | 3886 | .. _test-options: |
| 3870 | 3887 | |
| 3871 | 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 | 1236 | or the object itself is damaged. The limit also applies when parsing |
| 1237 | 1237 | xref streams. The default limit is 5,000. |
| 1238 | 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 | 1247 | .SH TESTING (options for testing or debugging) |
| 1240 | 1248 | The options below are useful when writing automated test code that |
| 1241 | 1249 | includes files created by qpdf or when testing qpdf itself. | ... | ... |
manual/release-notes.rst
| ... | ... | @@ -106,6 +106,12 @@ more detail. |
| 106 | 106 | |
| 107 | 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 | 115 | - When running in a FIPS environment using the GnuTLS crypto provider, |
| 110 | 116 | calls to GnuTLS now use 'LAX' mode as the use of weak algorithms is |
| 111 | 117 | required to decrypt existing files and is specified by the PDF standards | ... | ... |