Commit 2774ac302adf74171dbe2b4cbacfd9eae03d24cd
1 parent
34db6e30
Add `limit_errors` tracking to global options
Enhance the `global` namespace by introducing `limit_errors` for tracking the number of exceeded limits. Update related tests and documentation to ensure functionality and clarity.
Showing
8 changed files
with
61 additions
and
7 deletions
include/qpdf/Constants.h
| ... | ... | @@ -273,12 +273,15 @@ enum qpdf_result_e { |
| 273 | 273 | * options or global limits.For the meaning of individual parameters see `qpdf/global.cc` |
| 274 | 274 | */ |
| 275 | 275 | enum qpdf_param_e { |
| 276 | + /* global state */ | |
| 277 | + qpdf_p_limit_errors = 0x10020, | |
| 278 | + | |
| 276 | 279 | /* global options */ |
| 277 | - qpdf_p_default_limits = 0x10100, | |
| 280 | + qpdf_p_default_limits = 0x11100, | |
| 278 | 281 | /* global limits */ |
| 279 | 282 | |
| 280 | 283 | /* object - parser limits */ |
| 281 | - qpdf_p_objects_max_nesting = 0x11000, | |
| 284 | + qpdf_p_objects_max_nesting = 0x12000, | |
| 282 | 285 | qpdf_p_objects_max_errors, |
| 283 | 286 | qpdf_p_objects_max_container_size, |
| 284 | 287 | qpdf_p_objects_max_container_size_damaged, | ... | ... |
include/qpdf/global.hh
| ... | ... | @@ -54,6 +54,18 @@ namespace qpdf::global |
| 54 | 54 | handle_result(qpdf_global_set_uint32(param, value)); |
| 55 | 55 | } |
| 56 | 56 | |
| 57 | + /// @brief Retrieves the number of limit errors. | |
| 58 | + /// | |
| 59 | + /// Returns the number a global limit was exceeded. This item is reaf only. | |
| 60 | + /// | |
| 61 | + /// @return The number of limit errors. | |
| 62 | + /// | |
| 63 | + /// @since 12.3 | |
| 64 | + uint32_t inline limit_errors() | |
| 65 | + { | |
| 66 | + return get_uint32(qpdf_p_limit_errors); | |
| 67 | + } | |
| 68 | + | |
| 57 | 69 | namespace options |
| 58 | 70 | { |
| 59 | 71 | /// @brief Retrieves whether default limits are enabled. | ... | ... |
libqpdf/QPDFParser.cc
| ... | ... | @@ -437,7 +437,8 @@ QPDFParser::parseRemainder(bool content_stream) |
| 437 | 437 | case QPDFTokenizer::tt_array_open: |
| 438 | 438 | case QPDFTokenizer::tt_dict_open: |
| 439 | 439 | if (stack.size() > max_nesting) { |
| 440 | - warn("ignoring excessively deeply nested data structure"); | |
| 440 | + global::Limits::error(); | |
| 441 | + warn("limits error: ignoring excessively deeply nested data structure"); | |
| 441 | 442 | return {}; |
| 442 | 443 | } else { |
| 443 | 444 | b_contents = false; |
| ... | ... | @@ -647,8 +648,10 @@ QPDFParser::check_too_many_bad_tokens() |
| 647 | 648 | auto limit = Limits::objects_max_container_size(bad_count || sanity_checks); |
| 648 | 649 | if (frame->olist.size() > limit || frame->dict.size() > limit) { |
| 649 | 650 | if (bad_count) { |
| 651 | + Limits::error(); | |
| 650 | 652 | warn( |
| 651 | - "encountered errors while parsing an array or dictionary with more than " + | |
| 653 | + "limits error: encountered errors while parsing an array or dictionary with more " | |
| 654 | + "than " + | |
| 652 | 655 | std::to_string(limit) + " elements; giving up on reading object"); |
| 653 | 656 | throw Error(); |
| 654 | 657 | } | ... | ... |
libqpdf/global.cc
| ... | ... | @@ -38,6 +38,9 @@ qpdf_global_get_uint32(qpdf_param_e param, uint32_t* value) |
| 38 | 38 | case qpdf_p_default_limits: |
| 39 | 39 | *value = Options::default_limits(); |
| 40 | 40 | return qpdf_r_ok; |
| 41 | + case qpdf_p_limit_errors: | |
| 42 | + *value = Limits::errors(); | |
| 43 | + return qpdf_r_ok; | |
| 41 | 44 | case qpdf_p_objects_max_nesting: |
| 42 | 45 | *value = Limits::objects_max_nesting(); |
| 43 | 46 | return qpdf_r_ok; | ... | ... |
libqpdf/qpdf/global_private.hh
| ... | ... | @@ -48,6 +48,21 @@ namespace qpdf::global |
| 48 | 48 | |
| 49 | 49 | static void objects_max_container_size(bool damaged, uint32_t value); |
| 50 | 50 | |
| 51 | + /// Record a limit error. | |
| 52 | + static void | |
| 53 | + error() | |
| 54 | + { | |
| 55 | + if (l.errors_ < std::numeric_limits<uint32_t>::max()) { | |
| 56 | + ++l.errors_; | |
| 57 | + } | |
| 58 | + } | |
| 59 | + | |
| 60 | + static uint32_t const& | |
| 61 | + errors() | |
| 62 | + { | |
| 63 | + return l.errors_; | |
| 64 | + } | |
| 65 | + | |
| 51 | 66 | static void disable_defaults(); |
| 52 | 67 | |
| 53 | 68 | private: |
| ... | ... | @@ -56,6 +71,8 @@ namespace qpdf::global |
| 56 | 71 | |
| 57 | 72 | static Limits l; |
| 58 | 73 | |
| 74 | + uint32_t errors_{0}; | |
| 75 | + | |
| 59 | 76 | uint32_t objects_max_nesting_{499}; |
| 60 | 77 | uint32_t objects_max_errors_{15}; |
| 61 | 78 | bool objects_max_errors_set_{false}; | ... | ... |
libtests/objects.cc
| ... | ... | @@ -221,6 +221,22 @@ test_2(QPDF& pdf, char const* arg2) |
| 221 | 221 | thrown = true; |
| 222 | 222 | } |
| 223 | 223 | assert(thrown); |
| 224 | + | |
| 225 | + /* Test limit errors */ | |
| 226 | + assert(qpdf::global::limit_errors() == 0); | |
| 227 | + QPDFObjectHandle::parse("[[[[]]]]"); | |
| 228 | + assert(qpdf::global::limit_errors() == 0); | |
| 229 | + objects_max_nesting(3); | |
| 230 | + try { | |
| 231 | + QPDFObjectHandle::parse("[[[[[]]]]]"); | |
| 232 | + } catch (std::exception&) { | |
| 233 | + } | |
| 234 | + assert(qpdf::global::limit_errors() == 1); | |
| 235 | + try { | |
| 236 | + QPDFObjectHandle::parse("[[[[[]]]]]"); | |
| 237 | + } catch (std::exception&) { | |
| 238 | + } | |
| 239 | + assert(qpdf::global::limit_errors() == 2); | |
| 224 | 240 | } |
| 225 | 241 | |
| 226 | 242 | void | ... | ... |
qpdf/qtest/qpdf/issue-146.out
| 1 | 1 | WARNING: issue-146.pdf: file is damaged |
| 2 | 2 | WARNING: issue-146.pdf: can't find startxref |
| 3 | 3 | WARNING: issue-146.pdf: Attempting to reconstruct cross-reference table |
| 4 | -WARNING: issue-146.pdf (trailer, offset 695): ignoring excessively deeply nested data structure | |
| 4 | +WARNING: issue-146.pdf (trailer, offset 695): limits error: ignoring excessively deeply nested data structure | |
| 5 | 5 | WARNING: issue-146.pdf (object 1 0, offset 92): expected endobj |
| 6 | 6 | WARNING: issue-146.pdf (object 7 0, offset 146): unknown token while reading object; treating as null |
| 7 | 7 | WARNING: issue-146.pdf (object 7 0, offset 168): expected endobj | ... | ... |
qpdf/qtest/qpdf/issue-202.out
| 1 | -WARNING: issue-202.pdf (trailer, offset 55770): ignoring excessively deeply nested data structure | |
| 1 | +WARNING: issue-202.pdf (trailer, offset 55770): limits error: ignoring excessively deeply nested data structure | |
| 2 | 2 | WARNING: issue-202.pdf: file is damaged |
| 3 | 3 | WARNING: issue-202.pdf (offset 54769): expected trailer dictionary |
| 4 | 4 | WARNING: issue-202.pdf: Attempting to reconstruct cross-reference table |
| 5 | -WARNING: issue-202.pdf (trailer, offset 55770): ignoring excessively deeply nested data structure | |
| 5 | +WARNING: issue-202.pdf (trailer, offset 55770): limits error: ignoring excessively deeply nested data structure | |
| 6 | 6 | WARNING: issue-202.pdf (object 222 0, offset 50101): dictionary has duplicated key /Creator; last occurrence overrides earlier ones |
| 7 | 7 | WARNING: issue-202.pdf (object 222 0, offset 50101): dictionary has duplicated key /Producer; last occurrence overrides earlier ones |
| 8 | 8 | WARNING: issue-202.pdf: unable to find trailer dictionary while recovering damaged file | ... | ... |