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,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&amp; pdf, char const* arg2) @@ -221,6 +221,22 @@ test_2(QPDF&amp; 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