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,12 +273,15 @@ enum qpdf_result_e { | ||
| 273 | * options or global limits.For the meaning of individual parameters see `qpdf/global.cc` | 273 | * options or global limits.For the meaning of individual parameters see `qpdf/global.cc` |
| 274 | */ | 274 | */ |
| 275 | enum qpdf_param_e { | 275 | enum qpdf_param_e { |
| 276 | + /* global state */ | ||
| 277 | + qpdf_p_limit_errors = 0x10020, | ||
| 278 | + | ||
| 276 | /* global options */ | 279 | /* global options */ |
| 277 | - qpdf_p_default_limits = 0x10100, | 280 | + qpdf_p_default_limits = 0x11100, |
| 278 | /* global limits */ | 281 | /* global limits */ |
| 279 | 282 | ||
| 280 | /* object - parser limits */ | 283 | /* object - parser limits */ |
| 281 | - qpdf_p_objects_max_nesting = 0x11000, | 284 | + qpdf_p_objects_max_nesting = 0x12000, |
| 282 | qpdf_p_objects_max_errors, | 285 | qpdf_p_objects_max_errors, |
| 283 | qpdf_p_objects_max_container_size, | 286 | qpdf_p_objects_max_container_size, |
| 284 | qpdf_p_objects_max_container_size_damaged, | 287 | qpdf_p_objects_max_container_size_damaged, |
include/qpdf/global.hh
| @@ -54,6 +54,18 @@ namespace qpdf::global | @@ -54,6 +54,18 @@ namespace qpdf::global | ||
| 54 | handle_result(qpdf_global_set_uint32(param, value)); | 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 | namespace options | 69 | namespace options |
| 58 | { | 70 | { |
| 59 | /// @brief Retrieves whether default limits are enabled. | 71 | /// @brief Retrieves whether default limits are enabled. |
libqpdf/QPDFParser.cc
| @@ -437,7 +437,8 @@ QPDFParser::parseRemainder(bool content_stream) | @@ -437,7 +437,8 @@ QPDFParser::parseRemainder(bool content_stream) | ||
| 437 | case QPDFTokenizer::tt_array_open: | 437 | case QPDFTokenizer::tt_array_open: |
| 438 | case QPDFTokenizer::tt_dict_open: | 438 | case QPDFTokenizer::tt_dict_open: |
| 439 | if (stack.size() > max_nesting) { | 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 | return {}; | 442 | return {}; |
| 442 | } else { | 443 | } else { |
| 443 | b_contents = false; | 444 | b_contents = false; |
| @@ -647,8 +648,10 @@ QPDFParser::check_too_many_bad_tokens() | @@ -647,8 +648,10 @@ QPDFParser::check_too_many_bad_tokens() | ||
| 647 | auto limit = Limits::objects_max_container_size(bad_count || sanity_checks); | 648 | auto limit = Limits::objects_max_container_size(bad_count || sanity_checks); |
| 648 | if (frame->olist.size() > limit || frame->dict.size() > limit) { | 649 | if (frame->olist.size() > limit || frame->dict.size() > limit) { |
| 649 | if (bad_count) { | 650 | if (bad_count) { |
| 651 | + Limits::error(); | ||
| 650 | warn( | 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 | std::to_string(limit) + " elements; giving up on reading object"); | 655 | std::to_string(limit) + " elements; giving up on reading object"); |
| 653 | throw Error(); | 656 | throw Error(); |
| 654 | } | 657 | } |
libqpdf/global.cc
| @@ -38,6 +38,9 @@ qpdf_global_get_uint32(qpdf_param_e param, uint32_t* value) | @@ -38,6 +38,9 @@ qpdf_global_get_uint32(qpdf_param_e param, uint32_t* value) | ||
| 38 | case qpdf_p_default_limits: | 38 | case qpdf_p_default_limits: |
| 39 | *value = Options::default_limits(); | 39 | *value = Options::default_limits(); |
| 40 | return qpdf_r_ok; | 40 | return qpdf_r_ok; |
| 41 | + case qpdf_p_limit_errors: | ||
| 42 | + *value = Limits::errors(); | ||
| 43 | + return qpdf_r_ok; | ||
| 41 | case qpdf_p_objects_max_nesting: | 44 | case qpdf_p_objects_max_nesting: |
| 42 | *value = Limits::objects_max_nesting(); | 45 | *value = Limits::objects_max_nesting(); |
| 43 | return qpdf_r_ok; | 46 | return qpdf_r_ok; |
libqpdf/qpdf/global_private.hh
| @@ -48,6 +48,21 @@ namespace qpdf::global | @@ -48,6 +48,21 @@ namespace qpdf::global | ||
| 48 | 48 | ||
| 49 | static void objects_max_container_size(bool damaged, uint32_t value); | 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 | static void disable_defaults(); | 66 | static void disable_defaults(); |
| 52 | 67 | ||
| 53 | private: | 68 | private: |
| @@ -56,6 +71,8 @@ namespace qpdf::global | @@ -56,6 +71,8 @@ namespace qpdf::global | ||
| 56 | 71 | ||
| 57 | static Limits l; | 72 | static Limits l; |
| 58 | 73 | ||
| 74 | + uint32_t errors_{0}; | ||
| 75 | + | ||
| 59 | uint32_t objects_max_nesting_{499}; | 76 | uint32_t objects_max_nesting_{499}; |
| 60 | uint32_t objects_max_errors_{15}; | 77 | uint32_t objects_max_errors_{15}; |
| 61 | bool objects_max_errors_set_{false}; | 78 | bool objects_max_errors_set_{false}; |
libtests/objects.cc
| @@ -221,6 +221,22 @@ test_2(QPDF& pdf, char const* arg2) | @@ -221,6 +221,22 @@ test_2(QPDF& pdf, char const* arg2) | ||
| 221 | thrown = true; | 221 | thrown = true; |
| 222 | } | 222 | } |
| 223 | assert(thrown); | 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 | void | 242 | void |
qpdf/qtest/qpdf/issue-146.out
| 1 | WARNING: issue-146.pdf: file is damaged | 1 | WARNING: issue-146.pdf: file is damaged |
| 2 | WARNING: issue-146.pdf: can't find startxref | 2 | WARNING: issue-146.pdf: can't find startxref |
| 3 | WARNING: issue-146.pdf: Attempting to reconstruct cross-reference table | 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 | WARNING: issue-146.pdf (object 1 0, offset 92): expected endobj | 5 | WARNING: issue-146.pdf (object 1 0, offset 92): expected endobj |
| 6 | WARNING: issue-146.pdf (object 7 0, offset 146): unknown token while reading object; treating as null | 6 | WARNING: issue-146.pdf (object 7 0, offset 146): unknown token while reading object; treating as null |
| 7 | WARNING: issue-146.pdf (object 7 0, offset 168): expected endobj | 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 | WARNING: issue-202.pdf: file is damaged | 2 | WARNING: issue-202.pdf: file is damaged |
| 3 | WARNING: issue-202.pdf (offset 54769): expected trailer dictionary | 3 | WARNING: issue-202.pdf (offset 54769): expected trailer dictionary |
| 4 | WARNING: issue-202.pdf: Attempting to reconstruct cross-reference table | 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 | WARNING: issue-202.pdf (object 222 0, offset 50101): dictionary has duplicated key /Creator; last occurrence overrides earlier ones | 6 | WARNING: issue-202.pdf (object 222 0, offset 50101): dictionary has duplicated key /Creator; last occurrence overrides earlier ones |
| 7 | WARNING: issue-202.pdf (object 222 0, offset 50101): dictionary has duplicated key /Producer; last occurrence overrides earlier ones | 7 | WARNING: issue-202.pdf (object 222 0, offset 50101): dictionary has duplicated key /Producer; last occurrence overrides earlier ones |
| 8 | WARNING: issue-202.pdf: unable to find trailer dictionary while recovering damaged file | 8 | WARNING: issue-202.pdf: unable to find trailer dictionary while recovering damaged file |