Commit 2774ac302adf74171dbe2b4cbacfd9eae03d24cd

Authored by m-holger
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.
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&amp; 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
... ...