Commit b45e3420d6046c9a7258d36e2536b8fb0485c824

Authored by m-holger
Committed by GitHub
2 parents 9081ac69 a367e56a

Merge pull request #1228 from m-holger/fuzz7

Add further sanity and loop detection checks
fuzz/CMakeLists.txt
@@ -121,6 +121,7 @@ set(CORPUS_OTHER @@ -121,6 +121,7 @@ set(CORPUS_OTHER
121 69857.fuzz 121 69857.fuzz
122 69913.fuzz 122 69913.fuzz
123 69969.fuzz 123 69969.fuzz
  124 + 69977.fuzz
124 ) 125 )
125 126
126 set(CORPUS_DIR ${CMAKE_CURRENT_BINARY_DIR}/qpdf_corpus) 127 set(CORPUS_DIR ${CMAKE_CURRENT_BINARY_DIR}/qpdf_corpus)
fuzz/qpdf_extra/69977.fuzz 0 → 100644
No preview for this file type
fuzz/qpdf_fuzzer.cc
@@ -173,11 +173,11 @@ FuzzHelper::doChecks() @@ -173,11 +173,11 @@ FuzzHelper::doChecks()
173 { 173 {
174 // Get as much coverage as possible in parts of the library that 174 // Get as much coverage as possible in parts of the library that
175 // might benefit from fuzzing. 175 // might benefit from fuzzing.
176 - std::cout << "starting testWrite\n"; 176 + std::cerr << "\ninfo: starting testWrite\n";
177 testWrite(); 177 testWrite();
178 - std::cout << "\nstarting testPages\n\n"; 178 + std::cerr << "\ninfo: starting testPages\n";
179 testPages(); 179 testPages();
180 - std::cout << "\nstarting testOutlines\n\n"; 180 + std::cerr << "\ninfo: starting testOutlines\n";
181 testOutlines(); 181 testOutlines();
182 } 182 }
183 183
fuzz/qtest/fuzz.test
@@ -21,7 +21,7 @@ my @fuzzers = ( @@ -21,7 +21,7 @@ my @fuzzers = (
21 ['pngpredictor' => 1], 21 ['pngpredictor' => 1],
22 ['runlength' => 6], 22 ['runlength' => 6],
23 ['tiffpredictor' => 2], 23 ['tiffpredictor' => 2],
24 - ['qpdf' => 63], # increment when adding new files 24 + ['qpdf' => 64], # increment when adding new files
25 ); 25 );
26 26
27 my $n_tests = 0; 27 my $n_tests = 0;
include/qpdf/QPDF.hh
@@ -1502,6 +1502,9 @@ class QPDF @@ -1502,6 +1502,9 @@ class QPDF
1502 std::shared_ptr<EncryptionParameters> encp; 1502 std::shared_ptr<EncryptionParameters> encp;
1503 std::string pdf_version; 1503 std::string pdf_version;
1504 std::map<QPDFObjGen, QPDFXRefEntry> xref_table; 1504 std::map<QPDFObjGen, QPDFXRefEntry> xref_table;
  1505 + // Various tables are indexed by object id, with potential size id + 1
  1506 + int xref_table_max_id{std::numeric_limits<int>::max() - 1};
  1507 + qpdf_offset_t xref_table_max_offset{0};
1505 std::set<int> deleted_objects; 1508 std::set<int> deleted_objects;
1506 std::map<QPDFObjGen, ObjCache> obj_cache; 1509 std::map<QPDFObjGen, ObjCache> obj_cache;
1507 std::set<QPDFObjGen> resolving; 1510 std::set<QPDFObjGen> resolving;
libqpdf/Pl_DCT.cc
@@ -320,7 +320,7 @@ Pl_DCT::decompress(void* cinfo_p, Buffer* b) @@ -320,7 +320,7 @@ Pl_DCT::decompress(void* cinfo_p, Buffer* b)
320 cinfo->mem->max_memory_to_use = 1'000'000'000; 320 cinfo->mem->max_memory_to_use = 1'000'000'000;
321 // For some corrupt files the memory used internally by libjpeg stays within the above limits 321 // For some corrupt files the memory used internally by libjpeg stays within the above limits
322 // even though the size written to the next pipeline is significantly larger. 322 // even though the size written to the next pipeline is significantly larger.
323 - m->corrupt_data_limit = 100'000'000; 323 + m->corrupt_data_limit = 10'000'000;
324 #endif 324 #endif
325 jpeg_buffer_src(cinfo, b); 325 jpeg_buffer_src(cinfo, b);
326 326
libqpdf/QPDF.cc
@@ -441,6 +441,12 @@ QPDF::parse(char const* password) @@ -441,6 +441,12 @@ QPDF::parse(char const* password)
441 // 30 characters to leave room for the startxref stuff. 441 // 30 characters to leave room for the startxref stuff.
442 m->file->seek(0, SEEK_END); 442 m->file->seek(0, SEEK_END);
443 qpdf_offset_t end_offset = m->file->tell(); 443 qpdf_offset_t end_offset = m->file->tell();
  444 + m->xref_table_max_offset = end_offset;
  445 + // Sanity check on object ids. All objects must appear in xref table / stream. In all realistic
  446 + // scenarios at least 3 bytes are required.
  447 + if (m->xref_table_max_id > m->xref_table_max_offset / 3) {
  448 + m->xref_table_max_id = static_cast<int>(m->xref_table_max_offset / 3);
  449 + }
444 qpdf_offset_t start_offset = (end_offset > 1054 ? end_offset - 1054 : 0); 450 qpdf_offset_t start_offset = (end_offset > 1054 ? end_offset - 1054 : 0);
445 PatternFinder sf(*this, &QPDF::findStartxref); 451 PatternFinder sf(*this, &QPDF::findStartxref);
446 qpdf_offset_t xref_offset = 0; 452 qpdf_offset_t xref_offset = 0;
@@ -494,6 +500,13 @@ QPDF::warn(QPDFExc const&amp; e) @@ -494,6 +500,13 @@ QPDF::warn(QPDFExc const&amp; e)
494 { 500 {
495 m->warnings.push_back(e); 501 m->warnings.push_back(e);
496 if (!m->suppress_warnings) { 502 if (!m->suppress_warnings) {
  503 +#ifdef QPDF_OSS_FUZZ
  504 + if (m->warnings.size() > 20) {
  505 + *m->log->getWarn() << "WARNING: too many warnings - additional warnings surpressed\n";
  506 + m->suppress_warnings = true;
  507 + return;
  508 + }
  509 +#endif
497 *m->log->getWarn() << "WARNING: " << m->warnings.back().what() << "\n"; 510 *m->log->getWarn() << "WARNING: " << m->warnings.back().what() << "\n";
498 } 511 }
499 } 512 }
@@ -547,9 +560,6 @@ QPDF::reconstruct_xref(QPDFExc&amp; e) @@ -547,9 +560,6 @@ QPDF::reconstruct_xref(QPDFExc&amp; e)
547 560
548 m->file->seek(0, SEEK_END); 561 m->file->seek(0, SEEK_END);
549 qpdf_offset_t eof = m->file->tell(); 562 qpdf_offset_t eof = m->file->tell();
550 - // Sanity check on object ids. All objects must appear in xref table / stream. In all realistic  
551 - // scenarios at leat 3 bytes are required.  
552 - auto max_obj_id = eof / 3;  
553 m->file->seek(0, SEEK_SET); 563 m->file->seek(0, SEEK_SET);
554 qpdf_offset_t line_start = 0; 564 qpdf_offset_t line_start = 0;
555 // Don't allow very long tokens here during recovery. 565 // Don't allow very long tokens here during recovery.
@@ -567,7 +577,7 @@ QPDF::reconstruct_xref(QPDFExc&amp; e) @@ -567,7 +577,7 @@ QPDF::reconstruct_xref(QPDFExc&amp; e)
567 if ((t2.isInteger()) && (readToken(m->file, MAX_LEN).isWord("obj"))) { 577 if ((t2.isInteger()) && (readToken(m->file, MAX_LEN).isWord("obj"))) {
568 int obj = QUtil::string_to_int(t1.getValue().c_str()); 578 int obj = QUtil::string_to_int(t1.getValue().c_str());
569 int gen = QUtil::string_to_int(t2.getValue().c_str()); 579 int gen = QUtil::string_to_int(t2.getValue().c_str());
570 - if (obj <= max_obj_id) { 580 + if (obj <= m->xref_table_max_id) {
571 insertReconstructedXrefEntry(obj, token_start, gen); 581 insertReconstructedXrefEntry(obj, token_start, gen);
572 } else { 582 } else {
573 warn(damagedPDF( 583 warn(damagedPDF(
@@ -702,7 +712,7 @@ QPDF::read_xref(qpdf_offset_t xref_offset) @@ -702,7 +712,7 @@ QPDF::read_xref(qpdf_offset_t xref_offset)
702 int size = m->trailer.getKey("/Size").getIntValueAsInt(); 712 int size = m->trailer.getKey("/Size").getIntValueAsInt();
703 int max_obj = 0; 713 int max_obj = 0;
704 if (!m->xref_table.empty()) { 714 if (!m->xref_table.empty()) {
705 - max_obj = (*(m->xref_table.rbegin())).first.getObj(); 715 + max_obj = m->xref_table.rbegin()->first.getObj();
706 } 716 }
707 if (!m->deleted_objects.empty()) { 717 if (!m->deleted_objects.empty()) {
708 max_obj = std::max(max_obj, *(m->deleted_objects.rbegin())); 718 max_obj = std::max(max_obj, *(m->deleted_objects.rbegin()));
@@ -1255,11 +1265,21 @@ QPDF::insertXrefEntry(int obj, int f0, qpdf_offset_t f1, int f2) @@ -1255,11 +1265,21 @@ QPDF::insertXrefEntry(int obj, int f0, qpdf_offset_t f1, int f2)
1255 // If there is already an entry for this object and generation in the table, it means that a 1265 // If there is already an entry for this object and generation in the table, it means that a
1256 // later xref table has registered this object. Disregard this one. 1266 // later xref table has registered this object. Disregard this one.
1257 1267
  1268 + if (obj > m->xref_table_max_id) {
  1269 + // ignore impossibly large object ids or object ids > Size.
  1270 + return;
  1271 + }
  1272 +
1258 if (m->deleted_objects.count(obj)) { 1273 if (m->deleted_objects.count(obj)) {
1259 QTC::TC("qpdf", "QPDF xref deleted object"); 1274 QTC::TC("qpdf", "QPDF xref deleted object");
1260 return; 1275 return;
1261 } 1276 }
1262 1277
  1278 + if (f0 == 2 && static_cast<int>(f1) == obj) {
  1279 + warn(damagedPDF("xref stream", "self-referential object stream " + std::to_string(obj)));
  1280 + return;
  1281 + }
  1282 +
1263 auto [iter, created] = m->xref_table.try_emplace(QPDFObjGen(obj, (f0 == 2 ? 0 : f2))); 1283 auto [iter, created] = m->xref_table.try_emplace(QPDFObjGen(obj, (f0 == 2 ? 0 : f2)));
1264 if (!created) { 1284 if (!created) {
1265 QTC::TC("qpdf", "QPDF xref reused object"); 1285 QTC::TC("qpdf", "QPDF xref reused object");
@@ -1296,12 +1316,11 @@ QPDF::insertFreeXrefEntry(QPDFObjGen og) @@ -1296,12 +1316,11 @@ QPDF::insertFreeXrefEntry(QPDFObjGen og)
1296 void 1316 void
1297 QPDF::insertReconstructedXrefEntry(int obj, qpdf_offset_t f1, int f2) 1317 QPDF::insertReconstructedXrefEntry(int obj, qpdf_offset_t f1, int f2)
1298 { 1318 {
1299 - // Various tables are indexed by object id, with potential size id + 1  
1300 - constexpr static int max_id = std::numeric_limits<int>::max() - 1;  
1301 - if (!(obj > 0 && obj <= max_id && 0 <= f2 && f2 < 65535)) { 1319 + if (!(obj > 0 && obj <= m->xref_table_max_id && 0 <= f2 && f2 < 65535)) {
1302 QTC::TC("qpdf", "QPDF xref overwrite invalid objgen"); 1320 QTC::TC("qpdf", "QPDF xref overwrite invalid objgen");
1303 return; 1321 return;
1304 } 1322 }
  1323 +
1305 QPDFObjGen og(obj, f2); 1324 QPDFObjGen og(obj, f2);
1306 if (!m->deleted_objects.count(obj)) { 1325 if (!m->deleted_objects.count(obj)) {
1307 // deleted_objects stores the uncompressed objects removed from the xref table at the start 1326 // deleted_objects stores the uncompressed objects removed from the xref table at the start
@@ -1911,6 +1930,17 @@ QPDF::resolveObjectsInStream(int obj_stream_number) @@ -1911,6 +1930,17 @@ QPDF::resolveObjectsInStream(int obj_stream_number)
1911 1930
1912 int num = QUtil::string_to_int(tnum.getValue().c_str()); 1931 int num = QUtil::string_to_int(tnum.getValue().c_str());
1913 long long offset = QUtil::string_to_int(toffset.getValue().c_str()); 1932 long long offset = QUtil::string_to_int(toffset.getValue().c_str());
  1933 + if (num > m->xref_table_max_id) {
  1934 + continue;
  1935 + }
  1936 + if (num == obj_stream_number) {
  1937 + warn(damagedPDF(
  1938 + input,
  1939 + m->last_object_description,
  1940 + input->getLastOffset(),
  1941 + "object stream claims to contain itself"));
  1942 + continue;
  1943 + }
1914 offsets[num] = toI(offset + first); 1944 offsets[num] = toI(offset + first);
1915 } 1945 }
1916 1946
@@ -1922,8 +1952,9 @@ QPDF::resolveObjectsInStream(int obj_stream_number) @@ -1922,8 +1952,9 @@ QPDF::resolveObjectsInStream(int obj_stream_number)
1922 m->last_object_description += "object "; 1952 m->last_object_description += "object ";
1923 for (auto const& iter: offsets) { 1953 for (auto const& iter: offsets) {
1924 QPDFObjGen og(iter.first, 0); 1954 QPDFObjGen og(iter.first, 0);
1925 - QPDFXRefEntry const& entry = m->xref_table[og];  
1926 - if ((entry.getType() == 2) && (entry.getObjStreamNumber() == obj_stream_number)) { 1955 + auto entry = m->xref_table.find(og);
  1956 + if (entry != m->xref_table.end() && entry->second.getType() == 2 &&
  1957 + entry->second.getObjStreamNumber() == obj_stream_number) {
1927 int offset = iter.second; 1958 int offset = iter.second;
1928 input->seek(offset, SEEK_SET); 1959 input->seek(offset, SEEK_SET);
1929 QPDFObjectHandle oh = readObjectInStream(input, iter.first); 1960 QPDFObjectHandle oh = readObjectInStream(input, iter.first);
qpdf/qtest/qpdf/issue-118.out
1 WARNING: issue-118.pdf: can't find PDF header 1 WARNING: issue-118.pdf: can't find PDF header
2 -WARNING: issue-118.pdf (offset 732): loop detected resolving object 2 0  
3 -WARNING: issue-118.pdf (xref stream: object 8 0, offset 732): supposed object stream 2 is not a stream 2 +WARNING: issue-118.pdf (xref stream, offset 732): self-referential object stream 2
4 issue-118.pdf: unable to find /Root dictionary 3 issue-118.pdf: unable to find /Root dictionary
qpdf/qtest/qpdf/issue-120.out
  1 +WARNING: issue-120.pdf (xref stream, offset 712): self-referential object stream 3
1 qpdf: issue-120.pdf: unable to find page tree 2 qpdf: issue-120.pdf: unable to find page tree
qpdf/qtest/qpdf/issue-143.out
@@ -3,6 +3,7 @@ WARNING: issue-143.pdf (xref stream: object 3 0, offset 654): stream keyword not @@ -3,6 +3,7 @@ WARNING: issue-143.pdf (xref stream: object 3 0, offset 654): stream keyword not
3 WARNING: issue-143.pdf (xref stream: object 3 0, offset 607): stream dictionary lacks /Length key 3 WARNING: issue-143.pdf (xref stream: object 3 0, offset 607): stream dictionary lacks /Length key
4 WARNING: issue-143.pdf (xref stream: object 3 0, offset 654): attempting to recover stream length 4 WARNING: issue-143.pdf (xref stream: object 3 0, offset 654): attempting to recover stream length
5 WARNING: issue-143.pdf (xref stream: object 3 0, offset 654): recovered stream length: 36 5 WARNING: issue-143.pdf (xref stream: object 3 0, offset 654): recovered stream length: 36
  6 +WARNING: issue-143.pdf (xref stream, offset 654): self-referential object stream 3
6 WARNING: issue-143.pdf: file is damaged 7 WARNING: issue-143.pdf: file is damaged
7 WARNING: issue-143.pdf (object 1 0, offset 48): expected n n obj 8 WARNING: issue-143.pdf (object 1 0, offset 48): expected n n obj
8 WARNING: issue-143.pdf: Attempting to reconstruct cross-reference table 9 WARNING: issue-143.pdf: Attempting to reconstruct cross-reference table